"details": "# Context\n\nA SQL injection vulnerability exists in LangGraph's SQLite checkpoint implementation that allows attackers to manipulate SQL queries through metadata filter keys. This affects applications that accept **untrusted metadata filter keys** (not just filter values) in checkpoint search operations.\n\n# Impact\n\nAttackers who control metadata filter keys can execute arbitrary sql queries against the database.\n\n# Root Cause\n\nThe `_metadata_predicate()` function constructs SQL queries by interpolating filter keys directly into f-strings without validation:\n\n```python\n# VULNERABLE CODE (before fix)\nfor query_key, query_value in metadata_filter.items():\n operator, param_value = _where_value(query_value)\n predicates.append(\n f\"json_extract(CAST(metadata AS TEXT), '$.{query_key}') {operator}\"\n )\n param_values.append(param_value)\n```\n\nWhile filter **values** are parameterized, filter **keys** are not validated, allowing SQL injection.\n\n# Attack Example\n\n**Before Fix:**\n```python\nfrom langgraph.checkpoint.sqlite import SqliteSaver\n\nsaver = SqliteSaver.from_conn_string(\"checkpoints.db\")\n\n# Attacker controls the filter keys\nmalicious_filter = {\"x') OR '1'='1\": \"dummy\"}\n\n# Returns ALL checkpoints, bypassing filtering\nresults = list(saver.list(None, filter=malicious_filter))\n```\n\n**Resulting SQL:**\n```sql\nWHERE json_extract(CAST(metadata AS TEXT), '$.x') OR '1'='1') = ?\n-- Injected condition makes WHERE clause always true\n```\n\n## Who Is Affected?\n\n### LangSmith Deployment Customers: NOT Impacted\n\n**LangSmith deployment customers are NOT affected by this vulnerability.** LangSmith deployments do not allow configuring custom checkpointers, so the vulnerable code path cannot be reached.\n\n### High Risk: Custom Server Deployments\n\nYou are affected if your application:\n- Runs a custom server with SqliteSaver checkpointer\n- Exposes an endpoint for fetching checkpoint history (e.g., via `get_state_history()`)\n- Accepts metadata filter keys from untrusted sources\n\n**Example vulnerable code:**\n```python\n# Custom server endpoint - User controls filter key names - DANGEROUS\
[email protected](\"/api/history\")\ndef get_history(request):\n filter_field = request.json.get(\"filter_field\") # Untrusted input\n filter_value = request.json.get(\"filter_value\")\n\n # VULNERABLE: Attacker can bypass access controls\n history = list(graph.get_state_history(\n config,\n filter={filter_field: filter_value}\n ))\n return history\n```\n\n**Note on privilege escalation:** If an endpoint allows end users to specify arbitrary filter keys, those users likely already have legitimate access to query the checkpoint database. In such cases, this vulnerability may not constitute a privilege escalation, as users who can control filter keys would typically already be expected to have database access. However, the SQL injection still allows bypassing intended filtering logic and metadata-based access controls that the application may rely on for data isolation.\n\n### Additional Security Hardening (Defense in Depth)\n\nThis release also includes hardening improvements:\n\n**1. Checkpoint Limit Parameter**: used f-string interpolation into parameterized query. Not considered a vulnerability as it requires users to accept untrusted input and not validate it against the actual API signature. \n\n**2. Store Filter Value Parameterization**: Refactored all filter value handling from manual quote escaping to parameterized queries\n\n## Remediation\n\n### Immediate Actions\n\n1. **Update to the patched version** of `langgraph-checkpoint-sqlite`\n2. **Audit your code** for locations where filter keys come from untrusted sources",
0 commit comments