high

CVE

CVE-2026-115419, CVE-2026-115883, CVE-2026-115886

CWE

CWE-488, CWE-706, CWE-862, CWE-346, CWE-918

Affected Surface

  • PyPI `strands-agents < 1.43.0` and npm `@strands-agents/sdk < 1.5.0` running multi-caller Agent2Agent (`A2AServer`) endpoints (`CVE-2026-115419` / `GHSA-4v6f-7vjh-6x4m`)
  • PyPI `langgraph-checkpoint-postgres < 3.1.3` and `langgraph-checkpoint-sqlite < 3.1.2` (`langgraph-checkpoint < 4.3.0`) when user- or tenant-influenced strings reach `get`, `search`, `delete`, `list_namespaces`, or `batch` (`CVE-2026-115883` / `GHSA-6hmw-9432-67qq`)
  • PyPI `fastmcp` and `fastmcp-slim` `>= 3.0.0, < 3.4.10` and `>= 4.0.0, < 4.1.2` using component `auth=` checks with transforms (`GHSA-7xgj-8392-5q4r`) or `ClientCredentialsOAuthProvider` / `PrivateKeyJWTOAuthProvider` (`GHSA-vqqp-x5qm-5qp4`)
  • PyPI `mlflow >= 3.12.0rc0, < 3.17.0` tracking servers with `trulens` and `trulens-providers-litellm` installed (`CVE-2026-115886` / `GHSA-2wqp-q3c6-ccj5`)

TL;DR

Between 22 and 23 October 2026, four major AI agent and evaluation frameworks (Amazon Strands Agents SDK, LangGraph, Prefect FastMCP, and MLflow) disclosed five vulnerabilities affecting multi-tenant session isolation, MCP tool authorization, OAuth client-credential binding, and tracking-server egress:

  1. Amazon Strands Agents SDK (CVE-2026-115419 / GHSA-4v6f-7vjh-6x4m, CVSS 8.1 High): A2AServer and StrandsA2AExecutor accepted a single Agent instance and reused its in-memory conversation state and streaming state across every caller’s Agent2Agent (A2A) context_id. Any caller could read another tenant’s conversation history or inject persistent prompt instructions into subsequent sessions.
  2. LangGraph Checkpoint Postgres & SQLite (CVE-2026-115883 / GHSA-6hmw-9432-67qq, CVSS 6.0 Medium): PostgresStore and SqliteStore serialize a namespace tuple ("tenant", "bob") by joining labels with a period (".".join(namespace) -> "tenant.bob"). BaseStore only rejected . inside labels on put() / aput(), leaving get(), search(), delete(), list_namespaces(), and batch() / abatch() (including PutOp items inside batch()) unchecked. Passing ("tenant.bob",) read, overwrote, or deleted ("tenant", "bob").
  3. Prefect FastMCP (GHSA-7xgj-8392-5q4r, CVSS 6.8 Medium, and GHSA-vqqp-x5qm-5qp4, CVSS 5.9 Medium): First, when a ToolTransform or custom Transform added tags or modified metadata that an AuthCheck (such as restrict_tag("admin", scopes=["admin"])) relied on, list_tools() hid the tool, but direct call_tool() / get_resource() lookups checked the pre-transform component or returned the transformed component without running _component_auth_allows(). Second, ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider never exposed or forwarded the issuer= parameter to the underlying MCP SDK, so a compromised or user-supplied MCP server could advertise its own OAuth token endpoint and capture the client’s client_id and client_secret.
  4. MLflow (CVE-2026-115886 / GHSA-2wqp-q3c6-ccj5, CVSS 6.5 Medium): The unauthenticated POST /ajax-api/3.0/mlflow/scorer/invoke endpoint deserialized third_party_scorer_data via Scorer.model_validate_json(), reflectively instantiating mlflow.genai.scorers.trulens classes and forwarding raw kwargs (api_base, base_url, api_key, completion_kwargs) straight into LiteLLM(), allowing unauthenticated SSRF with caller-controlled headers.

What to do now:

  • Upgrade strands-agents to >= 1.43.0 (@strands-agents/sdk to >= 1.5.0) and migrate A2AServer(agent=...) calls to A2AServer(agent_factory=lambda context_id: ...).
  • Upgrade langgraph-checkpoint-postgres to >= 3.1.3, langgraph-checkpoint-sqlite to >= 3.1.2, and langgraph-checkpoint to >= 4.3.0.
  • Upgrade fastmcp / fastmcp-slim to >= 4.1.2 (or >= 3.4.10 on 3.x), and explicitly pass issuer="https://..." to ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider.
  • Upgrade mlflow to >= 3.17.0.

Affected packages and projects

EcosystemPackageCVE / GHSASeverityAffected versionsFixed versionsAction required beyond upgrade?
PyPI / npmstrands-agents, @strands-agents/sdkCVE-2026-115419 (GHSA-4v6f-7vjh-6x4m)CVSS 8.1 Highstrands-agents < 1.43.0; @strands-agents/sdk < 1.5.01.43.0 (PyPI), 1.5.0 (npm)Yes (recommended): pass agent_factory= (agentFactory) to A2AServer so each context_id gets its own Agent and session_manager
PyPIlanggraph-checkpoint-postgres, langgraph-checkpoint-sqlite, langgraph-checkpointCVE-2026-115883 (GHSA-6hmw-9432-67qq)CVSS 6.0 Mediumlanggraph-checkpoint-postgres < 3.1.3; langgraph-checkpoint-sqlite < 3.1.2; langgraph-checkpoint < 4.3.03.1.3, 3.1.2, 4.3.0Custom external stores that join namespaces with . should call validate_op_namespace(op)
PyPIfastmcp, fastmcp-slimGHSA-7xgj-8392-5q4rCVSS 6.8 Medium>= 3.0.0, < 3.4.10; >= 4.0.0, < 4.1.23.4.10, 4.1.2No
PyPIfastmcp, fastmcp-slimGHSA-vqqp-x5qm-5qp4CVSS 5.9 Medium>= 4.0.0a1, < 4.1.24.1.2 (requires mcp >= 2.2.0)Yes: pass issuer="https://..." to ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider, and rotate secrets exposed to untrusted MCP URLs
PyPImlflowCVE-2026-115886 (GHSA-2wqp-q3c6-ccj5)CVSS 6.5 Medium>= 3.12.0rc0, < 3.17.03.17.0No (previously stored scorer rows with destination kwargs are also blocked on read)

Technical analysis

1. Strands Agents SDK (CVE-2026-115419): shared Agent state across A2A context_ids

In Amazon’s Strands Agents SDK (strands-agents on PyPI and @strands-agents/sdk on npm), A2AServer adapts a Strands Agent to the Agent2Agent (A2A) protocol. Before Python 1.43.0 (commit 398343faad) and TypeScript 1.5.0 (commit f8ba9376fe), A2AServer accepted a single agent: SAAgent instance and passed that same object into StrandsA2AExecutor.

Because a Strands Agent holds its conversation messages (agent.messages), system prompt state, and session manager on the instance itself (and StrandsA2AExecutor also stored _current_artifact_id and _is_first_chunk on self), every incoming A2A request mutated the exact same conversation history regardless of the request’s context_id:

  • Cross-tenant conversation disclosure: Caller B sending a request with a fresh context_id caused the model to run with Caller A’s prior turns still in agent.messages.
  • Cross-session prompt injection: Any caller could plant persistent instructions in the shared agent.messages list that steered tool calls and responses for subsequent callers.

The fix introduces agent_factory: Callable[[str], Agent] (agentFactory: (contextId: string) => InvokableAgent in TypeScript) alongside an LRU context map (max_contexts = 1000) where each context_id owns a dedicated _ContextEntry(agent, lock) and per-request _StreamState:

# strands-py/src/strands/multiagent/a2a/server.py (patched in 1.43.0)
from strands import Agent
from strands.multiagent.a2a import A2AServer

# DeprecationWarning if a single `agent=` is passed; use agent_factory for multi-caller isolation:
server = A2AServer(
    agent_factory=lambda context_id: Agent(
        model=my_model,
        tools=[...],
        # Wire any session_manager per context_id here
    ),
    max_contexts=1000,
)

If legacy code still passes a single agent= instance, 1.43.0 / 1.5.0 emits a DeprecationWarning, serializes all requests behind a single lock, and swaps take_snapshot() / load_snapshot() state onto and off the shared instance per context_id (provided no shared session_manager is attached). For real concurrency and multi-tenant isolation, switch to agent_factory.

2. LangGraph (CVE-2026-115883): dot-joined namespace collision in PostgresStore and SqliteStore

In LangGraph’s persistence layer, namespaces are passed as tuples of strings such as ("org_123", "user_456"). Both PostgresStore (langgraph-checkpoint-postgres) and SqliteStore (langgraph-checkpoint-sqlite) store that tuple in SQL as a single dot-joined prefix column (".".join(namespace) -> "org_123.user_456").

To prevent ("org_123.user_456",) from colliding with ("org_123", "user_456"), BaseStore._validate_namespace() forbade periods (.) in namespace labels. However, before commit bfcfea554e, BaseStore only called _validate_namespace() inside put() and aput():

  • store.get(namespace, key), store.search(namespace_prefix), store.delete(namespace, key), and store.list_namespaces(prefix=..., suffix=...) (plus aget, asearch, adelete, alist_namespaces) never validated the labels.
  • store.batch(ops) and store.abatch(ops) validated nothing at all, not even PutOp(namespace, key, value) items passed directly inside batch().

Consequently, if an application built a namespace tuple from a caller-supplied string tenant_id (for example namespace = (f"tenant_{user_input}",) or (user_input,)), passing "tenant_a.secret_ns" allowed the caller to:

# Against langgraph-checkpoint-postgres < 3.1.3 or langgraph-checkpoint-sqlite < 3.1.2:
store.get(("tenant_a.secret_ns",), "api_keys")          # reads ("tenant_a", "secret_ns")
store.delete(("tenant_a.secret_ns",), "api_keys")       # deletes ("tenant_a", "secret_ns")
store.batch([PutOp(("tenant_a.secret_ns",), "k", {})])  # overwrites ("tenant_a", "secret_ns")

Commit bfcfea554e adds _validate_namespace_labels() to get, search, delete, list_namespaces, and their async counterparts in langgraph-checkpoint 4.3.0 (before queueing into AsyncBatchedBaseStore._aqueue, so one caller’s invalid label cannot fail concurrent callers sharing the batch), and calls validate_op_namespace(op) inside _group_ops() in both PostgresStore (3.1.3) and SqliteStore (3.1.2):

--- a/libs/checkpoint-postgres/langgraph/store/postgres/base.py
+++ b/libs/checkpoint-postgres/langgraph/store/postgres/base.py
@@ -1386,6 +1387,7 @@ def _group_ops(ops: Iterable[Op]) -> tuple[dict[type, list[tuple[int, Op]]], int
     grouped_ops: dict[type, list[tuple[int, Op]]] = defaultdict(list)
     tot = 0
     for idx, op in enumerate(ops):
+        validate_op_namespace(op)
         grouped_ops[type(op)].append((idx, op))
         tot += 1
     return grouped_ops, tot

3. FastMCP (GHSA-7xgj-8392-5q4r and GHSA-vqqp-x5qm-5qp4): post-transform auth bypass and un-bound M2M OAuth client secrets

Prefect’s fastmcp 4.1.2 and 3.4.10 (commit 85df42fbf2) fix two distinct security flaws:

A. Direct component calls skipped post-transform AuthCheck (GHSA-7xgj-8392-5q4r)

FastMCP allows servers to attach component-level authorization rules (such as auth=restrict_tag("admin", scopes=["admin"])) and also attach transforms (ToolTransform, Transform) that rename components or add tags like tags={"admin"} at the provider, server, or session layer.

When a client called list_tools(), FastMCP ran all transforms first and then filtered the transformed list through _tool_auth_allows(tool), hiding the "admin"-tagged tool from unprivileged callers. However, on the direct lookup path (server.call_tool("tagged_tool"), get_resource(), get_prompt()), FastMCP checked authorization on the untransformed component or returned the transformed component from call_next without re-evaluating _component_auth_allows() after the transform pipeline finished. Furthermore, if a concrete resource (secret://data) was denied by its AuthCheck and a wildcard resource template (secret://{name}) existed on the same server or via a ProxyProvider, falling through on denial allowed the template to serve the protected URI anyway.

The fix in 85df42fbf2 re-runs _component_auth_allows() on the final transformed Tool, Resource, ResourceTemplate, and Prompt at every lookup layer, and ensures a denied concrete resource shadows any matching resource template at that URI.

B. M2M OAuth wrappers omitted issuer binding (GHSA-vqqp-x5qm-5qp4)

In FastMCP 4.0.0a1 through 4.1.1, ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider (fastmcp/client/auth/client_credentials.py) wrapped the upstream mcp Python SDK’s machine-to-machine OAuth providers, which discover the MCP server’s authorization server via RFC 9728 Protected Resource Metadata.

Although the underlying mcp SDK supported an issuer= parameter to verify that the authorization server advertised by the MCP server matched the issuer that actually owns the client’s client_id and client_secret, FastMCP’s wrappers omitted issuer from __init__() and _bind(). If a FastMCP client connected to a user-supplied or compromised MCP server URL, that server could advertise https://attacker[.]example/oauth/token in its metadata and receive the client’s client_id and client_secret in the token-exchange POST.

FastMCP 4.1.2 bumps its dependency floor to mcp>=2.2.0, adds issuer: str | None = None to both providers, isolates cached tokens by (client_id, scopes, issuer), and emits an MCPDeprecationWarning when issuer is omitted:

from fastmcp import Client
from fastmcp.client.auth import ClientCredentialsOAuthProvider

# Upgrading to fastmcp >= 4.1.2 is not enough by itself; pass issuer= explicitly:
auth = ClientCredentialsOAuthProvider(
    client_id="my-client-id",
    client_secret="my-client-secret",
    scopes=["read", "write"],
    issuer="https://auth.example.com",
)

4. MLflow (CVE-2026-115886): unauthenticated POST /ajax-api/3.0/mlflow/scorer/invoke SSRF via third_party_scorer_data.kwargs

In MLflow 3.12.0rc0 through 3.16.x, the GenAI evaluation routes POST /ajax-api/3.0/mlflow/scorer/invoke, POST /ajax-api/3.0/mlflow/scorers/register, and POST /ajax-api/3.0/mlflow/genai/evaluate accept a JSON serialized_scorer string. Notably, /ajax-api/3.0/mlflow/scorer/invoke is registered without an authentication decorator on default MLflow tracking servers and runs Scorer.model_validate_json(serialized_scorer) synchronously.

Inside mlflow/genai/scorers/base.py, Scorer.model_validate_json() checked module_path against THIRD_PARTY_SCORER_ALLOWED_MODULES (ragas, deepeval, trulens, phoenix), imported the module, looked up getattr(module, class_name), and called scorer_class(**init_kwargs) with unconstrained data["kwargs"]. When trulens and trulens-providers-litellm were installed in the server environment, selecting module: "mlflow.genai.scorers.trulens" with a non-native model URI such as "openai:/gpt-4o" routed **kwargs directly into trulens.providers.litellm.LiteLLM(model_engine=..., **kwargs), which passed api_base, base_url, api_key, or completion_kwargs into litellm.completion().

Commit bac30b4148 (released in MLflow 3.17.0) adds _validate_serialized_scorer_payload() and _validate_third_party_scorer_data() in mlflow/utils/validation.py, recursively walking every nested third_party_scorer_data dictionary and list of 2-item pairs (used by completion_kwargs) and rejecting any key in GATEWAY_DESTINATION_KEYS (api_base, base_url, api_key, custom_llm_provider, litellm_params, etc.) as well as call_source decorator scorers:

--- a/mlflow/utils/validation.py
+++ b/mlflow/utils/validation.py
@@ -1119,6 +1119,25 @@
+def _find_destination_keys(value: Any) -> set[str]:
+    if isinstance(value, dict):
+        found = GATEWAY_DESTINATION_KEYS & set(value)
+        for nested in value.values():
+            found |= _find_destination_keys(nested)
+        return found
+    if isinstance(value, list):
+        found: set[str] = set()
+        for nested in value:
+            if isinstance(nested, list) and len(nested) == 2 and isinstance(nested[0], str):
+                found |= GATEWAY_DESTINATION_KEYS & {nested[0]}
+            found |= _find_destination_keys(nested)
+        return found
+    return set()

Detection and remediation

1. Audit environments, lockfiles, and vulnerable call patterns

Run the following commands across your Python and TypeScript repositories:

# 1. Check installed Python package versions
pip show strands-agents langgraph-checkpoint langgraph-checkpoint-postgres langgraph-checkpoint-sqlite fastmcp fastmcp-slim mlflow 2>/dev/null || true

# 2. Check npm lockfiles for @strands-agents/sdk < 1.5.0
npm ls @strands-agents/sdk 2>/dev/null || true

# 3. Audit code for single-agent A2AServer(...) construction and FastMCP M2M OAuth without issuer=
rg -n 'A2AServer\s*\(|StrandsA2AExecutor\s*\(|A2AExecutor\s*\(' --glob '*.{py,ts,js}' . 2>/dev/null
rg -n '(ClientCredentialsOAuthProvider|PrivateKeyJWTOAuthProvider)\s*\(' --glob '*.py' . 2>/dev/null

2. Upgrade packages and apply required code changes

  1. Strands Agents SDK (CVE-2026-115419):
    • Upgrade Python strands-agents to >= 1.43.0 and TypeScript @strands-agents/sdk to >= 1.5.0.
    • Refactor every multi-caller A2AServer(agent) / StrandsA2AExecutor(agent) call to pass agent_factory (agentFactory in TypeScript) so each context_id receives a fresh Agent instance and a context-scoped session_manager.
  2. LangGraph (CVE-2026-115883):
    • Upgrade langgraph-checkpoint-postgres to >= 3.1.3, langgraph-checkpoint-sqlite to >= 3.1.2, and langgraph-checkpoint to >= 4.3.0 (or upgrade self-hosted LangSmith Agent Server to >= 0.15.5 / >= 0.16.2).
  3. FastMCP (GHSA-7xgj-8392-5q4r and GHSA-vqqp-x5qm-5qp4):
    • Upgrade fastmcp / fastmcp-slim to >= 4.1.2 (or >= 3.4.10 on the 3.x branch).
    • Add issuer="https://<your-expected-auth-server>" to every ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider instantiation, and rotate any client_secret that was used with untrusted or user-configured MCP server URLs.
  4. MLflow (CVE-2026-115886):
    • Upgrade mlflow to >= 3.17.0, and audit tracking-store registered_scorers rows and HTTP logs for requests to /ajax-api/3.0/mlflow/scorer/invoke containing third_party_scorer_data.

Timeline

  • 2026-10-22 (20:04 UTC): AWS publishes bulletin 2026-146-AWS and CVE-2026-115419 (GHSA-4v6f-7vjh-6x4m) alongside strands-agents 1.43.0 and @strands-agents/sdk 1.5.0.
  • 2026-10-23 (21:12 UTC): LangChain publishes CVE-2026-115883 (GHSA-6hmw-9432-67qq) and releases langgraph-checkpoint 4.3.0, langgraph-checkpoint-postgres 3.1.3, and langgraph-checkpoint-sqlite 3.1.2.
  • 2026-10-23 (21:17 UTC): MLflow publishes CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5), fixed in mlflow 3.17.0.
  • 2026-10-23 (21:31 to 21:32 UTC): Prefect publishes GHSA-vqqp-x5qm-5qp4 and GHSA-7xgj-8392-5q4r for fastmcp and fastmcp-slim, fixed in 3.4.10 and 4.1.2.

From research to remediation

Check whether this pattern exists in your codebase

Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.

References