Repository navigation
fix(ssrf): block proxy-based SSRF bypass at every network boundary - #126
Merged
Merged
Conversation
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.
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, or169.254.169.254was accepted and connected to, bypassing the anti-SSRF guard entirely.This PR introduces a central
check_proxy_url()policy incore/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:fix(ssrf): classify proxy endpoints in the SSRF policy—check_proxy_url(), proxy scheme allowlist, IMDS/link-local/CGNAT/multicast/reserved blocks, explicitallow_localoperator opt-in.fix(client): enforce proxy policy at client and config boundaries— the effective proxy (explicit > config > environment) is validated beforehttpx.Clientis constructed;trust_envis explicit (defaultFalse);ProxyConfiggainsallow_local/use_envand validates everyhttp/https/socks5/proxy_listvalue at ingestion (string and list forms);parse_proxy()delegates to the policy.fix(api): block remote updates of protected config keys—PUT /api/configreturns 403 for operator-only keys (http.proxy.*,*.allow_local), including nested/batch updates and case variants.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 close1008).fix(services): report proxy SSRF blocks as validation errors—SSRFBlockedErroris re-raised at service boundaries (analyze, attack, quick, scrape) asVALIDATION_ERROR/ValidationErrorinstead of being swallowed by broadexcept Exception.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.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;pyright0 errors.Behavior changes (why this is marked as breaking)
HTTP_PROXY/HTTPS_PROXY/ALL_PROXYare no longer honoured by default — now opt-in viahttp.proxy.use_env: true(previous docs recommended these variables; when enabled, every env proxy is SSRF-validated before use, fail-closed).PUT /api/configonhttp.proxy.*/*.allow_localnow returns403(operator-only via CLI/env).Known follow-ups (not included in this PR)
_lookup_cves()inanalyze_service.pyandDynamicScraper.scrape()indynamic.pystill swallowSSRFBlockedErrorinto a warning/partial result.POST /api/config/loadis not yet covered by the protected-key allowlist._ALWAYS_FORBIDDEN_NETWORKSdoes not yet include NAT64 (64:ff9b::/96) / 6to4 (2002::/16) encodings, andsocks5h://is not in the proxy scheme allowlist.Related Issue
N/A — no GitHub issue tracks this finding (internal audit BUG-014).
Type of Change
Checklist
Screenshots (if applicable)
N/A — no UI changes (library/CLI/API hardening).