MCP session, package-index path, URL-parser, and generated-config boundaries¶
Five July 29 disclosures expose reusable boundaries where a client chooses a privileged backend, stateless transport loses caller identity, a package index controls a download path, two URL parsers disagree on authority, or line breaks turn a generated configuration value into executable structure.
Sources:
- GHSA-5h6c-295f-7pp3 / CVE-2026-16328, GHSA-6c5r-pj95-xvqv / CVE-2026-16326, and HashiCorp HCSEC-2026-24: Consul MCP backend-address override and stateless cross-client credential reuse;
- GHSA-qwm4-qh6w-59xr / CVE-2026-13346, the Python security announcement, and pip pull request 14110: doubly encoded index URLs can select an absolute download path;
- GHSA-mvrj-5wv7-cfg9 / CVE-2026-67201, the V issue, and fix commit 85859f0:
net.urllibandnet.httpdisagree on a backslash-bearing URL authority; and - GHSA-c2fc-3q38-9pf4 / CVE-2026-12357 and ZDI-26-447: authenticated Heimdall Data
generateFileContentinput can inject CRLF into generated content and reach root-context execution.
Authorized validation only
Use a disposable Consul cluster with fake ACL tokens, two synthetic MCP clients, an owned package index, temporary sibling directories, two local URL listeners, and a non-executing configuration parser/recorder. Never relay real Consul credentials, query metadata or internal services, overwrite user or system files, serve executable packages, or let generated content reach a production root process.
Boundary map¶
| Input | Trusted transition | Bounded positive evidence |
|---|---|---|
| MCP backend override | connected client selects the authority receiving server-configured Consul API requests | owned listener receives a marker request and a fake token reserved for the fixture |
| stateless MCP request | transport/session lookup selects another client's cached authenticated Consul client | client B's inert tool call is recorded under client A's canary identity |
| package-index URL | decoded URL path becomes the local download destination | a fixed marker is written to a disposable sibling path during pip download --only-binary |
| backslash-bearing URL | allowlist parser host becomes HTTP client's socket authority | validator logs listener A while the final connection reaches owned listener B |
| CR/LF-bearing configuration scalar | scalar becomes additional generated directives or executable configuration | offline parser sees one extra inert marker directive absent from the input model |
Keep these edges separate. A callback is not internal-service access; another client's identity at a recorder is not data theft; an outside-root download is not execution; parser disagreement is not SSRF until the final socket changes; and extra generated syntax is not root execution unless the affected privileged consumer actually accepts it.
Consul MCP authority and session-isolation matrix¶
HashiCorp states that consul-mcp-server 0.1.0 through 0.1.3 accepted per-request Consul-address overrides and could forward its configured token to the selected destination. In stateless mode, its authenticated Consul-client cache could also reuse one client's token for another client. Version 0.1.4 fixes both issues. Credential forwarding requires a configured server token; cross-client reuse requires stateless mode and more than one connected client.
Two-client, two-backend fixture¶
- Run a disposable Consul MCP server against backend A. Give A a fake ACL token that authorizes only one marker KV read or, preferably, replace Consul with a recorder that maps each fake token to a synthetic identity.
- Connect clients A and B with distinct transport/session identifiers and distinct canary tokens. Record client ID, session ID, transport mode, requested backend, selected backend, token fingerprint, and tool result identity.
- For backend authority, vary only the client-controlled address field across configured A, owned listener B, malformed authority, userinfo, alternate port, redirect, hostname case/trailing dot, and controlled DNS-answer rows. B must record only request metadata and a known fake token.
- For session isolation, alternate A and B requests, reverse connection order, reconnect one client, omit a session identifier, duplicate an identifier, and run requests concurrently. Test stateful and stateless transports as separate rows.
- Repeat on 0.1.4. A client must not override the configured backend, and every tool call must use only the credential bound to its authenticated session.
Report the narrow result as either client-selected backend -> server sends a fake configured credential to owned listener B or client B request -> server executes the inert operation with client A's synthetic identity. Preserve the deployment preconditions instead of generalizing either result to every MCP transport.
pip package-index URL to filesystem destination¶
The Python announcement says a malicious package index can supply a doubly encoded package URL that pip resolves to an arbitrary location on disk, including when the selected artifact is a wheel. A malicious package by itself is not sufficient; the index response must control the URL. The material risk called out by Python is pip download --only-binary, because installing source distributions from an untrusted index already permits build-time code execution.
Disposable index/download fixture¶
- Create a temporary virtual environment, download directory, sibling canary directory, and owned PEP 503-compatible index. Serve one inert wheel containing only static metadata and a marker text file; do not define build hooks or importable test code.
- Capture the index href as raw bytes, each URL-decoding stage, resolved filename, staging path, final destination, and checksums before and after the run.
- Establish a baseline with an ordinary relative wheel URL. Then test single-encoded, doubly encoded, mixed-case encoding, absolute-path, encoded separator, sibling-prefix, query/fragment, redirect, and platform-specific separator rows one at a time.
- Run only a bounded command such as
python -m pip download --only-binary=:all: --no-deps --dest "$TMP/downloads" --index-url "$OWNED_INDEX" canarypkg. PointTMPat a disposable tree and run as an unprivileged test user. - Require a real outside-destination marker write for a positive affected-build result. A decoded string that merely looks absolute is not enough. Repeat against the fixed pip build identified by the upstream advisory/patch and require rejection or confinement under the selected download directory.
Do not target shell startup files, package-manager configuration, credentials, service units, or executable search paths. Report malicious index href -> pip's final file write escapes the chosen download directory and separately state whether later execution was tested; it should not be needed for this proof.
V URL parser-to-connection differential¶
The V disclosure covers versions through 0.5.2 and says commit 85859f0 fixes a differential in which net.urllib.parse() extracts an allowlisted host from a URL containing a backslash in the authority while net.http.get() normalizes the same input and connects to a different host.
- Run listeners A and B on loopback aliases or an isolated test network. A is allowlisted; B is explicitly unapproved but owned. Both record only connection count, method, authority, path, and a random marker.
- Pass the exact same raw URL bytes first to the application's validator and then to its real HTTP call. Log parsed scheme, userinfo, host, port, serialized request target, DNS answer, socket destination, redirect hops, and
Hostheader. - Compare slash and backslash placement around userinfo and authority delimiters, percent-encoded separators, mixed separators, trailing dots, hostname case, explicit ports, and fragments. Keep redirects disabled for the core parser test, then assess them separately if the integration follows them.
- A positive row requires the validator to approve A while the final socket reaches B. Merely producing different parsed strings without a connection is incomplete evidence.
- Repeat on the fixed commit or a release containing it. Validation and connection must derive authority from one canonical representation, and ambiguous backslash-bearing authority forms should fail closed.
Stop at B's marker response. Never substitute metadata, loopback admin panels, RFC1918 services, corporate DNS, or VPN-only applications for the owned second listener.
Generated configuration CRLF boundary¶
ZDI reports that an authenticated, high-privilege attacker can pass CRLF sequences through Heimdall Data Database Proxy's generateFileContent path and ultimately execute code as root; release build 25.03.01.24 is fixed. Public details do not identify the exact field or generated grammar, so first map caller role, endpoint, controllable scalar, generated file, parser, and privileged consumer without guessing an exploit payload.
- Clone the affected behavior into an isolated fixture or instrument an approved lab appliance so generated content is diverted to a temporary file and never loaded by the production service.
- Establish a baseline scalar and capture the structured input model plus exact generated bytes.
- Vary CR, LF, CRLF, repeated line endings, leading/trailing whitespace, comment markers, quotes, continuation characters, and a harmless
CANARY_DIRECTIVE=1line. Test one field at a time. - Feed output only to a syntax parser or no-op consumer that records directive names. Do not invoke shells, interpreters, service reloads, hooks, or root-owned production paths.
- Confirm affected behavior only when one input scalar creates an additional parsed directive absent from the caller's model. Repeat on 25.03.01.24 and require line endings to be rejected or encoded so the marker remains scalar data.
The safe result is authorized scalar control -> generated bytes contain extra syntax -> offline consumer recognizes one inert marker directive. Cite ZDI for the reported root-context impact, but do not claim your fixture achieved root execution unless a separately approved lab test proves that final consumer edge.
Evidence and reporting¶
Preserve exact versions and transport modes, raw address/URL/config bytes, every decoding and parser representation, selected credential fingerprints, final socket/file destinations, client/session ordering, generated bytes, parser output, and affected-versus-fixed decision tables. Redact even fake-looking values if they could be confused with live tokens. Name the crossed boundary precisely: client to backend authority, session to credential identity, index URL to file destination, validation parser to socket authority, or scalar to generated grammar.
July 30 MCP Ruby transport follow-up¶
Two Ruby SDK advisories add complementary HTTP-transport checks:
- GHSA-5p9g-j988-pcwv / CVE-2026-67431: Streamable HTTP and SSE accepted a known session ID without proving that the request came from the principal that created the session; and
- GHSA-rjr6-rcgv-9m7m / CVE-2026-63118:
StreamableHTTPTransportdid not enforceHostorOrigin, leaving a loopback/LAN server exposed to browser-origin and DNS-rebinding trust confusion.
The affected mcp gem range is through 0.22.0; 0.23.0 is the fixed negative control. Session-ID acquisition is a separate precondition. Do not imply that the ID is guessable or disclosed merely because ownership is not checked.
Two-principal session-ownership fixture¶
- Run the exact Ruby SDK build with one inert recorder tool. The tool must accept a random marker and record principal, transport, session ID fingerprint, and arguments without performing any external action.
- Create principals A and B through the real application authentication middleware. Let A initialize a session and obtain its synthetic session ID through normal lab instrumentation.
- Keep the ID constant while varying the request principal: A, B, anonymous, expired A, and A after reauthentication. Test Streamable HTTP and SSE independently.
- Send one recorder-only tool call per row. Capture the HTTP request identity, SDK-selected session owner, dispatch decision, and which stream receives the response.
- Repeat on
0.23.0. B and anonymous requests carrying A's ID must be rejected before dispatch; A's valid request should remain the positive control.
Strong evidence is principal B request + A's known lab session ID -> recorder dispatch occurs under A's session. A response arriving on A's SSE stream helps establish routing behavior but is not required to run a dangerous tool. Never obtain IDs from production logs, browser storage, traffic, or another user's session.
Browser-origin and Host decision matrix¶
Use a browser harness, loopback-only Ruby MCP server, fake origin, and an inert tool. Keep DNS and all listeners inside an isolated lab; do not rebinding-test a workstation that has real filesystem, shell, cloud, or credential MCP tools loaded.
| Request row | Host | Origin | Expected result |
|---|---|---|---|
| configured local client | approved authority | absent or explicitly approved | accepted |
| foreign browser origin | approved authority | unapproved owned origin | rejected before session creation |
| Host confusion | unapproved owned authority | approved origin | rejected |
| rebinding simulation | owned name whose lab DNS answer changes to loopback | matching foreign origin | rejected |
| malformed/multiple | duplicate, absolute-form, or forwarded authority canaries | null-like or duplicate origin canaries | rejected or canonicalized consistently |
Record browser preflight behavior separately from the actual JSON-RPC request. CORS headers alone are not the DNS-rebinding control: after rebinding, browser same-origin logic may accept the attacker's name while the socket reaches loopback. A complete positive requires foreign browser-controlled authority/origin -> local MCP transport accepts request -> inert recorder tool dispatches. Stop there; do not list real tools or read their output.
Report session ownership and browser-origin reachability as separate boundaries. Include gem version, Rack middleware, bind address, proxy chain, transport, effective principal, session fingerprint, raw Host/Origin, DNS answer, final peer, recorder count, and the 0.23.0 negative-control result.