flawopen.com/Teardowns/cve-2026-71433-langgraph-store-namespace-boundary
LangGraph CVE-2026-71433: Namespace Leak
LangGraph Postgres and SQLite scoped reads could cross namespace boundaries and expose sibling tenant data. Upgrade both checkpoint packages to 3.1.1.
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:NImagine a librarian told to fetch folders beginning with “team.” If the catalog searches raw text, it can return “team2” along with “team.” LangGraph stored each namespace as dot-separated text, then used a prefix pattern that did not require a dot boundary. The fix checks for the exact namespace or a real next segment, so one tenant’s folder is no longer mistaken for another’s.
Core Concepts & Subsystem Terms
Namespace- A tuple of labels used by a LangGraph Store to group items. Applications may use it as a user or tenant boundary.
Prefix scope- A request to read one namespace and its descendants. Correct matching must stop at a segment boundary:
fooincludesfoo.child, not the siblingfoobar. SQL `LIKE` pattern- A text pattern where
%matches any sequence and_matches one character. Those characters must be escaped when namespace labels are meant literally. CWE-863- Incorrect Authorization: the store returned records outside the namespace the caller was allowed to read.
Framework mappings
- CWE reference for the weakness described in this guide: CWE-863
Root Cause Analysis
Before 3.1.1, the Postgres and SQLite checkpoint stores flattened namespace tuples with dots and scoped search by applying LIKE '<namespace>%'. LIKE does not understand the namespace separator, so a request scoped to foo could also match sibling foobar; legal % and _ labels could act as wildcards. list_namespaces had related prefix and suffix matching errors. An ordinary scoped read was enough; no malformed input was required. This affects only the Postgres/SQLite store search and namespace-list read paths when applications rely on namespaces for tenant separation. get, put, and delete use exact equality and were not affected; InMemoryStore compares namespace elements and was not affected. The fix matches the exact flattened namespace or requires a dot before descendants, escapes LIKE metacharacters, and uses segment-aware patterns for namespace listing. SQLite also avoids LIKE's ASCII case-insensitive behavior for search. The advisory reports no evidence of exploitation in the wild. The affected packages are fixed in 3.1.1.
CVE-2026-71433 deployment reachability matrix
| Deployment and namespace shape | Documented behavior | Triage action |
|---|---|---|
| PostgresStore / AsyncPostgresStore or SqliteStore / AsyncSqliteStore before 3.1.1; tenant isolation depends on namespaces. | A scoped search or list_namespaces can cross into a sibling when labels share a textual prefix (foo / foobar, 1 / 12) or contain % / _. SQLite search also treated ASCII case-only differences as equal. | Upgrade the relevant checkpoint package to 3.1.1 or later. Test sibling prefixes, wildcard characters, suffix matching, and case-only differences against the production backend. |
| SQLite store before 3.1.1 with namespace labels differing only in ASCII case. | The vulnerable descendant search used case-insensitive LIKE, while exact operations treated case as significant. | Upgrade and add a case-sensitivity regression test if labels can differ by case. |
Fixed-length UUID namespace labels with no % or _. | The advisory says this label scheme is not affected: fixed-length labels cannot be prefixes of one another. | Confirm every namespace segment follows that invariant. If labels can vary or contain wildcard characters, treat the deployment as in scope. |
| InMemoryStore. | Not affected: it compares namespace elements individually. | No upgrade for this CVE; still enforce application-level tenant ownership checks. |
| LangSmith hosted deployment. | The default LANGGRAPH_STORE_BACKEND=python uses AsyncPostgresStore and is in scope when running an affected package. The separate grpc backend received an equivalent fix. | Check the configured backend and deployed version; verify hosted rollout or package version rather than inferring exposure from the product name. |
Step-by-Step Attack Flow
Check the store backend and labels
The deployment must use langgraph-checkpoint-postgres or langgraph-checkpoint-sqlite before 3.1.1, and rely on namespace scoping for user or tenant separation. Prefix-related labels or % and _ labels make the boundary relevant.
A normal scoped read is issued
An authenticated application request searches a namespace such as foo. The caller does not need to submit a special payload; the ordinary namespace scope is the input to the flawed matcher.
The backend flattens and pattern-matches
The store joins labels with dots and uses a LIKE prefix ending in %. Because the pattern has no required separator, foo can match foobar; pattern characters in labels can also broaden the match.
Sibling namespace items can be returned
Where an application treats namespaces as tenant boundaries, search or namespace listing may return another user's stored items. This is a read-side confidentiality issue, not SQL injection or write access.
Upgrade and retest isolation
Upgrade both applicable checkpoint packages to 3.1.1 or later. Test prefix siblings, literal % and _ labels, suffix matching, and the SQLite case-sensitivity behavior using separate test tenants.
Source Code: Flaw vs. Secure Implementation
The vulnerable predicate treats every string beginning with the flattened namespace as a descendant. The patch adds an exact-equality arm and requires a literal dot before descendant segments; it separately escapes backslash, %, and _ for the LIKE arm. The upstream commit also replaces list_namespaces prefix/suffix patterns with segment-aware matching and adjusts SQLite search semantics. This is a source excerpt, not a replacement module.
Source excerpt: libs/checkpoint-postgres/langgraph/store/postgres/base.py — _prepare_batch_search_queries and _namespace_prefix_condition
def _prepare_batch_search_queries(op):
ns_condition = "TRUE"
ns_param = None
if op.namespace_prefix:
ns_condition = "store.prefix LIKE %s"
ns_param = (f"{_namespace_to_text(op.namespace_prefix)}%",)
return ns_condition, ns_param
def _escape_like_literal(text: str) -> str:
return text.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
def _namespace_prefix_condition(namespace_prefix: tuple[str, ...]) -> tuple[str, tuple]:
path = _namespace_to_text(namespace_prefix)
condition = r"(store.prefix = %s OR store.prefix LIKE %s ESCAPE '\\')"
return condition, (path, f"{_escape_like_literal(path)}.%")
if op.namespace_prefix:
ns_condition, ns_param = _namespace_prefix_condition(op.namespace_prefix)
Engineering & System Hardening Checklist
- ✓Upgrade
langgraph-checkpoint-postgresandlanggraph-checkpoint-sqliteto 3.1.1 or later wherever installed. - ✓Add regression cases where one tenant label prefixes another, such as
fooandfoobar, and assert each scoped search returns only the requested namespace and descendants. - ✓Test namespace labels containing
%,_, backslashes, and case-only differences against the exact database backend used in production. - ✓Test
list_namespacesprefix, suffix, wildcard, and empty-path behavior; require*to match exactly one segment as documented. - ✓Keep authorization checks at the application boundary and do not treat opaque or fixed-format namespace IDs as a substitute for verifying tenant ownership.