GET /.well-known/agent-skills/index.json
Agent Skills Discovery v0.2.0 index control. References a single skill stub at not.catastrophic.io/.well-known/agent-skills/echo.json so the discovery chain resolves end-to-end. The sha256 digest is all-zeros — obviously fake but format-correct, same pattern AASA and assetlinks use on this host. Build against this baseline; flip hostname to chaos to exercise the failure modes.
expect: 200 OK, Content-Type: application/json. $schema present, single skill entry whose url resolves to a valid skill document on this same host. X-Chaos-Origin: control. X-Chaos-Counterpart points at chaos.catastrophic.io/.well-known/agent-skills/index.json.
curl -i 'https://not.catastrophic.io/.well-known/agent-skills/index.json'
# The canonical agent-skills consumer flow: fetch index, walk skills,
# fetch each, verify sha256. Most modes break a different step.
import urllib.request, json, hashlib
index = json.loads(urllib.request.urlopen("https://not.catastrophic.io/.well-known/agent-skills/index.json").read())
print("$schema:", index.get("$schema"))
for skill in index.get("skills", []):
print(f"\n name: {skill['name']}")
try:
body = urllib.request.urlopen(skill["url"]).read()
actual = hashlib.sha256(body).hexdigest()
ok = actual == skill["sha256"]
print(f" url: {skill['url']}")
print(f" digest: declared={skill['sha256']}")
print(f" actual ={actual} {'OK' if ok else 'MISMATCH'}")
except Exception as e:
print(f" url: {skill['url']} -> {type(e).__name__}: {e}")
const res = await fetch("https://not.catastrophic.io/.well-known/agent-skills/index.json");
const index = await res.json();
console.log("Mode:", res.headers.get("X-Chaos-Skills-Mode"));
console.log("$schema:", index.$schema);
for (const skill of index.skills || []) {
try {
const r = await fetch(skill.url);
const bytes = new Uint8Array(await r.arrayBuffer());
const digest = await crypto.subtle.digest("SHA-256", bytes);
const actual = [...new Uint8Array(digest)].map(b => b.toString(16).padStart(2, "0")).join("");
console.log(`${skill.name}: ${actual === skill.sha256 ? "OK" : "MISMATCH"}`);
} catch (e) {
console.log(`${skill.name}: fetch failed - ${e.message}`);
}
}
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
)
type Skill struct {
Name, URL, SHA256 string
}
type Index struct {
Schema string `json:"$schema"`
Skills []Skill `json:"skills"`
}
func main() {
resp, _ := http.Get("https://not.catastrophic.io/.well-known/agent-skills/index.json")
defer resp.Body.Close()
var idx Index
json.NewDecoder(resp.Body).Decode(&idx)
fmt.Println("$schema:", idx.Schema)
for _, s := range idx.Skills {
r, err := http.Get(s.URL)
if err != nil { fmt.Printf("%s: fetch failed - %v\n", s.Name, err); continue }
body, _ := io.ReadAll(r.Body); r.Body.Close()
sum := sha256.Sum256(body)
actual := hex.EncodeToString(sum[:])
status := "OK"
if actual != s.SHA256 { status = "MISMATCH" }
fmt.Printf("%s: %s\n", s.Name, status)
}
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// sha2 = "0.10"
use sha2::{Digest, Sha256};
fn main() -> Result<(), Box> {
let index: serde_json::Value = reqwest::blocking::get("https://not.catastrophic.io/.well-known/agent-skills/index.json")?.json()?;
for skill in index["skills"].as_array().unwrap_or(&vec![]) {
let url = skill["url"].as_str().unwrap();
let declared = skill["sha256"].as_str().unwrap();
let body = reqwest::blocking::get(url)?.bytes()?;
let actual = format!("{:x}", Sha256::digest(&body));
println!("{}: {}", skill["name"], if actual == declared { "OK" } else { "MISMATCH" });
}
Ok(())
}
import com.fasterxml.jackson.databind.*;
import java.net.URI;
import java.net.http.*;
import java.security.MessageDigest;
public class AgentSkillsIndex {
public static void main(String[] args) throws Exception {
var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://not.catastrophic.io/.well-known/agent-skills/index.json")).build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
var idx = new ObjectMapper().readTree(resp.body());
for (var skill : idx.path("skills")) {
var body = client.send(
HttpRequest.newBuilder(URI.create(skill.get("url").asText())).build(),
HttpResponse.BodyHandlers.ofByteArray()
).body();
var actual = bytesToHex(MessageDigest.getInstance("SHA-256").digest(body));
System.out.println(skill.get("name").asText() + ": " +
(actual.equals(skill.get("sha256").asText()) ? "OK" : "MISMATCH"));
}
}
static String bytesToHex(byte[] b) {
var sb = new StringBuilder();
for (byte x : b) sb.append(String.format("%02x", x));
return sb.toString();
}
}
using System.Security.Cryptography;
using System.Text.Json;
using var client = new HttpClient();
var raw = await client.GetStringAsync("https://not.catastrophic.io/.well-known/agent-skills/index.json");
using var idx = JsonDocument.Parse(raw);
foreach (var skill in idx.RootElement.GetProperty("skills").EnumerateArray()) {
var url = skill.GetProperty("url").GetString();
var declared = skill.GetProperty("sha256").GetString();
var body = await client.GetByteArrayAsync(url);
var actual = Convert.ToHexString(SHA256.HashData(body)).ToLowerInvariant();
Console.WriteLine($"{skill.GetProperty("name")}: {(actual == declared ? "OK" : "MISMATCH")}");
}
require "net/http"
require "json"
require "digest"
index = JSON.parse(Net::HTTP.get(URI("https://not.catastrophic.io/.well-known/agent-skills/index.json")))
puts "$schema: #{index['$schema']}"
(index["skills"] || []).each do |skill|
body = Net::HTTP.get(URI(skill["url"])) rescue nil
if body
actual = Digest::SHA256.hexdigest(body)
puts "#{skill['name']}: #{actual == skill['sha256'] ? 'OK' : 'MISMATCH'}"
else
puts "#{skill['name']}: fetch failed"
end
end
$idx = (Invoke-RestMethod -Uri 'https://not.catastrophic.io/.well-known/agent-skills/index.json')
$idx.'$schema'
foreach ($skill in $idx.skills) {
try {
$body = (Invoke-WebRequest -Uri $skill.url).Content
$bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes)
$actual = [BitConverter]::ToString($hash).Replace('-','').ToLower()
"$($skill.name): " + ($(if ($actual -eq $skill.sha256) { 'OK' } else { 'MISMATCH' }))
} catch {
"$($skill.name): fetch failed - $($_.Exception.Message)"
}
}
headers
body