[OWASP ASI02] Block SSRF in recon-tool URL arguments and gateway base URL#1
Open
bdevierno1 wants to merge 1 commit into
Open
[OWASP ASI02] Block SSRF in recon-tool URL arguments and gateway base URL#1bdevierno1 wants to merge 1 commit into
bdevierno1 wants to merge 1 commit into
Conversation
- url_guard: replace incomplete 172.16-172.19 prefix list with _PRIVATE_PREFIX_RE covering the full RFC 1918 172.16.0.0/12 range (172.16–172.31); add 169.254.169.254 / fd00:ec2::254 to blocked hostname set for metadata-endpoint defence-in-depth. - tools: call is_safe_public_target() at the top of _analyze_headers_impl and _probe_paths_impl before any outbound HTTP request; these functions were previously reachable by an LLM-injected URL without hitting the API-level guard in main.py. - vecna_agent: introduce _validated_gateway_base() which reads OPENROUTER_API_BASE from env, validates it through is_safe_public_target, and raises RuntimeError if the URL targets a private/internal range; use it in _client_args_for_model() replacing the bare os.environ.get call. - tests: add backend/tests/test_url_guard.py with 29 assertions covering all blocked classes (RFC 1918 ranges including the previously-missing 172.20–172.31, loopback, link-local, metadata endpoints, zero address) and public-IP/hostname allow-cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
OWASP Reference
is_safe_public_target()call at the start of each recon tool before any outbound HTTP request; complete the RFC 1918 172.16.0.0/12 prefix denylist to cover 172.20–172.31; validateOPENROUTER_API_BASEgateway URL through the SSRF guard before use.Reproduction (from validator F-04 verdict)
The API-level guard (
is_safe_public_targetinmain.py:138) runs only at the/api/operationsPOST endpoint. It does not protect the recon tools that execute downstream. After the API accepts a legitimate public target, an attacker who can inject tool arguments (e.g. via prompt-injection content on the scanned site, or direct tool calls if the agent is exposed) can pass a private URL directly to:analyze_http_security_headers("http://169.254.169.254/latest/meta-data/")→_analyze_headers_implcalls_normalize_base(url)thenclient.get(base + "/")with no SSRF check → live request to AWS instance-metadata service.probe_common_paths("http://192.168.1.1/")→_probe_paths_impliteratesDEFAULT_PATHSagainst the host with no SSRF check → live requests to the internal gateway/router.A secondary gap:
url_guard.pyblocked172.16–172.19by hostname prefix but missed172.20–172.31(the rest of RFC 1918 172.16.0.0/12), so a URL likehttp://172.25.10.5/bypassed the prefix check (theipaddress-literal path still caught raw IP literals, but the prefix path was incomplete for prefix-like hostnames).A third gap:
OPENROUTER_API_BASEinvecna_agent.pywas read from the environment and used directly as the LLM gateway base URL without SSRF validation, allowing environment-variable injection to redirect LLM API calls to an internal endpoint.Fix description
1.
backend/app/url_guard.pyReplaced the manual tuple
("172.16.", "172.17.", "172.18.", "172.19.", ...)with a compiled regex_PRIVATE_PREFIX_REthat covers the full RFC 1918 172.16.0.0/12 range (second octet 16–31) plus all other private/reserved prefixes. Added169.254.169.254andfd00:ec2::254to_BLOCKED_HOSTNAMESfor metadata-endpoint defence-in-depth.2.
backend/agent/tools.pyAdded
is_safe_public_target(target_url)guard at the start of_analyze_headers_impland_probe_paths_impl. Either function now returns an error JSON immediately if the URL targets a private/internal address, before anyhttpxconnection is attempted. The guard re-uses the same logic already trusted at the API layer — no new code paths.3.
backend/agent/vecna_agent.pyIntroduced
_validated_gateway_base(): readsOPENROUTER_API_BASEfrom the environment, validates it throughis_safe_public_target, and raisesRuntimeErroron a blocked URL. Replaced the bareos.environ.get("OPENROUTER_API_BASE", ...)call in_client_args_for_modelwith this validated helper.4.
backend/tests/test_url_guard.py(new)29 assertions: all blocked categories (RFC 1918 full range, loopback, link-local, metadata endpoints, zero address,
.localhostsubdomains) and public-address allow-cases. Specifically tests172.20,172.25, and172.31to prove the previously-missing range is now covered.Out of scope
_resolve_dns_implmakes outbound HTTP only to the fixedcrt.shAPI (not to the target URL) — no SSRF risk; not changed here.vecna_litellm.pylow-level LiteLLM patcher: itsOPENROUTER_API_BASEread is superseded by the_validated_gateway_base()call invecna_agent.py(higher in the call stack); patching the lower layer would be redundant. Tracked as a sibling hardening item if desired.is_safe_public_targetpasses: requires a separate server-side DNS pinning orfollow_redirects=Falsehardening pass (different ASI class / scope).Next-Assignee: vecna-audit-verifier