Security audit: SSRF attack surface analysis#213
Conversation
|
@claude code review |
|
Claude finished @RafaelPo's task in 1m 47s —— View job Code Review: SSRF Audit Report
Overall AssessmentThis is an excellent, thorough security audit. I cross-referenced every finding against the actual source code and can confirm the technical accuracy is very high — line numbers, code snippets, and behavioural descriptions all check out. The three-layer SSRF architecture is correctly understood and well-documented. The severity ratings are reasonable, and the recommended fixes are practical. A few observations below. Issues to Address1. Local file paths leaked throughout the report Every These should be repo-relative paths (e.g., 2. FINDING-01 PoC overstates exploitability The PoC claims "~1-5% success rate on fast networks" but provides no empirical evidence for this figure. Given that the TOCTOU window is within a single async coroutine (microseconds), and that Python's DNS resolver caches results via the OS resolver, the real success rate is likely much lower. I'd recommend either removing the percentage claim or qualifying it more carefully (e.g., "probabilistic; rate depends on OS resolver caching behaviour and TTL"). 3. FINDING-01 recommended fix has a TLS/SNI gap The suggested IP-pinning approach ( 4. FINDING-03 severity of TEST-NET ranges is overstated The report groups Suggestions (Non-blocking)5. Consider adding a "Scope Limitations" section The audit focuses on static analysis. It would strengthen the report to explicitly note what was not tested:
6. FINDING-06 mitigating factor deserves more weight The report correctly notes that Layer 2 ( 7. FINDING-09 call chain could note the HTTPS guarantee The 8. Attack Surface Matrix is a great addition The matrix at the end is very useful for quickly assessing coverage. Consider adding a column for "Remediation Status" so it can be used as a tracking artifact going forward. SummaryThe report is well-structured, technically accurate, and provides actionable recommendations with clear prioritisation. The main things to fix before merging are the local file path references (should be repo-relative) and the DNS rebinding PoC success rate claim (should be qualified). The recommended fixes are sound but the FINDING-01 IP-pinning suggestion needs a TLS/SNI caveat. Good work on this audit — it provides a solid foundation for tracking and remediating the identified issues. |
| # presents the right certificate (reviewer feedback on FINDING-01). | ||
| extensions = dict(request.extensions) | ||
| if request.url.scheme == "https": | ||
| extensions["sni_hostname"] = hostname.encode("ascii") |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
| def test_blocks_unresolvable(self): | ||
| with patch( | ||
| "everyrow_mcp.utils.socket.getaddrinfo", | ||
| side_effect=socket.gaierror("Name resolution failed"), | ||
| ): | ||
| with pytest.raises(ValueError, match="Could not resolve"): | ||
| _resolve_and_validate("nonexistent.invalid") | ||
|
|
||
| def test_unwraps_ipv4_mapped_ipv6(self): | ||
| with pytest.raises(ValueError, match="blocked IP"): | ||
| _resolve_and_validate("::ffff:127.0.0.1") |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
Defensive security audit of the MCP server's SSRF attack surface, reviewing protections from commit 4000b88. Covers DNS rebinding TOCTOU, port restrictions, IP blocklist gaps, rate limit bypass, URL parser discrepancies, and container hardening. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FINDING-01 (High): Eliminate DNS rebinding TOCTOU by resolving DNS
in _SSRFSafeTransport, validating all IPs, then pinning the TCP
connection to the validated IP. Original hostname preserved in Host
header and TLS SNI extension.
FINDING-02 (Medium): Add _ALLOWED_PORTS allowlist {80, 443, 8080,
8443} enforced both pre-flight in _validate_url_target and at
transport time. Non-standard ports (Redis 6379, Postgres 5432,
SMTP 25, etc.) are now rejected.
Also addresses code review feedback: repo-relative paths in report,
qualified DNS rebinding PoC claims, TLS/SNI caveat documented,
FINDING-06 downgraded to Info, scope limitations section added.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Inline _resolve_and_validate directly into _validate_url_target. The wrapper added no value after the FINDING-01 refactor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The report is tracked separately; this PR focuses on the code fixes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@claude code review |
|
Claude finished @RafaelPo's task in 1m 59s —— View job Code Review
Overall AssessmentSolid work. The DNS-pinning approach in Issues to Address1. IPv6 Host header is malformed for non-standard ports (Bug)
host_header = hostname
if request.url.port and request.url.port not in (80, 443):
host_header = f"{hostname}:{request.url.port}"This will cause requests to IPv6 endpoints on non-standard ports to fail. The Sentry comment about missing IPv6 test coverage ( Fix: host_header = f"[{hostname}]" if ":" in hostname else hostname
if request.url.port and request.url.port not in (80, 443):
host_header = f"{host_header}:{request.url.port}"2. SNI hostname encoding crashes on Internationalized Domain Names (Bug)
extensions["sni_hostname"] = hostname.encode("ascii")In practice httpx normalizes most URLs during parsing, so this is unlikely to trigger for typical usage. However, if a non-ASCII hostname ever reaches this path, it's an unhandled crash. Defensive fix: extensions["sni_hostname"] = hostname.encode("idna")The Suggestions (Non-blocking)3. Blocking DNS in async context
loop = asyncio.get_running_loop()
addrinfos = await loop.getaddrinfo(hostname, None, family=socket.AF_UNSPEC, type=socket.SOCK_STREAM)This would require splitting into an async variant for the transport path. Not urgent — the current approach is safe and correct. 4. IPv6 SNI hostname also needs brackets consideration When 5. Even when the port is default (80/443), the Host header for an IPv6 address should use brackets: On the Sentry Reviews
Good security work overall — the DNS-pinning architecture is correct and the defense-in-depth layering (pre-flight + transport + redirect hook) is well-structured. The two bugs above are straightforward fixes. |
…ding refs Wrap IPv6 addresses in square brackets for Host header per RFC 7230 §5.4, preventing malformed headers like `2001:db8::1:8080` (now `[2001:db8::1]:8080`). Add tests for IPv6 ULA/link-local blocking and transport-level Host header construction. Remove internal finding references from comments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from .encode("ascii") to .encode("idna") for defensive handling
of any non-ASCII hostname that might reach the transport. In practice
httpx pre-normalizes to punycode, but this prevents a UnicodeEncodeError
in edge cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| if request.url.port and request.url.port not in (80, 443): | ||
| host_header = f"{hostname}:{request.url.port}" |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
Wrap socket.getaddrinfo in loop.run_in_executor so DNS resolution doesn't block the event loop when multiple users are making concurrent requests. Make _resolve_and_validate and _validate_url_target async, update all callers and tests accordingly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
everyrow-mcp/src/everyrow_mcp/)Key findings
_SSRFSafeTransportnarrows but doesn't eliminate the race between validation and TCP connect100.64.0.0/10CGNAT/AWS VPC) from blocklistX-Forwarded-Forspoofing without proxy IP validationTest plan
🤖 Generated with Claude Code