Intelligence

The Caller Was Remote. A Header Made It Local Enough to Rewrite the Agent's MCP Configuration.

IBM's advisory for CVE-2026-9186 states that Langflow OSS 1.0.0 through 1.11.2 let a caller supplied X-Forwarded-For header satisfy a loopback only gate on the route that writes MCP server entries into a developer's Cursor, Windsurf or Claude configuration file. This record independently read the affected route at three tags and used GitHub's own compare tool to confirm what Langflow's own later pull request states in prose: the originally reported bypass was already closed by a July commit contained in every one of the versions IBM's advisory names as affected, and a narrower, distinct bypass survived one release cycle longer under a specific proxy configuration. Both the fix and the vendor's own affected range are real; they do not describe the same window.

Event analysed: . This analysis was published on 6 September 2026.

When a caller supplied network origin header let a remote, authenticated caller satisfy a check built to admit only the local machine's own operator, whose authority was actually being exercised to rewrite that operator's own coding agent configuration?

Nobody's, and that is the finding worth keeping past the patch, along with the fact that the patch and the vendor's own stated affected range describe two different windows. IBM published CVE-2026-9186 on 4 September 2026 against Langflow OSS, CVSS 3.1 6.5 under CWE-284, Improper Access Control, stating that versions 1.0.0 through 1.11.2 are affected and recommending an upgrade to 1.11.3. IBM's own advisory page was blocked to direct fetch in every attempt this record made, and no GitHub Security Advisory mirroring this specific CVE could be located against langflow-ai/langflow, so this record treats IBM's own CVSS score, CWE classification and precise affected range as the vendor's stated claim rather than as independently confirmed by a direct read of the primary advisory. What this record could, and did, verify directly is the mechanism, the fix and the version chronology beneath that claim. Langflow's POST /api/v1/mcp/project/{project_id}/install route, read directly from the current source, requires an authenticated user and additionally checks that the caller's resolved IP address is a loopback address before it will write an MCP server entry into a Cursor, Windsurf or Claude configuration file already present on the host, merging a new entry into that client's own mcpServers JSON. Read directly at the v1.10.2 tag, the function resolving that IP unconditionally read the X-Forwarded-For header and returned its first, leftmost entry, with no operator controlled setting gating that behaviour at all, so a remote, authenticated caller supplying X-Forwarded-For: 127.0.0.1 satisfied the loopback check. Langflow's own pull request 13530, merged 13 July 2026 as commit 1641b28f33e2c47b9a0c6855922d44d1b8418b9d, rewrote that function to default to the real TCP peer address and to consult X-Forwarded-For only when an operator has explicitly enabled a trusted proxy setting, taking the rightmost entry in that case. This record independently confirmed, using GitHub's own compare tool rather than relying on pull request prose alone, that this exact commit is contained in the v1.11.0, v1.11.1 and v1.11.2 tags, the same three versions IBM's own advisory lists as affected for the CVE it published two months later. A subsequent pull request, 14425, merged 7 August 2026, states in its own body that verifying the originally reported proof of concept found it already fixed by the July commit and already present in 1.11.0 through 1.11.2, and documents a separate, narrower bypass surviving on the pre release state of the 1.11.3 line: a web framework detail meant that only the first of several repeated X-Forwarded-For header lines was read, and a proxy that appends a new line rather than extending the caller's own, such as HAProxy's forwardfor option, could let an attacker supplied loopback value still be read as the caller's effective address, when an operator had explicitly enabled the trusted proxy setting. This record read the shipped fix directly and confirmed it joins every occurrence of the header before selecting the correct entry, closing that specific gap, and confirmed that Langflow's own v1.11.3 release already lists this exact fix among its included changes. The same pull request states, and this record preserves rather than resolves, that its fix does not address a deeper case: behind a same host reverse proxy, the caller's TCP peer address is loopback for every remote user by the ordinary mechanics of that topology, with no header involved and nothing spoofed, so a check reading only the TCP peer cannot distinguish that architecture from a genuinely local operator. IBM's own stated affected range is accordingly broader than this record's own independently verified evidence supports for the originally reported bypass specifically; a distinct, narrower bypass, requiring an explicit and non default proxy trust configuration, did affect Langflow for a shorter window this record could confirm ends before the 1.11.3 release shipped. This record does not resolve which of the two mechanisms IBM's own CVE number is formally scoped to describe, since IBM's own page could not be read directly, and states the disagreement rather than adopting either account silently.

Read Langflow's own local only MCP configuration installer and the design intent is plain: a route that writes into a developer's own Cursor, Windsurf or Claude configuration file, adding an entry that tool will run the next time that IDE starts, should only ever fire for the person sitting at that machine. IBM's advisory for CVE-2026-9186 states that a caller could satisfy the check meant to enforce exactly that, from somewhere else entirely. This record is about what the check actually measured, what independently verifying the fix's own chronology against the vendor's stated affected range shows, and what Langflow's own later pull request states plainly that neither fix solves.

What IBM's advisory states

IBM published CVE-2026-9186 on 4 September 2026, naming Langflow OSS as the affected product. IBM's own page states a CVSS 3.1 score of 6.5 under vector AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N, classified under CWE-284, Improper Access Control, and states that versions 1.0.0 through 1.11.2 are affected, recommending an upgrade to 1.11.3. This record attempted to fetch IBM's own page directly and was blocked by this session's network egress policy on every attempt, and a search for a GitHub Security Advisory mirroring this specific CVE against langflow-ai/langflow returned nothing. IBM's own CVSS score, CWE classification and precise affected version range are accordingly recorded here as the vendor's stated claim rather than as independently confirmed by a direct read of the primary advisory, the same evidentiary distinction this desk has already applied elsewhere when a primary advisory page sat behind a block this session's proxy enforced.

What this record treats as independently established rather than merely reported: the exact route, its authentication requirement, its locality check, the file it writes and to what targets, the vulnerable pre fix code, the fixed code, the residual variant's own fix, and the containment of the fixing commit in the versions IBM's advisory names as affected, all read directly from Langflow's own public source at three tags and confirmed using GitHub's own compare tool. IBM's own advisory page at ibm.com was not directly readable in this session.

What the installer actually does

Langflow's install_mcp_config handler, read directly from the project's current source, is registered at POST /api/v1/mcp/project/{project_id}/install and depends on CurrentActiveMCPUser, so the route requires an authenticated Langflow user, not an anonymous caller. Before doing anything else, the handler resolves the caller's IP address through a function this record calls get_client_ip and checks it against a loopback test; failing that check raises an HTTP error stating plainly that MCP configuration can only be installed from a local connection. Passing it, the handler builds a command, uvx mcp-proxy pointed at the project's own SSE or streamable HTTP endpoint, or a mcp-composer invocation where that mode is enabled, resolves a configuration file path for whichever client name the caller supplies among a small set (Cursor, Windsurf, Claude), creates the parent directory if needed, reads any existing JSON at that path, removes stale entries pointing at the same project URL, and writes a merged mcpServers object back to disk. The path itself is resolved through a lookup keyed to the client name rather than accepted as an arbitrary caller supplied filesystem path; a caller cannot name an arbitrary destination file, only one of the handful of client configurations the lookup knows about. This record did not establish, and does not claim, that any of Cursor, Windsurf or Claude automatically reloads a configuration file changed on disk without the user reopening or restarting the application; the verified, immediate effect of a successful call is the host side configuration write itself, not a demonstrated subsequent tool execution inside the IDE, and this record states that boundary explicitly rather than assuming a specific client's own reload behaviour.

The original bypass, read directly

This record fetched the project's source directly at the v1.10.2 tag, predating the fix below. The function resolving the caller's IP for this check read the X-Forwarded-For header unconditionally when present, split it on commas and returned the first entry, with no setting anywhere gating whether that header was consulted at all. A remote, authenticated Langflow user could therefore send X-Forwarded-For: 127.0.0.1 alongside a genuine, non local connection, and the loopback check would pass on the strength of a value the caller supplied and fully controlled. Nothing about this path required the caller to actually be local, to route through any proxy at all, or to satisfy any condition beyond setting one request header.

The fix, and what independently verifying its containment shows

Langflow's own pull request 13530, described in its own title as security hardening across multiple surfaces and merged 13 July 2026 as commit 1641b28f33e2c47b9a0c6855922d44d1b8418b9d, rewrote the IP resolution function this check depends on. This record read the rewritten function directly at the current source, where its own docstring states the intent explicitly: default to the real TCP peer, request.client.host, and consult X-Forwarded-For only when an operator has explicitly set a trusted proxy option, taking the rightmost entry once that option is enabled, since the rightmost entry is the one a trusted intermediary itself appends rather than one a caller can set directly.

Rather than accept a later pull request's own prose as settling which released versions contain this fix, this record independently checked containment using GitHub's own compare tool, comparing each of the v1.11.0, v1.11.1 and v1.11.2 tags directly against the fixing commit. Each comparison returned the same result: the tag is up to date with all commits from that commit, meaning the fixing commit is already part of that tag's own history, with nothing left to compare. All three of the versions IBM's own advisory lists as affected already contained this fix, verified directly against the repository's own commit graph rather than taken from either vendor's prose.

IBM's advisory states versions 1.0.0 through 1.11.2 are affected by CVE-2026-9186. This record's own independently verified commit containment shows the fix for the originally reported bypass, the unqualified leftmost X-Forwarded-For read described above, already present in v1.11.0, v1.11.1 and v1.11.2. Both facts are evidenced directly; they describe different things. This record does not choose one account over the other. It states plainly that IBM's own stated affected range, if read as describing the mechanism this record could verify, extends further than the verified commit history supports, and that a second, narrower mechanism, described next, did affect a version of Langflow inside that same numbered line for a shorter window this record can also independently bound.

The residual variant on the 1.11.3 line

Langflow's pull request 14425, titled in its own words as joining repeated X-Forwarded-For lines before taking the last hop and merged 7 August 2026 as commit 425c4cb36ec3430145c0c12ce7f27fc50db2954e, states in its own description that verifying the originally reported bypass found it already fixed by the July commit above, contained in 1.11.0 through 1.11.2 as this record separately confirmed, and separately backported to a 1.10.3 release the pull request names but this record did not itself independently verify. The same pull request documents a distinct, narrower bypass found while doing that verification, present on what its own description calls the release-1.11.3 branch: the web framework Langflow uses returns only the first of several repeated occurrences of a header when a request carries more than one X-Forwarded-For line, rather than joining them, and a proxy that appends a new line instead of extending the caller's own line, HAProxy's own forwardfor option being the example the pull request names, could leave an attacker supplied first line, 127.0.0.1, in the position the rightmost-entry logic above still read as authoritative, if the caller placed it first and the proxy's own line landed second. The pull request states, and this record separately confirms by reading the shipped code, that this required an operator to have explicitly enabled the same trusted proxy setting the July fix introduced; the setting defaults to false, corroborated here by the shipped test suite's own naming and assertions rather than by an independent read of the setting's own literal default declaration, which this record could not locate in the source it read. The pull request also states, and this record independently confirmed by reading a proxy commonly used the other way, that nginx's own $proxy_add_x_forwarded_for configuration produces a single joined line rather than a repeated one, so this specific variant is scoped to a proxy configuration that appends rather than extends the header, not to every reverse proxied deployment.

This record read the shipped fix directly. A function this record calls get_last_forwarded_for_hop now reads every occurrence of the header the request carries, joins them in order, and selects the rightmost non empty entry from the joined result, closing the specific gap the repeated-line case opened. Langflow's own v1.11.3 release, published 11 August 2026 per the project's own releases page, lists exactly this change, joining repeated X-Forwarded-For lines before taking the last hop, among its own included fixes, four days after the pull request that shipped it merged. Read together, the evidence supports a narrower, more precise account than IBM's stated range on its own: the originally reported, unqualified leftmost read bypass was closed as of v1.11.0; a separate, narrower bypass requiring an explicit, non default trusted proxy configuration against a header appending proxy affected the pre release state of the 1.11.3 line specifically, and was closed before that version shipped.

What neither fix solves

Pull request 14425 states directly, in language this record treats as an upstream acknowledged design limitation rather than as a remaining bug in either shipped fix, that its own patch does not address a deeper case. Behind a same host reverse proxy, one running on the same machine as the Langflow server itself, with the trusted proxy setting left at its default of false, the TCP connection the Langflow process actually observes originates from the proxy, at loopback, for every remote user the proxy forwards a request from. No header is involved in that case at all, and there is nothing to spoof: request.client.host reads 127.0.0.1 honestly, because that is genuinely where the TCP connection to the Langflow process originated. A check that reads only the TCP peer, the corrected default behaviour both fixes rely on, cannot distinguish that topology from a request that actually originated at the console of the machine running Langflow. This record does not claim that every Langflow deployment sitting behind a same host reverse proxy is presently exploitable through this route; no source available to it states that this specific topology has been observed granting install access to a remote caller in a real deployment, and this record does not manufacture that claim from an acknowledged design limitation alone. What the acknowledgment establishes, on its own terms, is narrower and more durable than any one bypass: a locality check answers a topological question, not an authorization question, and the two remain distinguishable only for as long as nothing legitimate sits between the caller and the server producing the identical, correctly observed, unfalsified signal a genuine local operator would also produce.

Why this is not, at bottom, a story about Langflow

Every one of the three failure modes this record traces, an unqualified read of a caller supplied header, a header reading function that missed a repeated line under an explicitly opted in trusted proxy configuration, and a same host reverse proxy producing an honest but misleading TCP peer address, shares one structural feature: each treats a network attribute, whether caller supplied, proxy asserted, or a literal but topology dependent socket address, as though it were the same kind of fact as an authorization grant. It is not. An authenticated principal, a network peer, a value a forwarding chain reports, a trusted intermediary's own assertion, and the specific principal a policy actually entitles to perform a consequential, host local action are five separately traceable things. Langflow's own two fixes narrow which of those five a caller can manipulate directly, first by defaulting to the TCP peer rather than an unconditionally trusted header, then by correctly joining a header a caller and a proxy might jointly, if inadvertently, misrepresent between them. Neither fix, and none available to this record, establishes a way to answer the fifth question, whether the specific principal now satisfying the locality check is the principal a local only route was ever meant to admit, once any intermediary at all, malicious, misconfigured or simply present by ordinary deployment topology, sits on the path.

Where this sits in the pattern

This desk has already read a Grafana MCP server's own network position, once a caller could redirect its outbound requests, as authority a caller had not independently earned, and this record's own new AEW, Network locality is treated as a higher-authority principal class, is deliberately kept distinct from that reachability weakness rather than folded into it: Grafana's own advisory concerns a server's outbound reach into a destination nothing independently authorized, while this route's gate exists specifically to discriminate an inbound caller by network position, and fails because the signal it reads does not reliably measure what it claims to. LiteLLM's own MCP authentication handler converted a failed credential check into a fabricated, accepted identity, a different mechanism again: that check ran, failed, and was overridden afterward by a fallback; Langflow's own locality check never failed at all, it succeeded on input that was never a reliable measure of the fact it was checking. OGX's own Responses API let a caller name the destination of the server's own outbound MCP session with no independent check on where that session actually connected, a further instance of the same reachability substitution this record keeps separate from its own new weakness for the same reason as Grafana's. The broader argument this desk has made since its own launch, that technical admission and execution authority are not the same fact, holds here in a form specific to network topology: an authenticated Langflow user was, in every documented case here, technically admitted to call the install route at all; what neither the caller's authentication nor the caller's apparent network position ever established, on the evidence available to this record, is that the specific principal calling was the one the route's own local only design was built to admit.

The Authority Provenance ledger

Moona Intelligence separates what the documented mechanism technically permitted from what the available evidence establishes about who was entitled to grant that permission, the same discipline this desk applies to every intermediary and every locality gate it reviews.

Authority grantor. The operator who deploys Langflow, decides whether the machine or process running it sits behind any reverse proxy, and decides whether to enable rate_limit_trust_proxy. Neither IBM's advisory nor either pull request names an organizational principal, distinct from that operator, who separately decided a remote, authenticated Langflow user should be able to satisfy the install route's own local only gate.

Network peer. Whatever TCP connection actually reached the Langflow process: the caller's own connection directly, in the absence of any proxy, or a proxy's own connection to Langflow when one sits in front of it, in which case the peer this record calls the network peer is the proxy itself, not the original caller.

Reported origin. Whatever a forwarding chain, most often an X-Forwarded-For header, claims about the request's original source, a value this record's own reading confirms was, before the July 2026 fix, read unconditionally and, after that fix, read only once an operator had explicitly opted a trusted proxy in.

Trusted intermediary. Whichever proxy, if any, an operator has configured Langflow to trust through rate_limit_trust_proxy; before that setting existed in its current form, nothing distinguished a genuinely trusted intermediary's own asserted value from a caller's own unmediated claim.

Topological locality. Whether the network peer, as observed by the Langflow process itself, is a loopback address; documented directly, and independently confirmed by this record's own reading of the current source, as the sole fact the install route's locality gate actually tests, once the July and August 2026 fixes are both applied.

Authorization principal. The specific principal a local only, host side configuration write was ever meant to admit: whoever sits at the console of the machine running Langflow. Nothing available to this record describes a mechanism, in any version examined, that independently verifies this principal is the same as whichever principal happens to satisfy the topological locality test at a given moment.

Caller identity. An authenticated Langflow user in every mechanism this record traces; IBM's own CVSS vector specifies PR:L, low privileges required, consistent with that reading. Authentication to Langflow itself is not in question in any of the three mechanisms; what is in question is whether that authenticated caller also occupied the network position the route's own design assumed.

Failed limit. Before the July 2026 fix, no operator controlled setting gated whether X-Forwarded-For was consulted at all, and the function read its first, unqualified entry. After that fix and before the August 2026 fix, on a deployment with rate_limit_trust_proxy explicitly enabled against a header appending proxy, the function read only the first of several repeated header lines rather than joining them. After both fixes, on a deployment sitting behind a same host reverse proxy with rate_limit_trust_proxy left at its default, the function correctly reads the TCP peer, and that peer is honestly loopback for every remote caller the proxy forwards, a limit neither fix expresses itself as solving.

Inherited assumption. That a network attribute, however it was produced, could stand in for a verified answer to who the caller actually was relative to the machine running Langflow, an assumption each of the three mechanisms traced here satisfies differently and none of them resolves completely.

Challenge authority. An authenticated session with Langflow itself, required in every mechanism this record traces; no source available to it describes an additional, independent check verifying that an authenticated session also corresponds to a locally present operator, beyond the network attribute tests this record has already traced.

Revocation or modification. Undocumented in the material available to this record as a distinct mechanism from a code fix. Neither pull request describes a way to revoke or audit an MCP configuration entry already written to a developer's own Cursor, Windsurf or Claude configuration file before either fix was applied to a given deployment.

Recovery path. A shipped code fix in each case: pull request 13530 for the originally reported bypass, contained in v1.11.0 and independently confirmed by this record as also contained in v1.11.1 and v1.11.2, and pull request 14425 for the residual repeated header variant, shipped as part of v1.11.3. Neither fix, on the evidence available to this record, describes a remediation process for a configuration file that may have already been written on a given deployment before that deployment's own upgrade.

Exploitation. Unknown. No source available to this record states that either mechanism was exploited against a real deployment before its own fix shipped, and this record does not infer exploitation in the wild from a disclosed, patched CVE and an upstream pull request's own verification narrative.

Provenance evidence quality. Strong for the mechanism, the fix and the version chronology: this record read the vulnerable pre fix code, the fixed code and the residual variant's own fix directly from the project's public source at three tags, and independently confirmed commit containment using GitHub's own compare tool against the exact commit Langflow's own pull request names, rather than accepting that pull request's prose as sufficient on its own. Weaker for the formal CVE's own metadata: IBM's advisory page was blocked to direct fetch in this session on every attempt, no GitHub Security Advisory mirroring this specific CVE could be located, and this record's own attempt to independently confirm rate_limit_trust_proxy's literal default declaration in the settings source it read was unsuccessful, so that specific default is corroborated here through the shipped test suite's own naming and assertions rather than through a direct read of the declaration itself.

What remains unknown

This record does not establish, and states plainly that it cannot establish from the material available to it, exactly which of the two mechanisms traced here IBM's own CVE-2026-9186 is formally scoped to describe, since IBM's own advisory page could not be read directly in this session; the precise release dates of v1.11.0 and v1.11.1, cited here only approximately from the project's own releases listing; whether the 1.10.3 backport pull request 14425 itself names was independently verified by anyone other than Langflow's own maintainers; how many production Langflow deployments ran a vulnerable configuration before either fix, or sit today behind a same host reverse proxy with rate_limit_trust_proxy at its default; and whether either mechanism, or the acknowledged same host proxy limitation, was ever reached by a party other than the researcher or maintainer verifying it. Where the evidence does not establish a fact, this record states it as unknown rather than filling the gap with what the pattern would predict.

Sources

This analysis interprets third-party reporting, research and announcements. Moona is not the original reporter of the underlying events.

[2]
fix(security): harden multi-tenant surfaces (langflow-ai/langflow#13530)
GitHub (langflow-ai/langflow) · 13 July 2026 · Primary source
[3]
Commit 1641b28f33e2c47b9a0c6855922d44d1b8418b9d
GitHub (langflow-ai/langflow) · 13 July 2026 · Primary source
[5]
Commit 425c4cb36ec3430145c0c12ce7f27fc50db2954e
GitHub (langflow-ai/langflow) · 7 August 2026 · Primary source
[6]
mcp_projects.py at tag v1.10.2 (pre-fix)
GitHub (langflow-ai/langflow), raw source · 6 September 2026 · Primary source
[7]
mcp_projects.py at tag v1.11.3 (post both fixes)
GitHub (langflow-ai/langflow), raw source · 6 September 2026 · Primary source
[8]
rate_limit/service.py at tag v1.11.3
GitHub (langflow-ai/langflow), raw source · 6 September 2026 · Primary source
[9]
test_mcp_install_xff_trust.py at tag v1.11.3
GitHub (langflow-ai/langflow), raw source · 6 September 2026 · Primary source
[10]
Compare v1.11.0...1641b28f33e2c47b9a0c6855922d44d1b8418b9d
GitHub (langflow-ai/langflow) · 6 September 2026 · Primary source
[11]
Compare v1.11.1...1641b28f33e2c47b9a0c6855922d44d1b8418b9d
GitHub (langflow-ai/langflow) · 6 September 2026 · Primary source
[12]
Compare v1.11.2...1641b28f33e2c47b9a0c6855922d44d1b8418b9d
GitHub (langflow-ai/langflow) · 6 September 2026 · Primary source
[13]
Release v1.11.3, langflow-ai/langflow
GitHub (langflow-ai/langflow) · 11 August 2026 · Primary source

Protocol evidence

This record does not assess these architectures. The connection runs through the Risk Registry requirement each one bears on, and these published authority architectures are what the evidence says about that requirement.

Protocol evidence related through AEW-024 Network locality is treated as a higher-authority principal class

  • Supports requirement

    Agent Action Decision Protocol (AADP)

    Shamik Saha, individual submission to the IETF

    Requirement Revision 02 admits authenticated, digest bound evidence references the PDP dereferences and verifies itself, and never evidence claims as decision input

    A reported second revision of AADP is described to this dataset as requiring that a claim carried inside a request is not a trusted decision input merely because it appears there, and that only an authenticated reference the decision point itself dereferences and verifies may be admitted. Langflow's own pre fix locality check is a concrete, independently verified instance of exactly the failure that requirement answers: X-Forwarded-For is a value the caller's own request carries, and before commit 1641b28f33e2c47b9a0c6855922d44d1b8418b9d, Langflow's install route treated that carried claim as sufficient evidence of the caller's own network position, with no separate dereference or verification step standing between the claim and the decision it gated. The shipped fix narrows what may supply that value, defaulting to the literal TCP peer and requiring an explicit, operator configured trusted proxy opt-in before the header is read again, which moves the check closer to, without fully reaching, an authenticated reference a decision point verifies rather than an unmediated claim: this dataset could not independently read AADP's own revision 02 text, and grades this link as supporting the requirement from the weakness's own evidence rather than as a new known example of AADP's own specification.

    This record is the cited evidence for this relationship.

    View protocol evidence

Related Intelligence

All Intelligence Records →