Skip to content

feat: block private outbound MCP and model-provider URLs - #759

Open
bhaveshpatel640 wants to merge 12 commits into
mainfrom
AGE-2246
Open

bhaveshpatel640 wants to merge 12 commits into
mainfrom
AGE-2246

Conversation

@bhaveshpatel640

@bhaveshpatel640 bhaveshpatel640 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Block private, reserved, loopback, and in-cluster destinations for MCP url and model-provider base_url on save and on fetch.

Modeled on tfy-llm-gateway safeFetch (connect-time DNS lookup + per-hop redirect checks, deny CIDRs, k8s hostname shapes). Not a full port: no ENABLE_SSRF / ssrf: 'none', no forward-proxy path, no URL_VERIFY_CONFIG.

Closes AGE-2246

Changes

  • Deny RFC1918, CGNAT, TEST-NET, multicast, link-local, loopback, and matching IPv6 ranges before outbound HTTP
  • Socket lookup pins the connected IP; redirects are followed hop-by-hop so a 302 cannot land on a private address
  • Same check on MCP and model-provider create/update (HTTP 400)
  • Reject single-label and in-cluster hostnames (.svc, .cluster, .internal, …) unless allowlisted
  • OUTBOUND_URL_ALLOW_PRIVATE_HOSTS / OUTBOUND_URL_BLOCKED_HOSTS (Helm: configs.outboundUrl)

Localhost and other private hosts need an allowlist entry (for example Ollama).

How was this tested?

Unit tests for the URL guard (including redirects), MCP connect/save, and model-provider save.

Checklist

  • I have read the contributing guidelines
  • pnpm build, pnpm test, pnpm typecheck, pnpm lint:ci, and pnpm format:check pass locally
  • Tests added/updated where it makes sense
  • No hand-edits to generated code (packages/trueforge-sdk, python/trueforge_sdk, .github/fern/openapi/openapi.json, docs/openapi.json) — fork PRs omit SDK regen; maintainers regenerate after merge
  • Docs / .env.example updated if configuration or behavior changed

Note

High Risk
Changes security-critical outbound HTTP for MCP, LLM providers, and OAuth; misconfigured allowlists could break legitimate internal gateways or, if disabled, re-open SSRF.

Overview
Adds an outbound URL guard (on by default via ENABLE_SSRF) that blocks SSRF-style targets—private/reserved/loopback/link-local IPs, in-cluster-style hostnames (e.g. .svc, .internal), and non-http(s) schemes—with optional allow and block host lists.

ssrfFetch routes MCP, model-provider, and MCP OAuth HTTP through a guarded undici agent: DNS is checked before connect, redirects are followed manually hop-by-hop so a public URL cannot redirect to metadata or RFC1918, and the prior MCP-specific long body timeout is preserved on the shared agent.

Save-time validation returns HTTP 400 when MCP server url or model-provider base_url would be blocked. Helm and .env.example expose configs.outboundUrl / OUTBOUND_URL_*; tests cover the guard, API rejection, and updated mocks for undici.

Reviewed by Cursor Bugbot for commit 7b20691. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7b20691

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@truefoundry/trueforge-core Patch
@truefoundry/trueforge Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/trueforge-core/src/core/util/ssrfGuard.ts
@bhaveshpatel640
bhaveshpatel640 force-pushed the AGE-2246 branch 3 times, most recently from 6c042e3 to 7399f93 Compare September 16, 2026 08:36

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/trueforge-core/src/core/util/ssrfGuard.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 05e4e43. Configure here.

Comment thread packages/trueforge-core/src/core/util/ssrfGuard.ts Outdated
Comment thread packages/trueforge-core/src/core/util/ssrfGuard.ts Outdated
@bhaveshpatel640
bhaveshpatel640 force-pushed the AGE-2246 branch 2 times, most recently from b535eff to 13f3565 Compare September 16, 2026 17:58
// Node fetch (undici) defaults bodyTimeout to 300s of silence, then kills the stream with
// `Body Timeout Error` — we reconnect and the ~5m cycle repeats in logs. 30m matches the
// Gateway idle-body window; MCP request deadlines still come from requestTimeoutMs.
const MCP_BODY_TIMEOUT_MS = 30 * 60 * 1000;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

moved to ssrfGuard.ts

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.

1 participant