Skip to content

🔒️ Add SSRF_ALLOWED_HOSTS env for self-hosted internal APIs#2474

Open
klinux wants to merge 2 commits intobaptisteArno:mainfrom
klinux:fix/ssrf-allowlist-env
Open

🔒️ Add SSRF_ALLOWED_HOSTS env for self-hosted internal APIs#2474
klinux wants to merge 2 commits intobaptisteArno:mainfrom
klinux:fix/ssrf-allowlist-env

Conversation

@klinux
Copy link
Copy Markdown

@klinux klinux commented Apr 27, 2026

Summary

Self-hosted deployments often have legitimate internal corporate APIs on RFC1918 ranges (10/8, 172.16/12, 192.168/16) — e.g., a backend chat API exposed only on the internal cluster network. Since v3.14, the SSRF mitigation introduced for CVE-2025-64709 / GHSA-8gq9-rw7v-3jpr blocks every private range unconditionally, which prevents HTTP Request blocks (and Function blocks via fetch) from reaching those APIs without exposing them to the public internet.

The advisory itself listed hostname allowlisting as one of the recommended mitigations (item #5: "Implement an SSRF-safe proxy or apply hostname allowlists for outgoing requests"), and this PR implements it as an opt-in env var.

What changes

  • New env var SSRF_ALLOWED_HOSTS (comma-separated hostnames) parsed in packages/env
  • validateHttpReqUrl now accepts an allowedHosts parameter (symmetric with the existing lookupHost injection point); the env var is the default
  • When the URL's hostname matches an entry, validateIPAddress is called with { allowPrivateRanges: true }, which only skips the RFC1918 range checks (10/8, 172.16/12, 192.168/16)

What the allowlist does NOT relax

Every other protection remains active even for allowlisted hosts:

  • ✅ Link-local 169.254.0.0/16 — the actual CVE vector (AWS/GCP/Azure metadata)
  • ✅ Loopback 127.0.0.0/8 and IPv6 ::1
  • ✅ 0.0.0.0/8
  • ✅ IPv6 link-local fe80::/10 and unique local fc00::/7
  • ✅ Cloud metadata hostnames (`metadata.google.internal`, `metadata.goog`, `metadata`)
  • ✅ `localhost` in production
  • ✅ Decimal/hex/octal IP encoding bypasses
  • ✅ IMDS bypass headers (`X-aws-ec2-metadata-token*`, `Metadata-Flavor`)

This is the deliberate design: even if an attacker controls DNS for an allowlisted hostname and points it to 169.254.169.254, the link-local check still fires. The allowlist intentionally narrows what's relaxed — corp LAN access, not metadata-service access.

Test plan

  • All existing 53 SSRF tests still pass unchanged (default behavior preserved when env unset)
  • New `describe` block covering 14 cases:
    • RFC1918 hostnames pass when listed (10/8, 172.16/12, 192.168/16, direct IP literal)
    • Link-local still blocks for allowlisted host (DNS hijack defense)
    • Loopback still blocks for allowlisted host
    • Direct `169.254.169.254` IP literal still blocks even when listed
    • `metadata.google.internal` still blocks even when listed
    • Decimal-encoded metadata IP still blocks even when listed
    • Default behavior preserved when `allowedHosts` is undefined or empty
    • Hostname not in allowlist still blocks
    • Case-insensitive matching (URL parser normalizes hostname)
    • No subdomain wildcarding (exact match only)
  • `bun test` green: 63/63 in `validateHttpReqUrl.test.ts`
  • `tsc --noEmit` green for `packages/lib` and `packages/env`
  • Full `nx affected` test suite green (whatsapp, feature-flags, spaces, rich-text, root, emails, bot-engine, results, builder, lib — all passed)

Use case

Currently, self-hosters facing this hit dead-ends: their internal corp DNS resolves to 10.x, the validator rejects it, and the only escape valves are (a) expose the API publicly (security regression — adds attack surface), (b) downgrade to ≤ v3.13.x (re-introduces the vulnerable code path), or (c) maintain a fork with the validator patched (fragile, breaks on every upgrade). An opt-in env var resolves this without weakening the core mitigation.

I'm opening a companion issue (#2475) explaining the use case in more detail and to gather feedback if a different design is preferred — happy to iterate.

Self-hosted deployments often have legitimate internal corporate APIs
on RFC1918 ranges (10/8, 172.16/12, 192.168/16). Since v3.14 the SSRF
mitigation introduced for CVE-2025-64709 blocks every private range
unconditionally, which prevents HTTP Request blocks from reaching those
APIs without exposing them to the public internet.

This adds an opt-in `SSRF_ALLOWED_HOSTS` env var (comma-separated list
of hostnames). When the URL's hostname matches an entry, the validator
skips the **RFC1918 private-range checks only**. Every other protection
remains active even for allowlisted hosts:

- link-local 169.254.0.0/16 (the actual CVE vector → metadata services)
- loopback 127.0.0.0/8 and IPv6 ::1
- 0.0.0.0/8
- IPv6 link-local fe80::/10 and unique local fc00::/7
- cloud metadata hostnames (metadata.google.internal etc.)
- localhost (production)
- decimal/hex/octal IP encoding
- IMDS bypass headers (X-aws-ec2-metadata-token*, Metadata-Flavor)

This means even if an attacker controls DNS for an allowlisted hostname
and points it to 169.254.169.254, the link-local check still fires.
The allowlist intentionally narrows what's relaxed: corp LAN access,
not metadata-service access.

Allowed hosts can also be passed via the new `allowedHosts` parameter
(symmetric with `lookupHost`); the env var is the default.

Tests cover: RFC1918 hostnames pass when listed, link-local/loopback
still block for listed hosts, default behavior preserved when env unset,
case-insensitive matching, no subdomain wildcarding.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 27, 2026

@klinux is attempting to deploy a commit to the Typebot Team on Vercel.

A member of the Team first needs to authorize it.

@baptisteArno
Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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.

2 participants