Caido Mode Skill
SkillSearchLets your agent search recorded HTTP traffic and run hands-on web security tests using saved authentication.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Caido Mode Skill skill
About this skill
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.
What this skill tells your AI
The instructions your AI receives, as published by caido/skills in skills/caido-mode/SKILL.md and read by ahel’s review.
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.
- cookies), then probe with
- 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 …, orsend-raw … --name …for a crafted one). - Cache auth in files, don't re-paste it. Use
export-curl --configonce 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-Kconfig is for your internal testing only; never hand them acurl -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 opaque403/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-Typeis 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 exactContent-Typethe endpoint expects (e.g. Googlebatchexecuteneedsapplication/x-www-form-urlencoded;charset=UTF-8) — a wrong/missing one is a common cause of400/403. curl setsContent-Lengthitself; don't add it.
- ⚠ Because
- Static cookies, no jar. It does not use
cookie-jarby default, so curl never writes a response's rotatedSet-Cookieback 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-jaropts 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>/holdsauth.cfg, body files, notes. $BASE: setBASE=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> --configto 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 — thencurl -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(theproxy/activeUrlfields) — don't parsesecrets.jsondirectly. 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)
searchis NEWEST FIRST by default (descending by request id).--limit Ntherefore 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--limitand 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. recentis always newest-first but takes no filter; usesearch --limit Nfor newest-matching-a-filter.--compact→ one terse line per request (id status METHOD host/path).- Prefer
search/recent --compactfor browsing;get/export-curlonce 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):
npx tsx caido-client.ts get-session "IDOR victim 999" --compact # session + its active entry
npx tsx caido-client.ts replay-entries "IDOR victim 999" --limit 20 # request/response history in the tab
npx tsx caido-client.ts replay-entries "IDOR victim 999" --raw --compact # include raw bytes
session-entries is an alias for replay-entries. Use these to read what's in a tab; use
edit-session (above) to send a modified request into it.
Collections — use them heavily
Collections organize sessions for handoff. Before creating a session, list existing collections and decide where it belongs. Names are mandatory and collections are never auto-created.
npx tsx caido-client.ts collections # query first
npx tsx caido-client.ts create-collection "Swagger - petstore.yaml"
npx tsx caido-client.ts rename-collection "old name" "new name"
| Situation | Collection decision |
|---|---|
| One request reproduced for the user | Default collection — don't create one. Name the session and tell the user the name. |
| A replay tab per endpoint in a JS file | New collection JS File Endpoints. |
| A replay tab per endpoint in a Swagger spec | New collection Swagger - <filename>. |
| A multi-request chain for a vuln | New collection Vuln chain - <description>, steps named 1. …, 2. …. |
All endpoints under /api/v2 | New collection /api/v2/*. |
Pass collections by name; the CLI resolves it (and tells you to create it first if missing):
npx tsx caido-client.ts create-session 8431 --name "1. login" --collection "Vuln chain - IDOR to ATO"
When you report back, name the collection and sessions — never IDs.
Match & Replace — auto-rewrite traffic
Match & Replace (Caido calls these "Tamper" rules internally) rewrites requests/responses
automatically as they pass through Caido. The killer use: inject auth at the proxy so your
curl commands don't carry it — add a rule that sets Authorization on every proxied request, then
curl -x <proxy> -k "$BASE/path" is authenticated with no -K/headers at all.
A rule is one section (which part) × one operation × a matcher × a replacer, with optional condition (HTTPQL scope) and sources:
| Piece | Choices |
|---|---|
| section | req: req-method req-path req-query req-body req-first-line req-header req-all req-sni · resp: resp-body resp-status resp-first-line resp-header resp-all · ws: ws-up ws-down |
| operation | raw (match within the section) · update/add/remove (header & query only, by name) · method/status only update |
| matcher | --match-value <str> · --match-regex <re> · --match-full (whole section) · --match-name <n> (header/query update/add/remove) |
| replacer | --replace <term> (literal; "" allowed) · --workflow <id> (run a workflow) |
| condition | --condition '<httpql>' — only apply when the request matches (e.g. one host) |
| sources | --sources INTERCEPT,REPLAY,… — which traffic it applies to |
Four gotchas, all defaulted for you:
- New rules are created DISABLED. Enable with
toggle-mr-rule <id> --on. - Default collection is Caido's "Default Collection" (override with
--collection <name|id>). - Default sources is
INTERCEPT(proxy traffic), matching Caido. Add--sourcesto broaden. - JS targets — pick matcher based on what you're matching against.
--match-valueis fine for stable literals (string constants, JSON keys, fixed API paths). Use--match-regexwhen matching near minified identifiers: symbol names rotate on every bundle deploy (e.g._.ex→_.Ww), so a literal rule silently stops matching with no error. Anchor the regex to structurally stable neighbours — surrounding string literals, known function names, fixed JSON keys — rather than the minified identifier itself.
Preview before committing: test-mr-rule applies a rule to a raw request without creating
anything — use it to confirm a rule does what you expect.
# Preview: would this add the header correctly?
npx tsx caido-client.ts test-mr-rule --section req-header --operation add \
--match-name X-Test --replace hi --raw 'GET / HTTP/1.1\r\nHost: t.com\r\n\r\n'
# Inject auth on all proxied requests to one host (then enable it)
ID=$(npx tsx caido-client.ts create-mr-rule --section req-header --operation add \
--match-name Authorization --replace "Bearer eyJ…" \
--condition 'req.host.eq:"target.com"' --name "auth inject" | jq -r '.created.id')
npx tsx caido-client.ts toggle-mr-rule "$ID" --on
# Other patterns
npx tsx caido-client.ts create-mr-rule --section req-header --operation remove \
--match-name If-None-Match --sources REPLAY --name "drop INM" # strip a header
npx tsx caido-client.ts create-mr-rule --section req-body --match-regex '"admin":false' \
--replace '"admin":true' --name "force admin" # body regex
npx tsx caido-client.ts create-mr-rule --section resp-status --replace 403 --name "fake 403" # response
npx tsx caido-client.ts mr-rules # list rules (+ enabled state)
npx tsx caido-client.ts toggle-mr-rule <id> --off
npx tsx caido-client.ts delete-mr-rule <id>
Manage collections with mr-collections, create-mr-collection, rename-mr-collection,
delete-mr-collection; move-mr-rule <id> <collection>; update-mr-rule <id> … re-specs a rule
(same flags as create); rename-mr-rule <id> <name>.
Output control (works with get, get-response, replay, edit, send-raw, edit-session)
| Flag | Description |
|---|---|
--max-body <n> | Max response body lines (default 200, 0 = unlimited) |
--max-body-chars <n> | Max body chars (default 5000, 0 = unlimited) |
--no-request | Omit the request raw from output |
--headers-only | Headers only, no body |
--compact | Shorthand: --no-request --max-body 50 --max-body-chars 5000 |
HTTPQL Reference
Caido's query language for searching HTTP history.
CRITICAL: String values MUST be quoted; integers are NOT.
CRITICAL: HTTPQL has NO NOT operator. Use the negated operator variant instead:
ncont(not contains),nlike,nregex,ne(not equals)- Wrong:
NOT req.path.cont:"/admin"— Right:req.path.ncont:"/admin"
Namespaces and Fields
| Namespace | Field | Type | Description |
|---|---|---|---|
req | ext | string | File extension (includes .) |
req | host | string | Hostname |
req | method | string | HTTP method (uppercase) |
req | path | string | URL path |
req | query | string | Query string |
req | raw | string | Full raw request |
req | port | int | Port number |
req | len | int | Request body length |
req | created_at | date | Creation timestamp |
req | tls | bool | Is HTTPS |
resp | raw | string | Full raw response |
resp | code | int | Status code |
resp | len | int | Response body length |
resp | roundtrip | int | Roundtrip time (ms) |
row | id | int | Request ID |
source | - | special | "intercept", "replay", "automate", "workflow" |
preset | - | special | Filter preset reference |
Operators
- String:
eq,ne,cont,ncont,like,nlike,regex,nregex - Integer:
eq,ne,gt,gte,lt,lte - Boolean:
eq,ne - Logical:
AND,OR, parentheses for grouping
Examples
req.method.eq:"POST" AND resp.code.eq:200 # POSTs with 200s
req.host.cont:"api" OR req.path.cont:"/api/" # API traffic
"password" OR "secret" OR "api_key" # bare string searches req AND resp raw
resp.code.gte:400 AND resp.code.lt:500 # 4xx
resp.len.gt:100000 # large responses (data exposure)
req.path.regex:"/(login|auth|signin|oauth)/" # auth endpoints
source:"replay" OR source:"intercept" # tool-generated vs proxied traffic
req.created_at.gt:"2024-01-01T00:00:00Z" # date filter
req.path.ncont:"/static" # exclude (no NOT keyword)
preset:"My Filter" # saved filter preset
Other capabilities (reference)
Findings — surface in Caido's Findings tab
npx tsx caido-client.ts findings --limit 50
npx tsx caido-client.ts create-finding 8431 --title "IDOR on /api/user/:id" \
--description "Reads other users' profiles by changing id" --reporter "rez0" --dedupe-key "idor-user"
npx tsx caido-client.ts update-finding <id> --title "…" --description "…"
Scopes / Filter presets / Environments
npx tsx caido-client.ts create-scope "Target" --allow "*.target.com" --deny "*.cdn.target.com"
npx tsx caido-client.ts create-filter "API 4xx" --query 'req.path.cont:"/api/" AND resp.code.gte:400' --alias "api4xx"
npx tsx caido-client.ts search 'preset:"API 4xx"' --compact
npx tsx caido-client.ts create-env "IDOR-Test"; npx tsx caido-client.ts env-set <env-id> victim_id "user_999"
Fuzzing / intercept / projects / tasks / info
npx tsx caido-client.ts create-automate-session 8431 # configure payloads in UI, then: fuzz <session-id>
npx tsx caido-client.ts intercept-status | intercept-enable | intercept-disable
npx tsx caido-client.ts projects ; npx tsx caido-client.ts viewer ; npx tsx caido-client.ts plugins
Full command reference
Every command (run npx tsx caido-client.ts <command>). Sessions/collections accept a name or
id; output is JSON unless noted. Run --help for full flag lists.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 278
- Forks
- 28
- Last commit
- Aug 2026
ahel review
K6low
bundled executables the agent is told to runK1binfo
installs-packages (in README.md)
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Item type
- skill
- Key
skills-caido-mode- Source
- github.com/caido/skills