Skip to content

fix: harden Rust MediaFetcher agains redirect bypass and DNS rebinding - #8569

Merged
zhongdaor-nv merged 11 commits into
mainfrom
zhongdaor/rust-mm-ssrf-fixes
Apr 23, 2026
Merged

zhongdaor-nv merged 11 commits into
mainfrom
zhongdaor/rust-mm-ssrf-fixes

Conversation

@zhongdaor-nv

@zhongdaor-nv zhongdaor-nv commented Apr 22, 2026 •

Copy link
Copy Markdown
Contributor

Overview:

As title

Similar fix in rust side as #8282

Details:

  • RFC-defined blocked IP ranges (RFC 1918 / 5735 / 6598 / 6890 / 4193 / 4291)
  • Cloud-metadata / k8s hostname blocklist (metadata.google.internal, kubernetes.default.svc, localhost, …)
  • Case-insensitive hostname matching; trailing-dot FQDN normalized
  • New allow_private_ips escape hatch for on-prem / local-dev
  • Async check_if_url_allowed_with_dns resolves hostnames and rejects
    any that resolve to blocked IPs
  • Custom BlocklistResolver attached to the shared reqwest::Client via ClientBuilder::dns_resolver — filters blocked IPs out of DNS answers before reqwest ever sees them, closing the DNS-rebinding gap that the Python side left open (because httpx has no clean equivalent of reqwest's dns_resolver hook)
  • redirect::Policy::custom revalidates the URL-level policy on every redirect hop, capped

Where should the reviewer start?

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

  • closes GitHub issue: #xxx

Summary by CodeRabbit

Release Notes

  • New Features

    • Added configurable option to allow private IP addresses when fetching media content.
  • Security

    • Enhanced media URL validation to prevent SSRF attacks by blocking private IP ranges during media fetching.
    • Improved DNS resolution handling with security-focused filtering for media requests.

Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
@github-actions github-actions Bot added fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` labels Apr 22, 2026
Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
@zhongdaor-nv
zhongdaor-nv force-pushed the zhongdaor/rust-mm-ssrf-fixes branch from e064170 to 2f2816d Compare April 22, 2026 23:15
Comment thread lib/llm/src/preprocessor/media/loader.rs
The ipnet dep added to dynamo-llm in the main fix needs to be recorded
in the sub-workspace lock files for lib/bindings/{python,kvbm} as well,
otherwise CI's cargo --locked step fails.

Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
@zhongdaor-nv
zhongdaor-nv marked this pull request as ready for review April 22, 2026 23:29
@zhongdaor-nv
zhongdaor-nv requested a review from a team April 22, 2026 23:29
@coderabbitai

coderabbitai Bot commented Apr 22, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

The changes add SSRF (Server-Side Request Forgery) protection for media URL fetching by introducing IP/hostname blocklisting, DNS filtering via a custom resolver, and an allow_private_ips configuration flag. The ipnet crate dependency is added to the workspace and lib crate, with corresponding implementation in the media loader module and Python bindings.

Changes

Cohort / File(s) Summary
Workspace and Crate Dependencies
Cargo.toml, lib/llm/Cargo.toml
Add ipnet crate (version 2) as a workspace-managed dependency for IP address/CIDR range handling.
Core SSRF Hardening Implementation
lib/llm/src/preprocessor/media/loader.rs
Introduce IP/hostname blocklisting for private/internal ranges, custom reqwest DNS resolver that filters blocked IPs, configurable allow_private_ips flag on MediaFetcher, synchronous and async URL validation methods with DNS resolution, custom redirect policy enforcement, and comprehensive test cases for blocked ranges and hostname variants.
Python Bindings
lib/bindings/python/rust/llm/preprocessor.rs
Add allow_private_ips(&mut self, allow: bool) method to the MediaFetcher Python binding to expose the SSRF hardening configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main security hardening change: protecting MediaFetcher against redirect bypass and DNS rebinding attacks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description covers the Overview, Details, and Where to start sections with sufficient information about SSRF hardening changes, blocked IP ranges, custom resolver, and redirect policy implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/llm/src/preprocessor/media/loader.rs`:
- Around line 135-152: The flag allow_private_ips currently disables both the
RFC-range IP check and the hostname blocklist (BLOCKED_HOSTS) in the loader URL
guard, which is surprising and dangerous; either (A) rename the flag to
something explicit like allow_internal_targets/allow_private_targets and update
its field doc-comment and any Python binding to reflect that it disables both
host and IP checks, or (B) split into two flags (allow_private_ips and
allow_blocked_hosts) and change the guard to check each independently using
is_blocked_ip and BLOCKED_HOSTS; at minimum add a /// doc comment on the
allow_private_ips field explaining it disables both blocklists and update any
bindings/docs to match.
- Line 23: The current hardcoded MAX_REDIRECTS constant in loader.rs is set to 3
which is lower than reqwest's default and can break valid multi-hop flows;
update the constant (MAX_REDIRECTS) to a higher value (e.g., 10 to match
reqwest's default) and ensure any code that relies on MAX_REDIRECTS (redirect
handling logic and calls to check_if_url_allowed or BlocklistResolver) continues
to re-validate each hop so security posture is unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eb834529-7bad-4b46-ae95-aa6bfea7dfe8

📥 Commits

Reviewing files that changed from the base of the PR and between 6ba534a and 4853a0f.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • lib/bindings/kvbm/Cargo.lock is excluded by !**/*.lock
  • lib/bindings/python/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Cargo.toml
  • lib/bindings/python/rust/llm/preprocessor.rs
  • lib/llm/Cargo.toml
  • lib/llm/src/preprocessor/media/loader.rs

Comment thread lib/llm/src/preprocessor/media/loader.rs
Comment thread lib/llm/src/preprocessor/media/loader.rs
test_fetch_and_decode uses mockito which binds to 127.0.0.1, which the
new RFC-range blocklist rejects under the default policy. The test
already sets allow_direct_ip + allow_direct_port; add allow_private_ips
so the loopback address isn't rejected by the blocklist.

Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
…locklists

The flag's name reads IP-specific but semantically it's a single
'allow internal/on-prem targets' switch that bypasses both the
RFC-range IP blocklist and the literal hostname blocklist
(BLOCKED_HOSTS). This is intentional — real on-prem deployments need
both at once, and splitting would add config surface without a
concrete use case. The doc comment now makes the bundled semantics
explicit so reviewers and users aren't surprised by the hostname
blocklist being gated on a flag named allow_private_ips.

Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
test_serve_deployment[multimodal_agg_frontend_decoding] failed with
'Host localhost is blocked' because Python's UrlValidationPolicy.from_env()
read DYN_MM_ALLOW_INTERNAL=1 while Rust's MediaFetcher just used
::default() and ignored the env — so when the frontend preprocessor
(Rust) did the fetch instead of the backend worker (Python), the
internal-network opt-in was silently dropped.

Add MediaFetcher::from_env() mirroring the Python behavior, have
MediaLoader::new fall back to it when no explicit fetcher is passed,
and use from_env in the PyO3 #[new] so MediaFetcher() from Python gets
env-aware defaults.

Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
Signed-off-by: zhongdaor <zhongdaor@nvidia.com>
@zhongdaor-nv
zhongdaor-nv merged commit 61b0ffc into main Apr 23, 2026
93 of 94 checks passed
@zhongdaor-nv
zhongdaor-nv deleted the zhongdaor/rust-mm-ssrf-fixes branch April 23, 2026 22:06

This branch had an error being deployed

1 failed deployment
GITLAB — ba8e467a Deployed Apr 23, 2026 by copy-pr-bot[bot] via Trigger CI Pipeline #24977
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants