Skip to content

Replace curl-in-bash HTTP replay with a native structured replay engine (undici + raw-socket) #83

Description

@badchars

Problem

The proxy vuln-testers (proxy-tester-injection and siblings) confirm and weaponize findings by constructing curl command strings in bash. This is fragile in ways that hurt result quality:

  • Two-layer escaping. A payload passes through the shell and the target parser. The LLM must escape both layers perfectly on every call. Quotes, backticks, $, newlines routinely break the request — or worse, get interpreted by the local shell (e.g. a `...` payload executes locally and never reaches the target).
  • State handling. Cookies / CSRF tokens / auth refresh must be re-pasted per request; a dropped header silently invalidates the test.
  • Concurrency. Race-condition / single-packet tests are hard to time with curl one-liners.

Net effect: false positives (broken request read as "safe") and missed findings (payload never landed intact). The inject_probe tool already demonstrates the correct pattern — it sends via a real HTTP engine (fetch), never a shell — but only for its fixed enumeration batteries. The agent's confirm/weaponize step still falls back to curl.

Proposed direction

Introduce a first-class, agent-callable HTTP replay engine where the payload travels as data, not as part of a command line. Two backends:

  • undici — normal requests: HTTP/1.1 + HTTP/2, streaming, connection pool, proxy, correct encoding, cookie handling. Covers the large majority of targets.
  • raw TCP/TLS socket (net/tls) — byte-exact replay for request smuggling / desync and intentionally-malformed requests (which undici normalizes and thus cannot send).

Exposed as two tools, mirroring Caido's RequestSpec / RequestSpecRaw:

  • http_replay(request_id, mutations[]) — take a captured request, mutate named fields, send.
  • http_replay_raw(raw_bytes) — send exact bytes verbatim.

Reliability requirements (must-haves)

  • Timeout ≠ error semantics: always return elapsed ms; timeout threshold > max intended SLEEP so time-based injection isn't masked as a failure.
  • Idempotency guard: never auto-retry state-changing methods (POST/PUT/PATCH/DELETE) unless explicitly flagged safe.
  • Structured error taxonomy: dns | conn_refused | tls | timeout | reset | http_error | rate_limited — returned as data, not swallowed.
  • Circuit breaker per host; concurrency limiter + global request budget; scope check before every send (reuse existing matcher).
  • Hard per-request AbortController; response-body size cap.

Scope / non-goals

  • Additive & feature-flagged. No existing path (curl, inject_probe, testers) changes until the engine is wired in behind a flag; curl stays as fallback during transition.
  • HTTP/3 (QUIC) and JA3 fingerprint mimicry are out of core (they'd require a native/binary dependency, against the project's clean-npm-install goal). Handled later via an optional external-tool bridge (e.g. detect an installed h3-capable curl / curl-impersonate), with a clean "install X for this target" message when absent — mirroring how hackbrowser optionally uses Chromium.

Acceptance criteria

  • Engine sends field-mutated and raw-byte requests without any shell layer.
  • Unit tests (payloads with quotes/backticks/$/newlines land intact).
  • Reliability behaviors above covered by tests (timeout-as-data, no-retry-on-mutating, circuit breaker).
  • One tester (proxy-tester-injection) migrated behind a flag as proof; curl fallback intact.
  • No change to default behavior when the flag is off.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions