feat(replay): HTTP replay engine + credential management + vuln agent integration - #86
Merged
Conversation
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.
Contributor
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
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 |
Closed
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_replaytool with 7 modes: single, compare, sweep, raw, credential, unauthenticated, follow_redirectsCredential Management (4 tools)
credential_set_recipe— define auth refresh recipes (login form, token refresh, API key rotation)credential_mint— execute recipes to obtain fresh credentialscredential_validate— test if credentials are still validcsrf_extract— extract CSRF tokens from pages for recipe useVuln Agent Integration
Test Plan
bun test test/replay/— mutation engine unit testsbun run dev web→ proxy session → agent uses http_replay for vuln testingattack_script jwt_tamperstill works (kept scripts)attack_script cors_checkerreturns "not found" (removed scripts)