fix(core): close IPv6/unspecified SSRF bypass in web viewport#82
Conversation
The web viewport SSRF guard from #64 classified hosts through `Url::host_str()`, which renders IPv6 literals in bracketed form (`[::1]`). `host.parse::<IpAddr>()` then fails on that bracket text, so `is_private_or_loopback_ip` was never reached: loopback, IPv4-mapped loopback, and unique-local IPv6 destinations all slipped the filter and were fetched freely. Two narrower gaps rode along. The unspecified addresses `0.0.0.0` and `[::]` connect to the local host yet matched no private or loopback predicate. And a trailing dot (`localhost.`) sidestepped the literal `localhost` comparison while resolving identically. Match on the parsed `Url::host()` enum so IPv6 literals arrive as `Host::Ipv6` and reach the classifier, reject unspecified addresses explicitly, and strip a trailing dot before the local-hostname check. New tests drive `validate_web_url` with concrete attack URLs instead of only exercising the IP predicate in isolation. Co-Authored-By: Nova (Claude Opus 4.7) <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
Follow-up to #64. The web viewport SSRF guard had a bracketed-IPv6 hole
that let every IPv6 destination through, plus two narrower bypasses.
validate_web_urlclassified the host viaUrl::host_str(), whichrenders IPv6 literals in bracketed form (
[::1]). The subsequenthost.parse::<IpAddr>()fails on that bracket text, sois_private_or_loopback_ipwas never reached.http://[::1]/,http://[::ffff:127.0.0.1]/(IPv4-mapped loopback), andhttp://[fc00::1]/(unique-local) all resolved and fetched freely.Two smaller gaps: the unspecified addresses
0.0.0.0and[::]connect to the local host but matched no private/loopback predicate,
and a trailing dot (
localhost.) sidestepped the literallocalhostcheck while resolving identically.
Fix
Url::host()enum so IPv6 literals arrive asHost::Ipv6and reach the IP classifier.0.0.0.0,[::]) explicitly.validate_web_urltests driven by concrete attack URL strings(
[::1], IPv4-mapped, ULA, link-local, unspecified, decimal-IP,localhost., bad schemes) rather than only the IP predicate.Verification
cargo test -p hypercolor-core --features servo --lib web_viewport— 13 passed (includes the two new SSRF tests)
cargo clippy -p hypercolor-core --features servo --lib --tests— cleancargo check --workspace— cleancargo fmt --all --check— clean🤖 Generated with Claude Code