Repository navigation
Conversation
shuvonsec
left a comment
There was a problem hiding this comment.
Solid, well-scoped - pure core (decide_verdict/resolve_headers/render_report) is separated and genuinely tested, secrets are env-only with a leak test, unsafe methods gated. Verified 25 tests pass. Two asks (both about re-issuing live traffic):
- (scope)
replay_oneand especiallyreplay allre-attack every stored target with no scope check, which contradicts Critical Rule #1 (READ FULL SCOPE before touching any asset). The repo already shipsscope_checker.py- please gate the send through it, or at minimum warn/confirm per target, especially forall. - (SSRF) The initial hop is unguarded:
safe_urlopenonly validates redirect hops, andload_bundletrustsbundle.json'sexchange.urlverbatim. A tampered/untrusted bundle makesreplay allhit 169.254.169.254 / localhost on the first request. Validate the initial host before calling capture. - (minor) Guard/redirect URLErrors are caught as verdict UNREACHABLE, which reads like "endpoint gone / fixed" - a bug that now 302s to an internal host gets masked. Worth distinguishing guard/redirect errors from real unreachability.
|
Great review, @shuvonsec — all three are about re-issuing live traffic responsibly, and you're right on each. Fixed in 1. Scope (Critical Rule #1). 2. SSRF on the initial hop. You nailed it — 3. Guard/redirect errors masked as UNREACHABLE. Fixed: a new +9 tests (34 total pass), covering scope in/out + |
9efc2d0 to
fb51a54
Compare
47ab19a to
a7773d8
Compare
Closes the docs/CAPABILITY-GAPS.md gap "No finding-replay/regression to re-verify past bugs on a target". Builds directly on the merged PoC bundler (awarexone#144): once /poc has captured a finding, /replay re-sends that exact request and tells you whether the bug is STILL_VULNERABLE, FIXED, CHANGED, or UNREACHABLE — turning a one-shot PoC into a monitorable, retest-ready asset. tools/replay.py — two modes: - run <bundle-dir> [--marker "signal"]: replay one bundle. - all [root]: sweep every poc-bundle/1 under root (default findings/) into a ranked regression-report.md + regression.json ("retest day"). Verdict engine (pure, exhaustively unit-tested): - Marker-based is the reliable signal (confidence: confirmed) — the string that proves the bug present -> STILL_VULNERABLE, absent -> FIXED. Markers persist as marker.txt so later sweeps reuse them. - Without a marker, a status+SHA-256 integrity check is used and honestly labelled "heuristic"; it never claims FIXED from a hash diff alone. Reuses existing infra: poc_bundler.capture (SSRF-guarded safe_http), parse_raw_request, and sha256. The manifest URL is trusted for scheme/host so an http:// finding isn't replayed over https (avoids a false UNREACHABLE). Safety: PUT/DELETE/PATCH skipped without --confirm-unsafe (replaying a mutating request could re-trigger it); secrets read from env only (like repro.sh) and never written into replay.json or the sweep report; --fail-if-vulnerable exits 3 for scheduled monitoring/CI. Console output reconfigures to UTF-8 so verdict icons don't crash a non-UTF-8 terminal. 25 tests in tests/test_replay.py (all verdict branches, header resolution, bundle I/O, offline replay via monkeypatch, CLI). /replay command + CLAUDE.md command-table and tools-list entries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…CKED verdict Resolves the change requests from @shuvonsec on the /replay PR. All three are about safely re-issuing live traffic. 1. (scope) replay now gates every send through scope_checker.py (Critical Rule awarexone#1: read full scope before touching any asset). Pass --scope '*.target.com' (repeatable) / --scope-file <file>; out-of-scope targets are OUT_OF_SCOPE and never sent. `all` REFUSES to run with no scope unless --no-scope is passed (explicitly asserting every stored target is in scope). `run` warns when no scope is given. 2. (SSRF) the initial hop is now validated. load_bundle trusts the bundle URL, and safe_urlopen only guards redirects — so a tampered bundle could make the FIRST request hit 169.254.169.254 / localhost. initial_host_blocked() checks the initial host with the exact same blocklist safe_http uses on redirects, before any request; a blocked host returns BLOCKED and nothing is sent. 3. (verdict) guard/redirect URLErrors are no longer masked as UNREACHABLE. A new BLOCKED verdict is returned for SSRF-guard / too-many-redirects errors, so a bug that now 302s to an internal host reads as a live signal to investigate, not "endpoint gone / fixed". Tests: +9 in tests/test_replay.py — scope in/out + `all` refusal, the initial-host guard on literals (169.254/localhost/127.0.0.1 blocked; public IP allowed), a tampered-bundle replay that returns BLOCKED without sending, and redirect-to- internal classified BLOCKED vs a real connection error UNREACHABLE. 34 pass. Verified live via the CLI. Docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What & why
docs/CAPABILITY-GAPS.md(agent runtime) lists: "No finding-replay/regression to re-verify past bugs on a target." This closes it — and it's the natural second half of the PoC bundler (#144): once/pochas captured a finding,/replayre-sends that exact request and tells you whether the bug is still live. Retests pay again and programs increasingly reward regression reports; this turns a one-shot PoC into a monitorable asset.Two modes
run <bundle-dir> [--marker "signal"]— replay one bundle.all [root]— sweep everypoc-bundle/1underroot(defaultfindings/) into a rankedregression-report.md+regression.json. This is "retest day" in one command.Verdicts
STILL_VULNERABLEFIXEDCHANGEDUNREACHABLEINDETERMINATESKIPPED_UNSAFE--confirm-unsafeHow it decides (and where it's honest about limits)
confidence: confirmed) — a string that proves the bug (leaked email, reflected payload, error text). Present → still vulnerable; absent → fixed. Markers persist asmarker.txtso later sweeps reuse them automatically.confidence: heuristic) — with no marker, it compares status + response SHA-256. Byte-identical → still vulnerable (unchanged); different →CHANGED. It never claims FIXED from a hash alone (dynamic content also changes) — deliberately conservative.Reuses existing infra (no new surface)
safe_http.safe_urlopen(viapoc_bundler.capture).poc_bundler.parse_raw_request, hashes with the samesha256_hex.http://finding isn't replayed overhttps(avoids a falseUNREACHABLE— caught and fixed during testing, with a regression test).Safety
PUT/DELETE/PATCHskipped without--confirm-unsafe— replaying a mutating request could re-trigger the action.repro.sh) and never written intoreplay.jsonor the sweep report (asserted by a test).--fail-if-vulnerableexits3when anything is still vulnerable — for scheduled monitoring / CI.Testing
tests/test_replay.py— every verdict branch, header/secret resolution, bundle discovery + I/O, offline replay (via monkeypatchedcapture), and both CLI modes.decide_verdict,resolve_headers,render_report) tested without a socket; verified live end-to-end against the repo'sdemo/app.py(STILL_VULNERABLE while up, UNREACHABLE while down).4 files changed, 758 insertions(+).Try it
🤖 Generated with Claude Code