Skip to content

fix(security): enforce DNS-aware URL validation#2009

Merged
senamakel merged 2 commits into
tinyhumansai:mainfrom
Zavianx:codex/OH-1926-dns-rebinding-network-tools
May 17, 2026
Merged

fix(security): enforce DNS-aware URL validation#2009
senamakel merged 2 commits into
tinyhumansai:mainfrom
Zavianx:codex/OH-1926-dns-rebinding-network-tools

Conversation

@Zavianx
Copy link
Copy Markdown
Contributor

@Zavianx Zavianx commented May 17, 2026

Summary

  • Migrate http_request, curl, and web_fetch to the existing DNS-aware URL guard before outbound requests are made.
  • Keep the existing allowlist and local/private host policy unchanged while adding resolved-IP validation against DNS rebinding.
  • Make the DNS guard testable with an injectable resolver so rebinding coverage is deterministic and no longer depends on live google.com DNS.
  • Add focused tests for public resolved IPs, private resolved IP blocking, resolver failures, explicit ports, and caller validation surfaces.

Problem

  • Security: DNS rebinding bypasses SSRF protection in url_guard.rs #1926 reports that network tools validate the hostname string but still make requests through callers that use validate_url() only.
  • A domain can pass the allowlist as a public-looking hostname while DNS resolves to a loopback, RFC1918, link-local, or other non-global address.
  • The DNS-aware guard existed, but the outbound request paths were not using it.

Solution

  • Route the three request-making network tools through validate_url_with_dns_check().
  • Split DNS resolution into a small resolver helper and test-only injection path, preserving production behavior while making tests deterministic.
  • Validate the DNS lookup port from the URL, including explicit ports, before checking resolved IPs.
  • Replace the previous live-DNS google.com test with resolver-injected coverage for public and private address outcomes.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines are covered by focused Rust tests; CI coverage gate remains authoritative.
  • N/A: Coverage matrix updated — internal security hardening only, no feature row added/removed/renamed.
  • N/A: All affected feature IDs from the matrix are listed in the PR description under ## Related — no feature ID affected.
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: Manual smoke checklist updated if this touches release-cut surfaces — no release-cut UI or installer surface touched.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime/platform impact: Rust core network tools only; no frontend or Tauri behavior changes.
  • Security: outbound network tools now reject allowlisted hostnames whose resolved IPs are local/private/non-global before making requests.
  • Compatibility: existing URL scheme, allowlist, redirect-disabled behavior, timeout, and response-size behavior are unchanged.
  • Performance: adds one DNS resolution step before request-making network tools; failures surface before attempting the HTTP request.

Related

Closes #1926

  • Follow-up PR(s)/TODOs: none for this focused migration.

AI Authored PR Metadata (required for Codex/Linear PRs)

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

Commit & Branch

  • Branch: codex/OH-1926-dns-rebinding-network-tools
  • Commit SHA: 9eb89beeed1b9fe4a4a931151852a0d8e8d26400

Validation Run

  • pnpm --filter openhuman-app format:check — passed via pre-push hook
  • pnpm typecheck — passed
  • Focused tests: pnpm debug rust -- --lib url_guard -- --nocapture; pnpm debug rust -- --lib http_request -- --nocapture; pnpm debug rust -- --lib curl -- --nocapture; pnpm debug rust -- --lib web_fetch -- --nocapture; pnpm debug rust -- --lib network -- --nocapture
  • Rust fmt/check (if changed): cargo fmt --manifest-path Cargo.toml --all --check; cargo check --manifest-path Cargo.toml; git diff --check; CODEX_EXPECT_REPO_PATH=/home/ubuntu/workflow/openhuman node scripts/codex-pr-preflight.mjs --strict-path --lightweight
  • Tauri fmt/check (if changed): N/A: no Tauri source changed; pre-push hook also ran cargo check --manifest-path app/src-tauri/Cargo.toml successfully.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: http_request, curl, and web_fetch now perform DNS resolved-IP SSRF checks before outbound requests.
  • User-visible effect: requests to allowlisted hostnames that resolve to private/local/non-global IPs now fail early with a DNS rebinding/private-address error.

Parity Contract

  • Legacy behavior preserved: existing allowlist matching, URL scheme restrictions, redirect policy, response truncation, and tool parameter contracts remain unchanged.
  • Guard/fallback/dispatch parity checks: focused tests cover URL guard allow/deny behavior and each migrated network tool path; deterministic resolver tests cover the DNS rebinding branch without relying on live DNS.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none found for #1926, DNS rebinding, or validate_url_with_dns_check among open PRs before starting.
  • Canonical PR: this PR.
  • Resolution (closed/superseded/updated): N/A.

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened URL validation for network requests by adding DNS-based checks and improved allowlist enforcement to reject disallowed domains.
    • DNS rebinding protections made more robust and port-aware to prevent private-address bypasses.
  • Tests

    • Added deterministic async unit tests covering disallowed domains, localhost/private IP rejection, explicit-port resolution, and resolver failure handling.

Review Change Stack

@Zavianx Zavianx requested a review from a team May 17, 2026 14:26
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 27674370-7cb5-49bd-84aa-fb48e620a173

📥 Commits

Reviewing files that changed from the base of the PR and between 870b685 and 9eb89be.

📒 Files selected for processing (5)
  • src/openhuman/tools/impl/network/curl.rs
  • src/openhuman/tools/impl/network/http_request.rs
  • src/openhuman/tools/impl/network/http_request_tests.rs
  • src/openhuman/tools/impl/network/url_guard.rs
  • src/openhuman/tools/impl/network/web_fetch.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/openhuman/tools/impl/network/http_request_tests.rs
  • src/openhuman/tools/impl/network/web_fetch.rs
  • src/openhuman/tools/impl/network/curl.rs
  • src/openhuman/tools/impl/network/http_request.rs

📝 Walkthrough

Walkthrough

Refactors url_guard to perform port-aware, resolver-driven DNS validation (async) and updates curl, http_request, and web_fetch tools to call the new async validate_url_with_dns_check. Tests were converted to deterministic async tokio tests that exercise resolver-driven rejection/acceptance paths.

Changes

DNS-Rebinding Protection and Tool Migration

Layer / File(s) Summary
Core DNS-rebinding protection infrastructure
src/openhuman/tools/impl/network/url_guard.rs
Makes validate_url_with_dns_check async and pluggable with validate_url_with_dns_check_with_resolver, adds extract_port and resolve_host_ips, and updates tests to use injected resolvers for deterministic DNS rebinding checks.
Network tool migrations to DNS-checking validation
src/openhuman/tools/impl/network/curl.rs, src/openhuman/tools/impl/network/http_request.rs, src/openhuman/tools/impl/network/web_fetch.rs, src/openhuman/tools/impl/network/http_request_tests.rs
CurlTool, HttpRequestTool, and WebFetchTool now call the async validate_url_with_dns_check (awaited) in their URL validation paths; tokio tests assert disallowed domains are rejected with messages referencing allowed_domains.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • tinyhumansai/openhuman#1918: Introduced validate_url_with_dns_check and initial DNS-rebinding protection used and extended by this PR.

Suggested reviewers

  • senamakel

Poem

🐰 I hopped through hostnames, sniffing each port,

Resolved every name with a careful report.
No rebinding tricks can now make me pause —
I check each IP and uphold the laws.
A safer web garden — hooray, fluffle applause!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix(security): enforce DNS-aware URL validation' clearly and specifically describes the main change: migrating network tools to use DNS-aware validation to prevent DNS rebinding attacks.
Linked Issues check ✅ Passed The PR fully addresses the security objective from issue #1926 by migrating all three network tools (http_request, curl, web_fetch) to use validate_url_with_dns_check() for DNS-aware validation.
Out of Scope Changes check ✅ Passed All changes are scoped to the DNS rebinding security fix; refactoring url_guard.rs to make DNS resolution pluggable and updating network tools to use the DNS-aware validation are directly aligned with issue #1926.
Docstring Coverage ✅ Passed Docstring coverage is 82.14% which is sufficient. The required threshold is 80.00%.

✏️ 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 added the rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. label May 17, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/openhuman/tools/impl/network/url_guard.rs (2)

91-97: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate the explicit port before the IP-literal fast path.

Line 93 returns early for IP literals, so a URL like https://8.8.8.8:99999 is accepted without ever running extract_port(). That misses the new “validate explicit ports first” contract and defers the failure to reqwest instead of rejecting it here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/tools/impl/network/url_guard.rs` around lines 91 - 97, The
early-return that accepts IP-literal hosts (the
host.parse::<std::net::IpAddr>().is_ok() check) currently runs before validating
an explicit port, so URLs like "8.8.8.8:99999" bypass extract_port(); call
extract_port(&url) before the IP-literal fast path and handle its error result
(return Err on invalid port) so explicit port validation always runs; adjust the
control flow in the function containing that host.parse check to call
extract_port first, validate/propagate any port errors, then continue with the
IP-literal check and the existing is_private_or_local_host logic.

72-117: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This still leaves a rebinding race between validation and connect.

The helper validates one DNS lookup and then returns the original hostname URL. The later reqwest send path resolves that hostname again, so an attacker-controlled domain can still answer with a public IP during validation and a private IP during the actual connection. To fully close this gap, the request path needs to connect using the validated SocketAddrs or otherwise pin resolution after validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/tools/impl/network/url_guard.rs` around lines 72 - 117, The
validation currently only performs one DNS lookup and returns the original
hostname (validate_url_with_dns_check /
validate_url_with_dns_check_with_resolver), which leaves a race where the actual
connection can resolve to a different IP; instead, modify the validation API to
return the concrete validated SocketAddr(s) (or a tuple (url, Vec<SocketAddr>))
from resolve_host_ips and validate_url_with_dns_check_with_resolver and
propagate that to callers so the request path uses those pinned SocketAddr(s)
for the TCP connect (e.g., by dialing the SocketAddr(s) directly or configuring
the HTTP client's connector while preserving the original Host header), and
update all callers of validate_url_with_dns_check to accept the new return type
and use the pinned addresses for the actual reqwest connect.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/tools/impl/network/url_guard.rs`:
- Around line 120-128: The DNS lookup in resolve_host_ips uses the blocking
std::net::ToSocketAddrs (to_socket_addrs) on async call paths; change
resolve_host_ips to perform the resolution off the async reactor by wrapping the
blocking call in tokio::task::spawn_blocking (or use an async resolver) and
await its JoinHandle, returning the same anyhow::Result<Vec<IpAddr>>; ensure
errors from the blocking task are propagated and logged the same way (preserve
the log::debug! message and the anyhow::anyhow! error string) so callers of
resolve_host_ips need not change.

---

Outside diff comments:
In `@src/openhuman/tools/impl/network/url_guard.rs`:
- Around line 91-97: The early-return that accepts IP-literal hosts (the
host.parse::<std::net::IpAddr>().is_ok() check) currently runs before validating
an explicit port, so URLs like "8.8.8.8:99999" bypass extract_port(); call
extract_port(&url) before the IP-literal fast path and handle its error result
(return Err on invalid port) so explicit port validation always runs; adjust the
control flow in the function containing that host.parse check to call
extract_port first, validate/propagate any port errors, then continue with the
IP-literal check and the existing is_private_or_local_host logic.
- Around line 72-117: The validation currently only performs one DNS lookup and
returns the original hostname (validate_url_with_dns_check /
validate_url_with_dns_check_with_resolver), which leaves a race where the actual
connection can resolve to a different IP; instead, modify the validation API to
return the concrete validated SocketAddr(s) (or a tuple (url, Vec<SocketAddr>))
from resolve_host_ips and validate_url_with_dns_check_with_resolver and
propagate that to callers so the request path uses those pinned SocketAddr(s)
for the TCP connect (e.g., by dialing the SocketAddr(s) directly or configuring
the HTTP client's connector while preserving the original Host header), and
update all callers of validate_url_with_dns_check to accept the new return type
and use the pinned addresses for the actual reqwest connect.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0dd016d7-9f5f-4502-b352-545511154326

📥 Commits

Reviewing files that changed from the base of the PR and between f9de38d and 870b685.

📒 Files selected for processing (5)
  • src/openhuman/tools/impl/network/curl.rs
  • src/openhuman/tools/impl/network/http_request.rs
  • src/openhuman/tools/impl/network/http_request_tests.rs
  • src/openhuman/tools/impl/network/url_guard.rs
  • src/openhuman/tools/impl/network/web_fetch.rs

Comment thread src/openhuman/tools/impl/network/url_guard.rs Outdated
@senamakel senamakel merged commit 4471051 into tinyhumansai:main May 17, 2026
26 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: DNS rebinding bypasses SSRF protection in url_guard.rs

2 participants