online / endpoints 59 / categories 14 / rate 60/min/ip /

GET /.well-known/agent-skills/index.json

GET /.well-known/agent-skills/index.json

Parametric chaos for the Agent Skills Discovery v0.2.0 index — the well-known document AI agents check to learn what skills a host advertises. Default mode is an index whose declared sha256 digest doesn't match the skill body it references; other modes cover missing schema reference, 404 skill URLs, and stale schema versions.

mode bad-digest (default; skills[0].sha256 doesn't match the body returned by skills[0].url — agents that verify before invoking see a checksum mismatch), missing-schema (top-level $schema absent; strict consumers refuse, lenient ones apply wrong validation rules), skill-404 (skills[0].url points at a path that returns 404; agents that fetch declared skills hit a dead link), stale-version ($schema references v0.1.0 but body uses v0.2.0-only fields; pinned validators reject, shape-inferring validators accept).

control Compare against the well-formed counterpart: not.catastrophic.io/.well-known/agent-skills/index.json side-by-side

build a request:

expect: 200 OK with Content-Type: application/json. X-Chaos-Skills-Mode reflects the selected mode; X-Chaos-Skills-Note explains the flaw.

bash
curl -i 'https://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest'
# 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://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest").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://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest");
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://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest")
    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://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest")?.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://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest")).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://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest");
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://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest")))
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://chaos.catastrophic.io/.well-known/agent-skills/index.json?mode=bad-digest')
$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)"
    }
}

Spec status (2026-05-17): Agent Skills Discovery is at v0.2.0 — less volatile than MCP but still pre-1.0. Modes target the stable core shape ($schema, skills[].{name, type, description, url, sha256}).