Skip to content

fix(ssrf): block proxy-based SSRF bypass at every network boundary - #126

Merged
HC-ONLINE merged 7 commits into
mainfrom
fix/ssrf-proxy-validation
Oct 22, 2026
Merged

HC-ONLINE merged 7 commits into
mainfrom
fix/ssrf-proxy-validation

Conversation

@HC-ONLINE

Copy link
Copy Markdown
Owner

Description

Fixes BUG-014 (internal audit): the SSRF policy only classified the request target, never the proxy endpoint where the TCP connection is actually opened. A proxy pointing to 127.0.0.1, 10.x, or 169.254.169.254 was accepted and connected to, bypassing the anti-SSRF guard entirely.

This PR introduces a central check_proxy_url() policy in core/client/ssrf.py (independent from the target policy: neither opt-in authorizes the other) and enforces it at every boundary where a proxy can enter or a connection can be opened. It spans 7 commits:

  1. fix(ssrf): classify proxy endpoints in the SSRF policy — check_proxy_url(), proxy scheme allowlist, IMDS/link-local/CGNAT/multicast/reserved blocks, explicit allow_local operator opt-in.
  2. fix(client): enforce proxy policy at client and config boundaries — the effective proxy (explicit > config > environment) is validated before httpx.Client is constructed; trust_env is explicit (default False); ProxyConfig gains allow_local/use_env and validates every http/https/socks5/proxy_list value at ingestion (string and list forms); parse_proxy() delegates to the policy.
  3. fix(api): block remote updates of protected config keys — PUT /api/config returns 403 for operator-only keys (http.proxy.*, *.allow_local), including nested/batch updates and case variants.
  4. fix(scraping): validate Chromium proxy and guard WebSocket routes — Playwright's own proxy is validated before the browser context is created; WebSocket routes are guarded (connect_to_server() only for allowed endpoints, otherwise close 1008).
  5. fix(services): report proxy SSRF blocks as validation errors — SSRFBlockedError is re-raised at service boundaries (analyze, attack, quick, scrape) as VALIDATION_ERROR/ValidationError instead of being swallowed by broad except Exception.
  6. feat(client): support SOCKS5 proxies via httpx[socks] — httpx[http2,socks]==0.28.1 + socksio==1.0.0; verified with a local SOCKS5 relay integration test.
  7. docs: document proxy SSRF policy and operator-only config — docs/CONFIGURATION.md, docs/API.md, .env.example.

Testing: 167 new tests (policy, client/config boundaries, API protection, Playwright proxy/WebSocket, service error propagation, real SOCKS5 transport). Full suite on this branch: 2106 passed, 3 skipped; ruff check . clean; pyright 0 errors.

Behavior changes (why this is marked as breaking)

  1. HTTP_PROXY/HTTPS_PROXY/ALL_PROXY are no longer honoured by default — now opt-in via http.proxy.use_env: true (previous docs recommended these variables; when enabled, every env proxy is SSRF-validated before use, fail-closed).
  2. Config files containing forbidden proxy endpoints fail closed at load (loader falls back to defaults with an error log) instead of silently connecting through them.
  3. PUT /api/config on http.proxy.* / *.allow_local now returns 403 (operator-only via CLI/env).

Known follow-ups (not included in this PR)

  • Two narrow error-classification gaps remain (the connection is still blocked, but the reported error code can be misleading with legacy/injected config state): _lookup_cves() in analyze_service.py and DynamicScraper.scrape() in dynamic.py still swallow SSRFBlockedError into a warning/partial result.
  • POST /api/config/load is not yet covered by the protected-key allowlist.
  • _ALWAYS_FORBIDDEN_NETWORKS does not yet include NAT64 (64:ff9b::/96) / 6to4 (2002::/16) encodings, and socks5h:// is not in the proxy scheme allowlist.

Related Issue

N/A — no GitHub issue tracks this finding (internal audit BUG-014).

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

This is a security bug fix (BUG-014) that also adds SOCKS5 support; the two boxes above are labelled "non-breaking" in the template, so the change is classified under Breaking change because of the hardening defaults listed in the description.

Checklist

  • I have followed the style guidelines of this project (Ruff & Pyright)
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly

Screenshots (if applicable)

N/A — no UI changes (library/CLI/API hardening).

@HC-ONLINE
HC-ONLINE merged commit 9b6d32d into main Oct 22, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant