{"id":51666001,"url":"https://github.com/geeknik/ctfuzz","last_synced_at":"2026-07-14T20:30:54.666Z","repository":{"id":353664831,"uuid":"1220275697","full_name":"geeknik/ctfuzz","owner":"geeknik","description":"Content-type differential fuzzer written in Go.","archived":false,"fork":false,"pushed_at":"2026-04-24T21:52:50.000Z","size":79,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-03T04:33:55.002Z","etag":null,"topics":["bug-bounty","go","research","security"],"latest_commit_sha":null,"homepage":"https://deepforkcyber.com/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/geeknik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T18:21:25.000Z","updated_at":"2026-04-26T17:27:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/geeknik/ctfuzz","commit_stats":null,"previous_names":["geeknik/ctfuzz"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/geeknik/ctfuzz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geeknik%2Fctfuzz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geeknik%2Fctfuzz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geeknik%2Fctfuzz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geeknik%2Fctfuzz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/geeknik","download_url":"https://codeload.github.com/geeknik/ctfuzz/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geeknik%2Fctfuzz/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35478633,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-14T02:00:06.603Z","response_time":114,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bug-bounty","go","research","security"],"created_at":"2026-07-14T20:30:53.256Z","updated_at":"2026-07-14T20:30:54.661Z","avatar_url":"https://github.com/geeknik.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ctfuzz\n\nContent-type differential fuzzer for authorized web application testing and\nbug bounty work. Given a list of known URLs, ctfuzz sends the same logical\npayload through many body encodings and `Content-Type` headers, then surfaces\nthe routes that behave differently across variants.\n\nThe tool is narrow by design.\n\n## What it is\n\n- A tool that highlights **parser disagreement** between what the edge sees\n  and what the backend accepts.\n- A tool that surfaces **content-type-sensitive authorization** — a route\n  that rejects JSON but accepts form data is a bug shape ctfuzz was built\n  to find.\n- A tool that records structured JSONL and exits with stable codes so an\n  agent pipeline can act on findings without parsing stdout.\n\n## What it is not\n\n- A general-purpose HTTP fuzzer.\n- A URL or parameter discovery engine — bring your own URL list.\n- A payload generator for XXE/SSRF/SQLi. ctfuzz sends the *same* logical\n  payload every time by design; the value is in the delta across encodings.\n- A WAF bypass framework.\n- A load tester.\n\n## Install\n\n```sh\ngit clone git@github.com:geeknik/ctfuzz.git\ncd ctfuzz\ngo build -o ctfuzz ./cmd/ctfuzz\n```\n\nRequires Go 1.22+. No runtime dependencies.\n\n## Quickstart\n\n```sh\ncat \u003e urls.txt \u003c\u003c'EOF'\nhttps://in-scope.example.com/api/user\nhttps://in-scope.example.com/api/search\nEOF\n\ncat \u003e payload.json \u003c\u003c'EOF'\n{ \"id\": \"1\", \"role\": \"user\", \"admin\": false }\nEOF\n\ncat \u003e scope.txt \u003c\u003c'EOF'\nin-scope.example.com\n*.in-scope.example.com\nEOF\n\n# Plan the run — no network traffic.\n./ctfuzz --urls urls.txt --payload payload.json \\\n         --scope-file scope.txt --dry-run\n\n# Run it through Burp, with a per-host rate cap.\n./ctfuzz --urls urls.txt --payload payload.json \\\n         --scope-file scope.txt \\\n         --proxy http://127.0.0.1:8080 \\\n         --rps 5 \\\n         --out results.jsonl\n\n# Summarize findings.\n./ctfuzz report --jsonl results.jsonl \u003e findings.md\n\n# Reproduce one interesting request as a curl command.\n./ctfuzz replay --jsonl results.jsonl --payload payload.json --seq 7\n```\n\n## Subcommands\n\n| Subcommand | Purpose |\n| ---------- | ------- |\n| `scan` (default) | Run the content-type differential matrix |\n| `replay` | Print a reproducible `curl` for one request from a results file |\n| `report` | Summarize `results.jsonl` as markdown or JSON |\n| `help` | Print top-level help |\n\nBare `ctfuzz [flags]` is equivalent to `ctfuzz scan [flags]`.\n\n## Safety defaults\n\nctfuzz assumes it is being pointed at live targets that an operator cares\nabout. The defaults reflect that posture:\n\n- No redirect following, no retries, bounded concurrency (8), 10s timeout.\n- `--rps 0` (off) — set this explicitly for real targets.\n- `--scope-file` enforces a host allowlist. Any out-of-scope URL aborts\n  the run with exit code 3 unless `--allow-scope-drops` is set.\n- `--max-requests-per-host N` hard-caps blast radius per host.\n- `--dry-run` prints the request matrix and exits without any network\n  traffic, including URL counts per host and the total request volume.\n- Canary is a 16-hex random token with no fixed prefix (no WAF-\n  fingerprintable tell). `--canary-prefix` adds a marker if you want one\n  for log correlation.\n- POST/PUT/PATCH/DELETE emit a prominent notice at run start with the\n  payload key count, so it's obvious when a run will mutate target state.\n- JSONL output is written atomically at mode 0600.\n\n## Coverage modes\n\n### 35 content-type variants by default\n\nCovers the JSON family (`application/json`, `vnd.api+json`, `hal+json`,\n`ld+json`, `json-patch+json`, `merge-patch+json`, `problem+json`,\n`scim+json`, `activity+json`, `manifest+json`, `reports+json`,\n`csp-report`, `text/json`, `x-json`, `x-ndjson`), the XML family\n(`application/xml`, `text/xml`, `soap+xml`, `atom+xml`, `rss+xml`, plus\ncharset variants), form + multipart, the YAML family, `text/plain`,\n`application/javascript`, `text/javascript`, and `application/octet-stream`.\n\n```sh\nctfuzz --types all          # default — all 35\nctfuzz --types core         # just application/json, application/xml, form\nctfuzz --types json-family  # JSON and JSON-ish only\n```\n\n### `--methods`\n\nRun the full variant matrix across multiple HTTP methods for compound\ndifferential analysis.\n\n```sh\nctfuzz --methods POST,PUT,PATCH,DELETE,GET ...\n```\n\nThe analyzer groups by `(URL, method)`, so each method has its own\nsummary line and its own clustering analysis.\n\n### `--mismatch`\n\nAdditive opt-in. Appends 9 body/Content-Type disagreement scenarios per\nURL per method so you can probe parser-disagreement bugs:\n\n```\njson-as-xml   json-as-form    json-as-plain   json-no-header\nxml-as-json   xml-as-form\nform-as-json  form-as-xml     form-no-header\n```\n\n`*-no-header` variants actually omit the `Content-Type` header end-to-end,\nnot replace it with an empty string.\n\n## Differential detection\n\nEach URL's variants are compared on:\n\n- Status code distribution\n- Response body SHA-256\n- Response header fingerprint\n- Response size and duration\n- Redirect location and host\n- Canary reflection\n- Parser/framework error keywords\n\nURLs are scored 0–100. Summaries are promoted to `interesting` when at\nleast one **reportable** signal fires. Fingerprint-only noise (varying\n`Date`, per-request request-IDs) is scored but suppressed from the\n`interesting` flag — it still lands in the JSONL so agents can inspect\nthe full signal set.\n\n### Structural header clustering\n\nctfuzz's most distinctive signal. When response headers cluster cleanly\ninto a small number of groups (e.g., 24 content types see one header\nset, 11 see another) and each group has ≥ 2 members, that's\ndeterministic behavior, not jitter. The structural signal:\n\n- Scores +25 (reportable).\n- Populates `summary.header_groups` in the JSONL with each group's\n  content-type list and the subset of whitelisted response headers whose\n  values actually differ across groups.\n- Prints a compact cluster line on the console and a full breakdown in\n  verbose mode.\n\nThe triage-header whitelist covers `Server`, `X-Powered-By`, `Vary`,\n`Content-Security-Policy`, `X-Frame-Options`, `Strict-Transport-Security`,\nCORS family, `Set-Cookie` names only (no values), `Retry-After`,\n`X-RateLimit-*`, `Reporting-Endpoints`, `Server-Timing`, and ~30 others.\nCookie values and per-request identifiers are never captured.\n\n## Output schema\n\nJSONL, one line per record. The first line is always a manifest.\n\n```json\n{\"kind\":\"manifest\",\"schema\":1,\"created\":\"2026-04-24T16:00:00Z\",\"canary\":\"5f3a8c...\",\"methods\":[\"POST\"],\"types\":[\"application/json\", ...],\"mismatch\":false}\n{\"seq\":0,\"url\":\"https://...\",\"method\":\"POST\",\"content_type\":\"application/json\",\"body_encoding\":\"json\",\"variant\":\"application/json\",\"status\":200,\"duration_ms\":118,\"response_len\":421,\"body_sha256\":\"...\",\"header_sha256\":\"...\",\"redirect_location\":\"\",\"canary_reflected\":false,\"error_keyword\":false,\"body_truncated\":false,\"interesting\":false,\"error\":\"\",\"response_headers\":{\"Server\":\"...\",\"Vary\":\"...\"}}\n{\"url\":\"https://...\",\"method\":\"POST\",\"kind\":\"summary\",\"interesting\":true,\"score\":65,\"reason\":\"success status differs from authorization failure; response headers cluster by content-type family\",\"statuses\":{\"application/json\":200,\"application/xml\":403,\"application/x-www-form-urlencoded\":403},\"header_groups\":[{\"hash\":\"...\",\"content_types\":[\"application/json\"],\"headers\":{\"X-Frame-Options\":\"DENY\"}}, ...]}\n```\n\nPass `--include-body` to add the (truncated to `--max-body-read`) response\nbody to each request record.\n\n## Exit codes\n\n| Code | Meaning |\n| ---- | ------- |\n| 0 | Clean run, no interesting findings |\n| 1 | Unexpected error (config, IO, network infrastructure) |\n| 2 | CLI usage error (Go `flag` package default) |\n| 3 | Scope violation — URLs outside `--scope-file` without `--allow-scope-drops` |\n| 4 | Clean run, interesting findings present |\n\nAgent pipelines should treat 3 as \"stop, escalate to human\" and 4 as\n\"trigger triage.\"\n\n## Project layout\n\n```\ncmd/ctfuzz/           main, replay, report subcommands\ninternal/analyze/     signal scoring, structural clustering\ninternal/config/      flag parsing + validation\ninternal/fingerprint/ hashing + triage-header whitelist\ninternal/httpclient/  transport, retries, per-host rate limiter\ninternal/input/       URL and header loaders\ninternal/output/      atomic JSONL writer\ninternal/payload/     payload loader (depth caps, control-char guards)\ninternal/render/      body renderers + mismatch scenarios\ninternal/result/      record types (Request, Summary, Manifest)\ninternal/scope/       host allowlist matcher\n```\n\nSee [DESIGN.md](./DESIGN.md) for the full architecture and threat model.\n\n## Development\n\n```sh\ngo test ./...\ngo test ./... -race\ngo vet ./...\ngo build -o ctfuzz ./cmd/ctfuzz\n```\n\nThe `-race` suite should stay clean on every commit. All parser code is\ncovered by adversarial tests (`*_adversarial_test.go`).\n\n## License\n\nSee [LICENSE](./LICENSE).\n\n## Author\n\n[geeknik](https://github.com/geeknik)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeeknik%2Fctfuzz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeeknik%2Fctfuzz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeeknik%2Fctfuzz/lists"}