Skip to content

Add domain allowlist to block SSRF via first-party proxy redirects#510

Open
prk-Jr wants to merge 7 commits intomainfrom
harden/ssrf-proxy-allowlist
Open

Add domain allowlist to block SSRF via first-party proxy redirects#510
prk-Jr wants to merge 7 commits intomainfrom
harden/ssrf-proxy-allowlist

Conversation

@prk-Jr
Copy link
Collaborator

@prk-Jr prk-Jr commented Mar 16, 2026

Summary

  • The first-party proxy followed redirects with no restriction on destination hosts. A signed URL pointing to an attacker-controlled origin could redirect to an internal service, enabling SSRF.
  • Adds proxy.allowed_domains — an opt-in list of permitted redirect-target hostnames supporting exact match ("example.com") and subdomain wildcard ("*.example.com") with dot-boundary enforcement to prevent evil-example.com bypass.
  • When the list is empty (the default), all redirect destinations are permitted — preserving backward compatibility for existing deployments.

Changes

File Change
crates/common/src/proxy.rs Add is_host_allowed helper; enforce allowlist on each redirect hop in proxy_with_redirects; 13 unit tests
crates/common/src/settings.rs Add allowed_domains: Vec<String> field to Proxy struct with serde default
trusted-server.toml Uncomment [proxy] section; add allowed_domains key with commented vendor examples
.env.dev Document TRUSTED_SERVER__PROXY__ALLOWED_DOMAINS env var override

Closes

Closes #414

Test plan

  • cargo test --workspace
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check
  • JS tests: cd crates/js/lib && npx vitest run
  • JS format: cd crates/js/lib && npm run format
  • WASM build: cargo build --bin trusted-server-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve

Note: JS tests fail with ERR_REQUIRE_ESM due to a pre-existing jsdom@28 / html-encoding-sniffer incompatibility on main — unrelated to this PR.

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code — use expect("should ...")
  • Uses log macros (not println!)
  • New code has tests
  • No secrets or credentials committed

The first-party proxy followed up to 4 redirects with no restriction on
redirect destinations. A signed URL pointing to an attacker-controlled
origin could redirect to an internal service, enabling SSRF.

Add `proxy.allowed_domains` — an opt-in list of permitted redirect target
hostnames. Supports exact match and `*.`-wildcard prefix with dot-boundary
enforcement. When the list is empty (default) behavior is unchanged for
backward compatibility.

Closes #414
@prk-Jr prk-Jr self-assigned this Mar 16, 2026
Copy link
Collaborator

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

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

Nice hardening update overall — the redirect allowlist behavior and wildcard boundary checks look solid.

I found two follow-ups worth addressing:

  1. Medium: proxy.allowed_domains entries are currently used as-is, so empty/whitespace patterns can make the allowlist non-empty but non-matchable, unexpectedly blocking redirects.

    • Evidence: crates/common/src/proxy.rs:587, crates/common/src/proxy.rs:470, crates/common/src/settings.rs:292
    • Recommendation: normalize at config load time (trim/lowercase/drop empties), and optionally validate pattern shape (example.com or *.example.com).
  2. Low: tests cover is_host_allowed logic well, but don’t yet exercise full redirect follow enforcement end-to-end in proxy_with_redirects.

    • Evidence: crates/common/src/proxy.rs:1907, crates/common/src/proxy.rs:484
    • Recommendation: add one allowed + one blocked redirect-chain test through proxy_request/redirect flow.

Everything else looked good, and CI is green.

Copy link
Collaborator

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

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

LGTM — core SSRF hardening looks correct and CI is green.

One suggestion for follow-up: the new proxy.allowed_domains setting should be documented in the proxy and configuration guides (docs/guide/first-party-proxy.md and docs/guide/configuration.md) so operators are aware of this security control. Specifically, it would be helpful to add a [proxy] section documenting:

  • allowed_domains semantics and wildcard matching rules
  • The default-open behavior when the setting is omitted
  • A production recommendation to explicitly set the allowlist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSRF via first-party proxy -- no target domain allowlist

2 participants