{
    "name": "caido-mode",
    "version": "1.0.0",
    "description": "Full Caido SDK integration for Claude Code. Search HTTP history with HTTPQL, test with curl proxied through Caido (caching auth in reusable static curl config files), add match & replace rules, and organize handoffs into named replay sessions and collections - all via the official @caido/sdk-client. PAT auth recommended.",
    "system_prompt": "name caido-mode description Full Caido SDK integration for Claude Code. Search HTTP history with HTTPQL, test with curl proxied through Caido (caching auth in reusable static curl config files), add match & replace rules, and organize handoffs into named replay sessions and collections - all via the official @caido/sdk-client. PAT auth recommended. tags [\"worker\"] Caido Mode Skill A CLI over Caido's API (built on the official @caido/sdk-client ) for HTTP-history-driven testing. The tool lives at ~/.claude/skills/caido-mode/caido-client.ts ; every command is npx tsx caido-client.ts <command> and outputs JSON unless noted. How to operate (read this first) There are two distinct modes : Testing → use curl , always proxied through Caido. Find a real authenticated request in history, cache its auth into a reusable curl config (a faithful static snapshot of its headers cookies), then probe with curl -K auth.cfg \"$BASE/path\" . All traffic must go through Caido (the config carries the proxy), so every request lands in HTTP history. Handoff → use replay sessions + collections. Only when handing a request (or a set) to the user do you materialize it as a named replay session inside a named collection. Hard rules: Everything goes through Caido — except high-volume bruteforce/fuzzing. Never curl a single target request directly; always via the Caido proxy (the generated config does this; otherwise add -x <proxy> ). The one exception: don't proxy bruteforce/fuzzing tools ( ffuf , etc.) or any batch of 100+ requests at once through Caido — it bloats HTTP history. Run those direct (no -x ), then bring any interesting hit back into Caido (re-send it through the proxy / promote to Replay) to investigate and hand off. Test with curl . Don't spin up replay sessions for probing — that's handoff only. To show the operator a request, send it to Replay. Whenever you want the operator to see a specific request, create a named replay session for it (in a named collection if there's more than one) — that's how they inspect and re-run it in Caido. A request you tested via curl only becomes something the operator can work with once you promote it into Replay ( create-session <id> --name … , or send-raw … --name … for a crafted one). Cache auth in files, don't re-paste it. Use export-curl --config once per target; then reference the config. Don't dump cookies/JWTs into every command (or repeatedly into context). If you hand the operator a runnable command, make it a FULL self-contained curl (all headers inline, via export-curl ) — for a PoC or something they'll run outside Caido. The -K config is for your internal testing only; never hand them a curl -K /tmp/… line. Replay session names are mandatory , and editing a session forces explicit name intent. Use collections for multi-request handoffs ; refer to sessions/collections by name, not ID . The primary workflow (do this by default) # 1. Find a base request that already has the auth/cookies you need. npx tsx caido-client.ts search 'req.host.cont:\"target.com\" AND req.path.cont:\"/api/user\"' --compact # → 8431 200 GET target.com/api/user/me # 2. ONCE per target: cache its auth into a reusable curl config. npx tsx caido-client.ts export-curl 8431 --config # → writes /tmp/caido/target.com/auth.cfg — a FAITHFUL STATIC snapshot: # proxy + insecure + compressed + ALL the request's auth/identity headers # (cookies, Authorization, Origin/Referer, X-*, Sec-*, app-specific headers) # and prints BASE + the captured header list # 3. Test with curl. -K carries the proxy + auth, so it goes through Caido into history. BASE=https://target.com curl -K /tmp/caido/target.com/auth.cfg \" $BASE /api/user/999\" # IDOR curl -K /tmp/caido/target.com/auth.cfg -X POST \" $BASE /api/profile\" \\ -H 'Content-Type: application/json' --data-binary @/tmp/caido/target.com/body.json Iterate step 3 freely — it's cheap, it's all in Caido, and the big auth blob stays in the file. Confirm a probe landed in Caido with search 'req.host.cont:\"target.com\"' --compact . Send the path exactly as written When testing path traversal / path-normalization ( ../ , /.. , /./ , encoded variants), pass curl --path-as-is — otherwise curl collapses ../ and /./ client-side before sending, so the server never sees the payload and the test silently passes. Keep the path verbatim: curl --path-as-is -K /tmp/caido/target.com/auth.cfg \" $BASE /api/../../../etc/passwd\" (Likewise add -g / --globoff if the URL contains [ ] { } you don't want curl to interpret.) The config is a faithful STATIC snapshot (important) export-curl --config captures every auth/identity header from the base request (not a curated subset) and inlines the cookies statically . Two deliberate choices, both learned the hard way: All headers, not an allowlist. Modern apps gate authorization on app-specific headers you can't predict — x-goog-ext-* , X-Browser-Validation , X-Client-Data , Origin , Referer , X-Same-Domain , Sec-* , … A narrow allowlist silently drops these and you get opaque 403 / PERMISSION_DENIED . The config now mirrors what actually authorized the request. Only truly per-request/volatile headers are dropped: Host , Content-Length , Content-Type , Connection , Accept-Encoding (curl manages these per request). ⚠ Because Content-Type is dropped, you MUST pass it yourself on every POST/PUT/PATCH: curl -K auth.cfg -X POST \"$BASE/path\" -H 'Content-Type: application/json' --data-binary @body . Use the exact Content-Type the endpoint expects (e.g. Google batchexecute needs application/x-www-form-urlencoded;charset=UTF-8 ) — a wrong/missing one is a common cause of 400 / 403 . curl sets Content-Length itself; don't add it. Static cookies, no jar. It does not use cookie-jar by default, so curl never writes a response's rotated Set-Cookie back over your captured-good cookies (servers like Google rotate on every response, including error responses — a write-back jar drifts the session into failure). Need to follow rotation? export-curl <id> --config --cookie-jar opts in. To drop a specific header: --exclude <name> (repeatable). To omit cookies entirely (e.g. when a Match & Replace rule injects auth): --exclude cookie . Other conventions Per-target scratch dir: /tmp/caido/<host>/ holds auth.cfg , body files, notes. $BASE : set BASE=https://<host> once; write requests as \"$BASE/path\" . Bodies in files: save large/complex bodies once and send with --data-binary @body.json (the correct use of --data-binary — a byte-exact body ). Add -H 'Content-Type: …' per request since the config omits it. Lazy refresh: the snapshot is static, so when a request starts returning 401/403 (token expired / cookies aged out), re-run export-curl <fresh-id> --config to re-snapshot, then retry. CSRF: the matching X-CSRF* /double-submit header is captured automatically. For tokens that rotate per action, fetch fresh: T=$(curl -sK auth.cfg \"$BASE/csrf\" | jq -r .token) . Proxy-injected auth (alternative): instead of a config, a Match & Replace rule can inject Authorization /cookies on all proxied traffic — then curl -x <proxy> -k \"$BASE/path\" needs no headers. See Match & Replace . Giving commands to the user To surface a request inside Caido for the operator, send it to Replay (see \"Replay sessions\" below) — that's the default. This section is for the other case: handing them a runnable command (a PoC, or something to run outside Caido). Then always produce a full, self-contained curl — every header inline, no -K : npx tsx caido-client.ts export-curl 8431 # full curl, all headers inline (portable PoC) Drop -x / -k for a portable PoC the user can run anywhere; keep them only if the user is meant to run it through their own Caido. Never hand the user a curl -K /tmp/... line — that file is yours. The proxy All curl testing must go through Caido's proxy. Its address defaults to the Caido URL (proxy and API share an address). Discover/confirm it any time: npx tsx caido-client.ts auth-status # prints \"proxy\": \"http://localhost:8080\" export-curl --config bakes the proxy into the config ( proxy = \"…\" ). For an ad-hoc curl, add -x <proxy> -k yourself. Override the proxy only if its listener differs from the API URL — setup --proxy <addr> or export CAIDO_PROXY=<addr> . Get the proxy from auth-status (the proxy / activeUrl fields) — don't parse secrets.json directly. Auth is URL-keyed now: the address lives under .caido.default / .caido.instances , not .caido.url . Authentication setup # One-time: create a PAT in Caido (Dashboard → Developer → Personal Access Tokens), then: npx tsx caido-client.ts setup <your-pat> npx tsx caido-client.ts setup <pat> http://192.168.1.100:8080 # non-default instance npx tsx caido-client.ts setup <pat> http://localhost:8080 --proxy http://localhost:8080 # Or env vars export CAIDO_PAT=caido_xxxxx export CAIDO_URL=http://localhost:8080 export CAIDO_PROXY=http://localhost:8080 # only if the proxy differs from the URL npx tsx caido-client.ts auth-status # check (also prints the proxy) npx tsx caido-client.ts health # verify instance is up setup validates the PAT via the SDK's device-code flow (auto-approved by the PAT), then caches the PAT + access token (+ proxy) to ~/.claude/config/secrets.json . Subsequent runs use the cached token; a valid cached token works even without the PAT. Multiple Caido instances Credentials are keyed by instance URL — two instances on one machine never clobber each other. setup <pat> <url> stores that instance under its URL (and makes it the active default); setting up a second URL adds a second entry rather than overwriting the first. npx tsx caido-client.ts setup <pat-a> http://localhost:8080 npx tsx caido-client.ts setup <pat-b> http://localhost:8081 # added, not overwritten npx tsx caido-client.ts auth-status # lists configuredInstances + activeUrl The active instance is CAIDO_URL env → stored default → http://localhost:8080 . Select per shell/agent with CAIDO_URL (concurrency-safe — no shared \"current instance\" to race on), e.g. CAIDO_URL=http://localhost:8081 npx tsx caido-client.ts recent . CAIDO_PAT / CAIDO_PROXY env override the active instance's stored values. Searching HTTP history (HTTPQL) npx tsx caido-client.ts search 'req.method.eq:\"POST\" AND resp.code.eq:200' --compact npx tsx caido-client.ts search 'req.host.cont:\"api\"' -- limit 50 npx tsx caido-client.ts search 'req.host.cont:\"api\"' --asc -- limit 50 # oldest first (rarely wanted) npx tsx caido-client.ts recent --compact # newest requests, one line each npx tsx caido-client.ts get 8431 --compact # full details (JSON) when you need them npx tsx caido-client.ts get-response 8431 --compact npx tsx caido-client.ts raw 8431 --out /tmp/caido/target.com/body.json # dump bytes (e.g. a body) search is NEWEST FIRST by default (descending by request id). --limit N therefore returns the newest N matches. Pass --asc (alias --oldest ) only when you actually want oldest first. To get \"the most recent matching X\", just run search '<filter>' --limit N — do NOT pull a large --limit and re-sort client-side (e.g. jq 'sort_by(.createdAt) | reverse' ). That sorts only the truncated window you fetched, so any request newer than the Nth result is silently invisible — you'll mistake stale traffic for the latest. Let Caido do the ordering. recent is always newest-first but takes no filter ; use search --limit N for newest-matching-a-filter. --compact → one terse line per request ( id status METHOD host/path ). Prefer search / recent --compact for browsing; get / export-curl once you've picked one. See the HTTPQL Reference below for the full query language. Replay sessions — for handoff only Use these when giving a request to the user . Normal testing uses curl (above), not sessions. Sessions created from a raw request have their header line endings normalized to CRLF automatically — a handoff session is never built with bare-LF ( \\n ) endings. # Create a NAMED session from a history request (name is REQUIRED). npx tsx caido-client.ts create-session 8431 --name \"IDOR /api/user/:id\" npx tsx caido-client.ts sessions # list (alias: replay-sessions) npx tsx caido-client.ts rename-session \"IDOR /api/user/:id\" \"IDOR - confirmed\" npx tsx caido-client.ts move-session \"IDOR - confirmed\" \"Vuln chain - IDOR to ATO\" # Build a handoff session from a raw request file (CRLF auto-normalized): npx tsx caido-client.ts send-raw --host target.com --raw @/tmp/req.txt --name \"crafted repro\" Editing a session forces name intent If the user asks you to test inside Replay, use edit / edit-session . Because an edit changes what a session contains, declare what happens to its name — pass exactly one of --no-name-change ( --nonach ) or --new-name \"<name>\" : npx tsx caido-client.ts edit 8431 --path /api/user/999 --name \"IDOR victim 999\" # new session npx tsx caido-client.ts edit-session \"IDOR victim 999\" --body '{\"role\":\"admin\"}' --nonach --compact npx tsx caido-client.ts edit 8431 --path /api/admin --session \"IDOR victim 999\" --new-name \"priv-esc\" edit preserves cookies/auth from the original request; it supports --method , --path , --set-header , --remove-header , --body (auto Content-Length), --replace <from>:::<to> , and connection overrides ( --sni , --connect-host , …). Inspecting an existing replay tab When a replay tab is already open in Caido and you want to work from its current state, look it up by name or id (no need to re-create it):",
    "model_config": {
        "provider": "deepseek",
        "model": "deepseek-chat",
        "temperature": 0.7,
        "max_tokens": 4096,
        "top_p": 0.9
    },
    "trigger_words": [],
    "source": "DeepseekModel",
    "source_url": "https://deepseekmodel.com/skill?id=caido-skills-skills-caido-mode-skill-md"
}