Skip to content

inject_probe sends via its own raw fetch() + duplicated scope guard — route it through the shared replay send engine (BackendFetch/Send.governed) that http_replay uses #106

Description

@Serhatcck

Filed from a review of inject_probe vs the http_replay engine.

Summary

inject_probe fires a payload battery at one parameter-bearing request, but it does its network I/O through its own send path — raw global fetch(), its own host scope guard, its own timeout/budget — instead of the verified, governed replay engine in src/replay/* that http_replay sits on. This duplicates logic that already exists (and is unit-tested) and leaves the battery ungoverned.

What inject_probe does now

  • src/tool/inject-probe.ts — internal send() (~line 907) calls the global fetch() (~line 937). It builds a URL + RequestInit rather than an HttpMessage.Request.
  • Own scope guard: guardHost(r, u) (~lines 861-863) plus the in-scope allowlist check (~lines 1690-1706) — parallel to http_replay's inScope (src/tool/http-replay.ts), i.e. the same "only reach a host the crawl already captured" rule, implemented twice.
  • Own per-request deadline (SEND_TIMEOUT_MS, ~line 902) and its own ad-hoc send budget counter.

Why it matters

  • No DoS/rate governing on a battery. http_replay sends go through Send.governed(...) + Governor (budget / circuit-breaker / retry-on-transient). inject_probe fires many payloads per call with none of that — it is exactly the caller that most needs pacing/circuit-breaking, yet has the least.
  • Duplication / drift risk. Two scope guards, two timeout policies, two response-extraction paths must be kept in sync; a fix to one (e.g. the fetch-hang deadline) has to be mirrored by hand.
  • No byte-exact option. fetch() normalizes the request; the engine also has BackendSocket (raw TCP/TLS) for payloads that must survive on the wire (relevant to some injection classes).

Proposed approach

Route inject_probe's sends through the same engine primitives http_replay uses (src/replay/*), not the raw fetch():

  • Minimal: replace the fetch() in send() with BackendFetch.send(msg, { origin, totalTimeoutMs, signal }) (src/replay/backend-fetch.ts), building an HttpMessage.Request from the probe.
  • Fuller (preferred): wrap it in Send.governed(() => BackendFetch.send(...), { budget, breaker }) so the battery is paced and circuit-broken, and share inScope instead of guardHost (delete the duplicate). Optionally reuse ../replay/observe for the generic reflection/error-signature facts, keeping inject_probe's specialized observation (tag-survival, SSTI evaluation, cmd-output detection) as-is.

Note: this is not a tool-to-tool call — http_replay's mutation/compare/sweep surface does not fit a payload battery. The shared thing is the send engine, not the tool.

Relation to #91

If the shared send is wired to a session-wide governor (module-level, keyed by session/host), this and #91 (governors instantiated per-call → inert) are naturally addressed together: one governor coordinating http_replay, inject_probe, and the parallel testers is the actual session-wide protection.

Constraints to preserve

  • inject_probe stays an evidence engine (facts, no verdict) — its specialized observation and coverage reporting must not change.
  • The existing scope-safety guarantee (only hosts the crawl captured) must be preserved exactly (that is the whole point of sharing inScope).
  • inject_probe already accepts a constructed target: {method, url} — the migration should keep that path working (it parallels the http_replay target source).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions