Skip to content

feat(replay): HTTP replay engine + credential management + vuln agent integration - #86

Merged
badchars merged 66 commits into
devfrom
feature/http-replay-engine
Aug 24, 2026
Merged

feat(replay): HTTP replay engine + credential management + vuln agent integration#86
badchars merged 66 commits into
devfrom
feature/http-replay-engine

Conversation

@badchars

Copy link
Copy Markdown
Contributor

Summary

Complete HTTP replay engine for vuln tester subagents — replaces shell-escaped curl with a structured mutation pipeline. Payloads travel as data, solving the escaping problem permanently.

Core Engine (12 modules)

  • http_replay tool with 7 modes: single, compare, sweep, raw, credential, unauthenticated, follow_redirects
  • 15 mutation ops: set-header, remove-header, set-query, set-body, set-method, set-target, set-cookie, remove-cookie, body-merge, encode, decode, regex-replace, duplicate-param, truncate, randomize
  • Request capture → mutation → replay pipeline with Zod-validated schemas

Credential Management (4 tools)

  • credential_set_recipe — define auth refresh recipes (login form, token refresh, API key rotation)
  • credential_mint — execute recipes to obtain fresh credentials
  • credential_validate — test if credentials are still valid
  • csrf_extract — extract CSRF tokens from pages for recipe use

Vuln Agent Integration

  • All 9 vuln prompts updated with http_replay examples
  • curl/webfetch hard-deny replaced with prompt-level preference
  • Auto-refresh on 401 responses using credential recipes
  • 8 redundant Python scripts removed (cors_checker, response_diff, idor_tester, ssti_tester, rate_limit_bypass, oauth_tester, graphql_tester, waf_bypass)

Test Plan

  • bun test test/replay/ — mutation engine unit tests
  • bun run dev web → proxy session → agent uses http_replay for vuln testing
  • attack_script jwt_tamper still works (kept scripts)
  • attack_script cors_checker returns "not found" (removed scripts)
  • Credential recipe: set recipe → mint → validate → auto-refresh on 401
  • Compare mode: two requests with different auth → diff response

actions-user and others added 30 commits August 12, 2026 12:46
Two-backend (undici + raw-socket) structured replay engine to replace the
curl-in-bash confirm/weaponize path in the proxy vuln-testers. Captures the
full capability catalog, the single choke-point funnel enforcement, governed
concurrency/backpressure, and the phased build plan.
Parse raw request bytes into an ordered, case- and duplicate-preserving
structure and serialize back with canonical CRLF. Targets are not normalized
(.., %2e, // survive) and body bytes round-trip verbatim, including shell-hostile
payloads and non-UTF-8 content. This is the structured layer of the two-level
model; byte-exact replay is the raw-socket backend's job.
Pure clone-and-return mutations over HttpMessage: query set/add/remove (add
enables parameter pollution), header set/add/remove (case-insensitive match,
existing-name case preserved, add allows duplicates), body/method/target/version
setters. Values are raw — encoding is a separate explicit step — and
Content-Length is never recomputed, so deliberate mismatches stay possible.
Deterministic, chainable codecs for WAF-bypass testing: url, url-all (every
byte), url-double, base64, base64url, hex, html-dec, html-hex, unicode, upper,
lower. Byte-oriented codecs operate on UTF-8 bytes; pipeline() chains them
left-to-right. Encoding is explicit and separate from mutation so the agent
controls exactly what bytes land on the wire.
…83)

Classify send failures into a stable taxonomy (dns/conn_refused/tls/timeout/
reset/unreachable/http_error/rate_limited/unknown) by unwrapping Node/fetch/
socket error codes and causes. Encodes the retry policy: only transient kinds on
idempotent methods retry; timeout is never retryable (it may be time-based
injection evidence); state-changing methods never auto-retry unless explicitly
flagged safe.
Pure, deterministic state machines protecting target/model/CyberStrike from an
overwhelming send rate: per-host CircuitBreaker (open after N failures, half-open
after cooldown), AimdLimiter (additive-increase/multiplicative-decrease
concurrency that auto-tunes to capacity), TokenBucket (req/s ceiling), and
GlobalBudget (anti self-DoS hard cap). Time is injected for testability. DEFAULTS
carries the approved starting values.
Parse raw HTTP response bytes (status line + headers + body) for the raw-socket
backend, preserving header order/case/duplicates and returning the body verbatim
(binary-safe; no de-chunking here). Defines the unified Result shape both
backends emit: exactly one of response/error, with timing ALWAYS present so a
timeout still carries elapsed ms — the signal time-based injection needs.
Bun's fetch surfaces ConnectionRefused/ConnectionClosed/ConnectionTimedOut/
DNSFailure rather than POSIX codes; add them to the taxonomy plus message-based
fallbacks (unable to connect -> conn_refused, etc.) so a dead-port or timeout
send is classified correctly on Bun, not left as unknown.
The engine core: send an HttpMessage via native fetch (undici under the hood,
like inject_probe). Payload travels as request data — no shell — so backtick/$/
quote payloads land byte-for-byte, verified end-to-end against a local server.
Never throws: transport failures return a classified Result.error, timeouts
preserve elapsed ms, oversized bodies are capped, TLS verification is toggleable.
Byte-exact/Host-override cases are backend B's job (fetch normalizes).
Writes the EXACT bytes given and parses the raw response itself — the byte-exact
half of the two-level model. Enables request smuggling/desync, malformed
messages, duplicate/odd-case headers, and Host-header override (verified: a
lowercase-method, duplicate-Host, oddly-spaced request lands byte-for-byte on the
wire). Body completion is detected from Content-Length or chunked framing so it
doesn't hang on keep-alive; SNI override and TLS verification are configurable.
Facts, never verdicts (inject_probe contract): reflection() reports whether a
marker is reflected raw vs html-encoded (special chars only, named or numeric —
matching real output encoders), errorSignatures() fingerprints SQL/NoSQL/LDAP/
XPath/stack-trace errors, and diff() reports status/length/time deltas and body
identity — the substrate for boolean- and time-based reasoning. No 'vulnerable'
field; the agent judges.
…udget skip (#83)

Backend-agnostic reliability wrapper combining errors + governor: per-attempt
budget consumption, circuit-breaker skip, transient+idempotent-only retry (POST/
PUT/PATCH/DELETE never auto-retried unless flagged safe), and AIMD feedback
(429/503/timeout -> throttle, success -> grow). A timeout is throttled but never
retried (may be time-based evidence). Clock and sleep are injectable, so the full
policy is verified deterministically with mock thunks — no network.
The §16 concurrency: run many sends at once but capped so the target/model/
CyberStrike are never overwhelmed. Cap is a fixed number or a live thunk (pass
AimdLimiter.value for adaptive concurrency that grows/shrinks with capacity); an
optional TokenBucket paces req/s. Worker-agnostic and pure — results in input
order, a throwing worker yields undefined without failing the batch, abort stops
new launches. Verified with mock workers (cap respected, AIMD adaptation, abort,
rate pacing).
Add Available Tools and Sending Test Requests sections with LLM
mutations: prompt injection body, system prompt override header,
multi-turn attack flow. Note llmhook as primary instrument.
…gine

LLM + http_replay mutations replace the "loop payloads, send HTTP,
check response" pattern these scripts implemented:

- cors_checker.py (Origin header → set-header)
- response_diff.py (two requests → two http_replay calls + LLM diff)
- idor_tester.py (cross-credential → proxy-tester-idor agent)
- ssti_tester.py (template payloads → injection agent)
- rate_limit_bypass.py (XFF rotation → set-header)
- oauth_tester.py (redirect_uri → set-query)
- graphql_tester.py (introspection → LLM-crafted queries)
- waf_bypass.py (encoding → http_replay encode pipeline)

8 scripts remain: jwt_tamper (crypto), ssrf_listener (callback
server), race_tester (async concurrency), file_upload_tester
(binary multipart), and 4 recon tools.
- Remove 8 script entries from AVAILABLE_SCRIPTS (replaced by http_replay)
- Update tool description: specialized capabilities beyond http_replay
- Update common-methodology.txt: add http_replay as primary send tool,
  reduce attack_script list to 8 remaining specialized tools
New mutation ops: body-merge, body-set-field, body-remove-field,
set-cookie, remove-cookie, set-path-param. Adds followRedirects
option to BackendFetch.send.
Allows agents to update a credential's auth headers after minting
fresh tokens. Validates session ownership before updating.
Recipe is a multi-step HTTP flow that produces fresh auth tokens.
Steps reference captured request_ids, support extract (Set-Cookie,
JSON, headers, regex), inject (cookies, headers, body_fields via
{{template}} syntax), and credential_map for final header mapping.

DB: adds nullable `recipe` JSON column to web_credential table
(auto-reconciled on startup). WebCredential gains setRecipe/getRecipe
methods and a rowToInfo helper to eliminate row-mapping duplication.
…lidate, csrf_extract

Four new tools for credential lifecycle management:
- credential_set_recipe: save a refresh recipe to a credential
- credential_mint: execute recipe to produce fresh auth tokens
- credential_validate: test if a credential is still accepted
- csrf_extract: fetch a page and extract CSRF tokens via regex/header/cookie
http_replay gains credential swap, unauthenticated mode, compare mode
(baseline+exploit with structured diff), sweep mode (multi-value test),
and auto-refresh: when a 401 is returned and the credential has a saved
recipe, the engine executes the recipe, updates credential headers, and
retries once.

Registers all new credential management tools and grants permissions
to vuln agents.
AuthN agent now owns the credential lifecycle: validate credentials,
create refresh recipes (cookie, bearer, OAuth, CSRF+login flows),
mint fresh tokens, and make them available to other agents via
http_replay's credential parameter. Adds examples for credential
swap, compare mode, sweep mode, and body-field mutations.
…er-step origin

- resolveTemplate returns empty when any {{var}} is unresolved (prevents partial headers like "Bearer ")
- inject.body_fields detects form-urlencoded and uses URLSearchParams instead of JSON merge
- Recipe steps now throw on HTTP 4xx/5xx responses
- Each step resolves origin from its own captured request (supports multi-origin OAuth)
- credential_set_recipe validates all step request_ids exist in session before saving
… compare/sweep support

- Promise-based mutex prevents concurrent 401s from triggering duplicate refreshes
- tryProactiveRefresh checks elapsed vs 80% TTL before all send modes
- doRefresh merges new auth headers into existing (case-aware) instead of replacing all
- Compare and sweep modes now get proactive credential refresh before sends
- bodyMerge catches invalid JSON gracefully instead of throwing
- setPathParam returns request unchanged on out-of-range instead of throwing
…srf-extract credential injection

- credential_validate: only 2xx is "valid", 3xx flagged as redirected with login-redirect hint
- credential_validate: inScope() guard refuses hosts not in session's captured set
- csrf_extract: new credential_id parameter injects auth headers for authenticated CSRF pages
…n in vuln agents

- Replace "prefer http_replay" with MANDATORY HTTP Request Policy
- Explicitly ban Python (requests/urllib/aiohttp), curl, wget for HTTP sends
- Add credential_mint, credential_validate, csrf_extract to Available Tools
- Clarify attack_script is ONLY for specialized scripts (JWT crypto, race, etc.)
- Update testing workflow to reference compare mode for baseline+exploit
…tch in vuln agents

bash deny patterns block curl, wget, Python HTTP libraries (requests, urllib,
aiohttp, httpx) at the permission layer. Old session history can override prompt
guidance but permission denies are absolute — agents physically cannot use banned
HTTP clients. webfetch also denied; http_replay is the only HTTP send path.
…verride_body

Recipe mutations and override_body now support bag variable interpolation,
enabling multi-step auth flows where later steps need extracted IDs in URL
paths, query params, or body (Clerk, Okta, Auth0, custom OAuth).
…ompts

- Remove "curl / webfetch" from Available Tools in 8 prompts
- Remove "Fall back to curl" language from all 9 prompts
- Add compare mode, sweep mode, credential swap examples to idor/authz
- Add body-set-field and body-merge examples to mass-assignment
- Add credential_mint/credential_validate awareness to idor/authz
- Add attack_script race_tester reference to business-logic
- Add attack_script ssrf_listener reference to ssrf
- http_replay is now documented as "The ONLY way" (not "PRIMARY way")
- Add http_replay to proxy-analyzer permission (replays captured request
  to get full HTML body instead of curl/webfetch)
- Remove all curl references from analyzer prompt
- Keep webfetch as fallback for URLs without a captured request
…denies

The existing curl/wget/python deny patterns had gaps: agents could use
bun -e "fetch(...)", node -e, nc, ncat, socat, telnet, or base64-pipe
to send HTTP requests bypassing all denies. Added 18 new patterns.
resolveTemplate used !val which treated "" as unresolved, causing recipes
to silently fail when an extracted cookie/header value was empty. Changed
to val === undefined so only truly missing variables cause resolution failure.
Previously parseJsonBody returned {} for non-JSON bodies, causing
bodyMerge to silently replace form-encoded data with just the merge
fields. Now returns the original request unchanged when body cannot
be parsed as JSON.
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

if (cur[k] === undefined || cur[k] === null || typeof cur[k] !== "object") cur[k] = {}
cur = cur[k] as Record<string, unknown>
}
cur[parts[parts.length - 1]] = value
@badchars
badchars merged commit 584dbe5 into dev Aug 24, 2026
8 of 13 checks passed
@badchars
badchars deleted the feature/http-replay-engine branch August 24, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants