VYPR
High severity7.3OSV Advisory· Published Oct 29, 2025· Updated Apr 15, 2026

CVE-2025-64104

CVE-2025-64104

Description

LangGraph SQLite Checkpoint is an implementation of LangGraph CheckpointSaver that uses SQLite DB (both sync and async, via aiosqlite). Prior to 2.0.11, LangGraph's SQLite store implementation contains SQL injection vulnerabilities using direct string concatenation without proper parameterization, allowing attackers to inject arbitrary SQL and bypass access controls. This vulnerability is fixed in 2.0.11.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
langgraph-checkpoint-sqlitePyPI
< 2.0.112.0.11

Affected products

1

Patches

1
bc9d45b47610

fix(checkpoint-sqlite): add validation to filter keys in sql store (#5666)

https://github.com/langchain-ai/langgraphEugene YurtsevJul 25, 2025via ghsa
2 files changed · +44 0
  • libs/checkpoint-sqlite/langgraph/store/sqlite/base.py+24 0 modified
    @@ -3,6 +3,7 @@
     import concurrent.futures
     import datetime
     import logging
    +import re
     import sqlite3
     import threading
     from collections import defaultdict
    @@ -107,6 +108,23 @@ def _decode_ns_text(namespace: str) -> tuple[str, ...]:
         return tuple(namespace.split("."))
     
     
    +def _validate_filter_key(key: str) -> None:
    +    """Validate that a filter key is safe for use in SQL queries.
    +
    +    Args:
    +        key: The filter key to validate
    +
    +    Raises:
    +        ValueError: If the key contains invalid characters that could enable SQL injection
    +    """
    +    # Allow alphanumeric characters, underscores, dots, and hyphens
    +    # This covers typical JSON property names while preventing SQL injection
    +    if not re.match(r"^[a-zA-Z0-9_.-]+$", key):
    +        raise ValueError(
    +            f"Invalid filter key: '{key}'. Filter keys must contain only alphanumeric characters, underscores, dots, and hyphens."
    +        )
    +
    +
     def _json_loads(content: bytes | str | orjson.Fragment) -> Any:
         if isinstance(content, orjson.Fragment):
             if hasattr(content, "buf"):
    @@ -372,6 +390,8 @@ def _prepare_batch_search_queries(
                 filter_conditions = []
                 if op.filter:
                     for key, value in op.filter.items():
    +                    _validate_filter_key(key)
    +
                         if isinstance(value, dict):
                             for op_name, val in value.items():
                                 condition, filter_params_ = self._get_filter_condition(
    @@ -622,6 +642,8 @@ def _get_batch_list_namespaces_queries(
     
         def _get_filter_condition(self, key: str, op: str, value: Any) -> tuple[str, list]:
             """Helper to generate filter conditions."""
    +        _validate_filter_key(key)
    +
             # We need to properly format values for SQLite JSON extraction comparison
             if op == "$eq":
                 if isinstance(value, str):
    @@ -858,6 +880,8 @@ def _get_batch_GET_ops_queries(
     
         def _get_filter_condition(self, key: str, op: str, value: Any) -> tuple[str, list]:
             """Helper to generate filter conditions."""
    +        _validate_filter_key(key)
    +
             # We need to properly format values for SQLite JSON extraction comparison
             if op == "$eq":
                 if isinstance(value, str):
    
  • libs/checkpoint-sqlite/tests/test_store.py+20 0 modified
    @@ -1047,3 +1047,23 @@ def test_search_items(
             for ns in test_namespaces:
                 key = f"item_{ns[-1]}"
                 store.delete(ns, key)
    +
    +
    +def test_sql_injection_vulnerability(store: SqliteStore) -> None:
    +    """Test that SQL injection via malicious filter keys is prevented."""
    +    # Add public and private documents
    +    store.put(("docs",), "public", {"access": "public", "data": "public info"})
    +    store.put(
    +        ("docs",), "private", {"access": "private", "data": "secret", "password": "123"}
    +    )
    +
    +    # Normal query - returns 1 public document
    +    normal = store.search(("docs",), filter={"access": "public"})
    +    assert len(normal) == 1
    +    assert normal[0].value["access"] == "public"
    +
    +    # SQL injection attempt via malicious key should raise ValueError
    +    malicious_key = "access') = 'public' OR '1'='1' OR json_extract(value, '$."
    +
    +    with pytest.raises(ValueError, match="Invalid filter key"):
    +        store.search(("docs",), filter={malicious_key: "dummy"})
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.