Skip to content

feat(secureenv): opt-in proxy env forwarding to upstreams (MCP-2769)#704

Merged
Dumbris merged 2 commits into
mainfrom
mcp-2769-forward-proxy-env
Jun 17, 2026
Merged

feat(secureenv): opt-in proxy env forwarding to upstreams (MCP-2769)#704
Dumbris merged 2 commits into
mainfrom
mcp-2769-forward-proxy-env

Conversation

@Dumbris

@Dumbris Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

Split out of MCP-2751 (login-shell hydration). Codex flagged that adding HTTP_PROXY/HTTPS_PROXY to the secureenv default allow-list would forward credential-bearing proxy URLs (http://user:pass@proxy) to every stdio upstream on all platforms. This PR keeps proxy vars out of the default allow-list and makes forwarding an explicit, credential-redacting opt-in.

Behavior

  • New global config flag forward_proxy_env (default false). Current behavior is unchanged when unset.
  • When true, secureenv.BuildSecureEnvironment forwards the ambient proxy variables to spawned stdio upstreams:
    • Vars: HTTP_PROXY, HTTPS_PROXY, NO_PROXY, ALL_PROXY, FTP_PROXY — both upper- and lower-case spellings (case-alias aware).
    • Userinfo (credentials) is redacted from each value (http://user:pass@proxy:8080http://proxy:8080); the proxy host/port is preserved.
    • An explicitly configured proxy value (custom_vars or a server's env) wins and suppresses forwarding of the ambient value, including the other-cased alias.
    • Forwarding is independent of inherit_system_safe (it is its own explicit opt-in).
  • Proxy vars remain excluded from the default allow-list and from login-shell hydration (unchanged from feat(shellwrap): hydrate login-shell env once at startup (macOS GUI/launchd) — MCP-2751 #701).

Files

  • internal/config/config.goforward_proxy_env flag.
  • internal/secureenv/manager.go — forwarding + redactProxyCredentials.
  • internal/upstream/core/client.go — thread flag into per-server stdio env.
  • docs/configuration.md — opt-in docs + never-by-default rationale.
  • oas/* — regenerated (config is a documented model).

Tests

  • redactProxyCredentials table (user:pass, user-only, socks, host-list, unparseable).
  • Default-off leak guard (proxy in process env is NOT forwarded).
  • Opt-in forwards with credentials redacted; NO_PROXY passes unchanged.
  • Explicit value wins over the ambient alias.
  • End-to-end wiring test through managed.NewClient.

Verification

go test ./internal/secureenv/... ./internal/config/... ./internal/upstream/... -race   # ok
golangci-lint v2.5.0 run --config .github/.golangci.yml <changed pkgs>                 # 0 issues
go build ./... && go vet <changed pkgs>                                                # ok

Related #701

Split out of MCP-2751 login-shell hydration: forwarding HTTP(S)/proxy env
vars to every stdio upstream by default leaks credential-bearing proxy URLs
(http://user:pass@proxy). Keep proxy vars OUT of the default allow-list and
gate forwarding behind an explicit opt-in.

- config: new global `forward_proxy_env` (default false).
- secureenv: when opted in, BuildSecureEnvironment forwards the ambient proxy
  vars (HTTP_PROXY/HTTPS_PROXY/NO_PROXY/ALL_PROXY/FTP_PROXY, both upper- and
  lower-case spellings) to spawned stdio upstreams. userinfo (credentials) is
  redacted from each value; the proxy host/port is preserved. Case-alias aware:
  an explicitly configured spelling suppresses forwarding of the ambient
  alias. Forwarding is independent of inherit_system_safe.
- upstream/core: thread the flag into the per-server env config for stdio spawns.
- docs/configuration.md: document the opt-in + the never-by-default rationale.

Tests: redaction unit table, default-off leak guard, opt-in redacted forward,
explicit-wins-over-alias, and an end-to-end wiring test through managed.NewClient.

Related #701
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 17, 2026

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 231497a
Status: ✅  Deploy successful!
Preview URL: https://06cabd46.mcpproxy-docs.pages.dev
Branch Preview URL: https://mcp-2769-forward-proxy-env.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: mcp-2769-forward-proxy-env

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (14 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (25 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (21 MB)
  • installer-dmg-darwin-arm64 (19 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 27687831433 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

🤖 Codex review — REQUEST_CHANGES (1×P2, security)

[P2] Redact schemeless proxy credentials before forwarding — internal/secureenv/manager.go:348-350

When forward_proxy_env is enabled and the ambient proxy uses the common schemeless form user:pass@proxy.example.com:8080, url.Parse treats user as the scheme and leaves u.User nil, so the redaction path returns the original value and forwards the credentials — defeating the redaction guarantee for users whose proxy vars omit http:///https://. Add a fallback that detects userinfo (...@... before the host) without a URL scheme and redacts it before appending to upstream env.

Reviewed via codex exec review --base origin/main on e72ff2f6.

…2769)

Codex PR #704 review (P2, security): redactProxyCredentials missed schemeless
proxy values like "user:pass@proxy:8080" (no scheme). url.Parse reads "user" as
the scheme and leaves Userinfo nil, so the original credential-bearing value was
forwarded to upstreams despite the redaction guarantee.

Fix: when the value has no "://" but contains "@", re-parse with a dummy
"http://" scheme so url.Parse recognizes the userinfo, strip it, then remove the
dummy scheme. An "@" inside a path (host/path@x) keeps Userinfo nil and is left
intact. Adds table cases (schemeless user:pass / user-only / no-userinfo /
at-in-path) and an end-to-end forwarding test asserting no creds reach upstream.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

Fixed in 231497a — thanks, this was a real leak.

redactProxyCredentials now handles schemeless values: when the value has no :// but contains @, it re-parses with a dummy http:// scheme so url.Parse recognizes the userinfo, strips it, then removes the dummy scheme. An @ inside a path (host/path@x) keeps Userinfo nil and is left intact.

Tests added:

  • redactProxyCredentials table: user:pass@proxy.example.com:8080proxy.example.com:8080, user@host:3128host:3128, schemeless-no-userinfo unchanged, at-in-path unchanged.
  • End-to-end TestBuildSecureEnvironment_ForwardsSchemelessProxyRedacted: HTTP_PROXY=user:pass@proxy.example.com:8080 forwarded with no @/pass in the value.

go test ./internal/{secureenv,config,upstream}/... -race and golangci-lint v2.5.0 both green.

@Dumbris

Dumbris commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

✅ Review (Claude Code, Codex rate-limited) — ACCEPT

Rigorous substitute review while Codex is rate-limited (per maintainer direction).

Previously-flagged P2 (schemeless credential redaction) — CONFIRMED RESOLVED. redactProxyCredentials (internal/secureenv/manager.go:340-372) uses a two-stage approach (url.Parse, then a dummy-scheme re-parse for schemeless user:pass@host:8080). Empirically verified across 14 edge cases: IPv6 (user:pass@[::1]:8080), @-in-password, percent-encoded creds, query/fragment, empty user/pass → creds stripped, host preserved; host/path@x, NO_PROXY lists, *, empty → left intact.

Opt-in correctness ✓ForwardProxyEnv defaults false; gated at manager.go:286 + client.go:184-188; proxy vars explicitly excluded from the default allowlist (comment cites the #701 lesson). stdio-only. Explicit config wins (forwarding runs last, suppressed if any case-spelling already present).

Security ✓ — no raw-value logging, no un-redacted forward path. Tests ✓proxy_forward_test.go + secure_env_integration_test.go cover schemeless creds, default-off, NO_PROXY, case-alias precedence; go test ./internal/secureenv/... ./internal/upstream/... green (incl. -race); go build ./... exit 0.

Non-blocking (optional hardening, not gating): a proxy value with surrounding whitespace (" http://user:pass@host ") is returned unchanged because url.Parse fails on the leading space and the :// guard skips the schemeless branch — a strings.TrimSpace at the top of redactProxyCredentials would close it. Extremely unlikely in practice; filed as a note, not a blocker.

VERDICT: ACCEPT

@mcpproxy-gatekeeper mcpproxy-gatekeeper 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.

Approved via Claude Code review (Codex quota exhausted).

@Dumbris Dumbris merged commit 0521e90 into main Jun 17, 2026
45 checks passed
Dumbris added a commit that referenced this pull request Jun 18, 2026
…P-2776) (#705)

Follow-up to PR #704 (MCP-2769) reviewer note: redactProxyCredentials did not
trim surrounding whitespace, so a value like "  http://user:pass@proxy  " made
url.Parse error (leading space) and fall through, forwarding the credentialed
value verbatim and bypassing the redaction guarantee.

Trim whitespace at function entry before parsing; whitespace is never meaningful
in a proxy URL. Adds table cases for whitespace-wrapped scheme'd and schemeless
credentialed values.

Co-authored-by: Paperclip <noreply@paperclip.ing>
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