OFFAT OWASP

Gray-box design (offat-graybox)#

Gray-box fuses the white-box view (source, proven data flows) with a black-box view (the HTTP endpoint attack surface, mapped from source) and analyzes them together with AI. It sends no live traffic - reachability is established statically from the code graph.

recon -> map endpoints -> code graph + patterns -> reachability -> analyze (AI) -> report

The idea#

SAST alone reports "a dangerous sink exists at file:line". Gray-box answers the question that decides severity: can an exposed endpoint actually reach it? It maps every route from source to its handler, links handlers to sinks through the call graph, and has the AI judge each finding with that reachability context. A sink reachable from an unauthenticated route is escalated; a dead-code sink is capped at inconclusive.

Run#

pip install ./triager './whitebox[graph]' ./graybox
offat-graybox /path/to/repo                 # AI when a provider is available
offat-graybox ./repo --no-ai                # offline heuristic only
offat-graybox ./repo --provider claude-code # drive a local agent CLI
offat-graybox ./repo --fail-on high         # CI gate on reachable high+ findings
Flag Description
--classes comma-separated vuln classes to hunt (default: all)
-o, --out output directory (default offat-report/graybox)
--no-ai heuristic triage only (no tokens)
--provider auto (default), anthropic, claude-code, codex, heuristic
--no-graph skip the code graph (falls back to graft / same-file reachability)
--semgrep also run semgrep (opt-in)
--no-graft map endpoints natively instead of via graft
--no-cache do not read/write the AI-verdict cache
--fail-on exit non-zero if an actionable finding is at/above this severity

Endpoint mapping#

A native parser maps every route deterministically. Supported route forms: Flask/FastAPI decorators, Django path(), Express app/router.verb(), Spring @*Mapping, Go routers (GET/POST/Handle/HandleFunc), Rails routes.rb, and OpenAPI/Swagger specs shipped in the repo (YAML or JSON - covers spec-driven frameworks like connexion, where the operationId becomes the handler).

Reachability comes from the code graph: a proven flow carries the routes that reach it, and every pattern hit is linked through its enclosing function's callers up to the route handlers. Dead code stays unreachable instead of being promoted because it shares a file with a route. With --no-graph the older heuristics (handler-name match, graft callers, same-file) are used.

Token discipline#

AI analysis is the only token-spending stage, and it is kept cheap by design:

Lever Effect
Reachable-only unreachable sinks are triaged offline (0 tokens)
Batched + tiered a cheap model screens in batches; a strong model verifies only the confirmed/likely subset
Cached verdicts keyed on a content hash; re-runs pay only for new/changed findings

Tune with OFFAT_SCREEN_MODEL, OFFAT_VERIFY_MODEL, OFFAT_BATCH_SIZE, or --no-cache. Every AI path degrades to a deterministic heuristic, so a run never breaks and never requires a key.

Output#

report.json, findings.jsonl, endpoints.json, results.sarif, report.md, report.html, report.junit.xml. Each finding carries reachable, reachable_from (the endpoints that reach the sink), source_mode: graybox, and a threat block mapping it to the OWASP API Security Top 10 (2023), OWASP Top 10 (2021) and a primary CWE. The report includes a Threat mapping summary counting findings per OWASP API Top 10 category.

Lineage#

The multi-stage methodology mirrors the public security-harness workflow (recon -> hunt -> chain -> verify -> report).

Edit this page on GitHub