Skip to content

hackbrowser and http_replay tool should support a proxy #102

Description

@Serhatcck

CyberStrike has no proxy support anywhere today. Verified: no proxy handling in packages/hackbrowser/src, packages/cyberstrike/src/replay, or the config schema. The only transport control that exists is TLS verification — rejectUnauthorized, surfaced as the insecure_tls parameter (tool/http-replay.ts:273,615) — plus a NODE_TLS_REJECT_UNAUTHORIZED=0 scoped to the Chromium install step (tool/hackbrowser-launcher.ts:179).

The workflow this blocks: an operator wants everything CyberStrike sends to appear in their own intercepting proxy, so it can be inspected, diffed and hand-replayed alongside the automated run.

Scope — every sender, named

hackbrowser and http_replay are the two the title mentions, but they are not the only components that open outbound connections. All of them must resolve the same config; the ones that must NOT be proxied need to be explicit rather than accidental.

Sender Transport Proxied?
hackbrowser crawler Chromium (Playwright) yes
hackbrowser → session ingest worker fetch to the local server never — bypass
http_replay replay/backend-fetch.ts yes
http_replay_raw replay/backend-socket.ts opt-in only (see rule 4)
inject_probe own sender not directly — it is being moved onto the replay engine and will inherit from it
webfetch, csrf_extract, credential recipe steps fetch yes
LLM / provider API calls provider SDKs opt-in, default off

Config

Config-file only for v1 — no per-run or per-tool override.

{
  "network": {
    "proxy": {
      "enabled": true,
      "url": "http://proxy.example.com:8080",
      "auth": { "username": "operator", "password": "{env:CS_PROXY_PASSWORD}" },
      "bypass": ["localhost", "127.0.0.1", "::1", "*.internal.example.com"],
      "includeProviders": false
    },
    "tls": {
      "caPath": "/path/to/intercepting-ca.pem",
      "rejectUnauthorized": true,
      "clientCertificates": [
        {
          "host": "app.example.com",
          "certPath": "/path/to/client.crt",
          "keyPath": "/path/to/client.key",
          "passphrase": "{env:CS_CLIENT_CERT_PASSPHRASE}"
        },
        {
          "host": "api.example.com:8443",
          "pfxPath": "/path/to/client.pfx",
          "passphrase": "{env:CS_CLIENT_CERT_PASSPHRASE}"
        }
      ]
    }
  }
}

Secrets use the {env:VAR} interpolation the config loader already supports (config/config.ts:1337). No credential is ever written to the config file in plaintext by us.

clientCertificates is a per-host list (Postman's model): resolution happens at send time by matching the request's host — and host:port when a port is given, so a target on a non-default port can carry its own certificate.

Behavior rules

  1. Absent or enabled: false → today's behavior, byte for byte. This feature must have no effect on an unconfigured install.
  2. Loopback is always bypassed, whether or not it appears in bypass. The crawler posts captured traffic to the local server; routing that through an external proxy would break ingest and duplicate every captured request into the proxy's history.
  3. Provider/LLM traffic is opt-in via includeProviders, default false. When enabled, the proxy operator can see the API key in the request headers — the description text for this field must say so plainly.
  4. The raw-socket backend bypasses the proxy unless explicitly enabled. That backend exists to send byte-exact, deliberately malformed messages (smuggling / desync). An intercepting proxy re-frames HTTP/1.1 messages, which silently invalidates exactly those tests. If enabled, it must tunnel via CONNECT and pass bytes through untouched.
  5. insecure_tls on a single http_replay call still overrides for that call. Per-call beats config.
  6. Prefer caPath over disabling verification. Trusting the intercepting proxy's CA keeps certificate validation on for the target; rejectUnauthorized: false should stay a last resort, not the documented way to use a proxy.

Implementation notes per sender

  • Chromium: Playwright takes proxy: { server, username, password, bypass } at launch or context level. The crawler runs in a subprocess and only receives serializable options, so the resolved proxy/TLS descriptor has to be added to WorkerOptions (hackbrowser-subprocess/worker-ipc.ts) the same way the model descriptor already is.
  • backend-fetch.ts: already constructs an init.tls object for rejectUnauthorized (replay/backend-fetch.ts:104) — the CA and proxy settings extend the same spot.
  • backend-socket.ts: raw net / tls connect (replay/backend-socket.ts:100-106). Needs a CONNECT handshake before the TLS upgrade when proxying is enabled, and the CA / client-certificate options belong on the tls.connect call.
  • Bypass matching should be one shared helper, not reimplemented per backend — the crawler already has host-pattern matching for network scope (packages/hackbrowser/src/scope.ts) and the same wildcard semantics should apply here.

Out of scope for this issue

  • Per-run or per-tool proxy override (config only for now)
  • PAC files and proxy auto-detection
  • SOCKS support for the raw-socket backend
  • Recording/exporting proxy traffic inside CyberStrike

To verify during implementation

  • Whether Chromium picks up the OS proxy when Playwright is given no proxy option — this determines what "no config → today's behavior" actually means on each platform.
  • fetch proxy semantics on the runtime the main process uses, specifically HTTPS via CONNECT, and whether the TLS ca option takes a path or a buffer.
  • Playwright's per-context clientCertificates support at the pinned version, and whether it accepts the same per-host shape proposed above.

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