fix(url-safety): reject RFC 6598 shared address space in strict mode#5474
Merged
StressTestor merged 2 commits intoJul 18, 2026
Merged
Conversation
…mode Strict mode (block_private=True) is a full SSRF lockdown, but it only rejected is_private and is_loopback targets. CPython does not classify RFC 6598 shared/CGNAT space (100.64.0.0/10) as is_private (it is "shared", not "private"), so a public redirect into 100.64.0.1 passed the per-hop guard and still issued the request to a potentially internal CGNAT service. not is_global would also exclude it, but only on CPython 3.11.10+/3.12.4+/ 3.13+; the CI matrix runs 3.11/3.12, so reject the range explicitly to stay correct across patch levels and the 3.14 runtime image. Default local-first mode is unchanged. Adds strict-mode coverage for shared, non-global, and public targets.
…mment The prior comment claimed `not is_global` catches 100.64.0.0/10 only on CPython 3.11.10+/3.12.4+/3.13+. That is inaccurate for CGNAT: is_global is False for 100.64.0.1 on every supported version (verified 3.10-3.14). The version-fragility applies to other ranges gh-113171 touched, not CGNAT. The explicit range reject is still the right choice; restate the reason as is_private not covering shared space, and not coupling strict mode to is_global's broader, cross-version definition. No behavior change.
11 tasks
Collaborator
Author
|
@RaresKeY If I could get a review of this one as well that'd be great, appreciate it. |
RaresKeY
approved these changes
Jul 18, 2026
RaresKeY
left a comment
Collaborator
There was a problem hiding this comment.
I checked the latest head's strict URL-classification change and did not find an author-actionable issue.
Validation
- Ran: focused URL-safety and strict redirect coverage, including RFC 6598 bounds, IPv4-mapped values, and mixed resolver answers.
- Not run: live DNS/HTTP behavior or a full multi-version runtime matrix.
3 tasks
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.
Summary
strict-mode SSRF hardening for the shared URL validator.
check_outbound_url(..., block_private=True)is the full SSRF lockdown used for exposed multi-tenant deployments. it rejectedis_privateandis_loopbacktargets, but RFC 6598 shared / CGNAT space (100.64.0.0/10) fell through, because CPython does not classify that range asis_private(it is "shared", not "private"). so a publicskills.sh-style redirect into100.64.0.1passed the per-hop guard and still issued the request to a potentially internal CGNAT service. this change rejects100.64.0.0/10explicitly in strict mode.an explicit range reject (a pure integer-arithmetic membership test) closes exactly the shared-space gap and avoids coupling strict mode to
is_global's broader definition, which has shifted across CPython versions for other special ranges. default local-first mode (block_private=False) is unchanged: loopback, LAN, and CGNAT stay reachable for local embedding servers. link-local metadata (169.254.0.0/16) was already covered, so CGNAT was the residual.Target branch
dev, notmain.Linked Issue
no separate issue was filed. the shared-space gap was raised by @RaresKeY in the strict-mode SSRF review on #5261; this splits the validator-level fix out from that importer PR so it can land on its own.
Part of #5261
Type of Change
Checklist
devdocker compose uporuvicorn app:app) and verified the change works end-to-end. Type-checks and unit tests are not enough. (left unchecked on purpose: this is a pure validator function with no runtime surface a full app-run exercises beyond the injected-resolver tests, and the resolver injection is exactly how the DNS path is driven deterministically. verified at that level plus a revert-to-RED proof, see How to Test.)How to Test
the validator is DNS-dependent, so the tests inject a resolver and never touch real network.
conftest.pypulls the full app stack, so run this stdlib-only module directly with--noconftest:python3 -m pytest tests/test_url_safety.py -q --noconftest-> 13 passedgit checkout dev -- src/url_safety.py && python3 -m pytest tests/test_url_safety.py::test_strict_mode_blocks_cgnat_shared_space -q --noconftest-> fails onassert ok is False. restore the file -> passes again.Visual / UI changes
N/A. no UI or rendering changes. touches only
src/url_safety.py(validator logic) andtests/test_url_safety.py.