LangChain's Default Config Poses a 10.4x Amplification Risk — How Can Public Chatbots Defend Themselves?

LangChain's default config has a storage amplification risk: 10.4x with O(n²) growth, requiring proactive protection.
The article reveals that LangChain's default combination of create_agent and SqliteSaver amplifies untrusted input 10.4x on disk, with quadratic growth. An attacker only needs to send unauthenticated HTTP requests; input is checkpointed before inference, so writes occur even if the LLM fails. Deleting sessions does not reclaim space; only VACUUM plus wal_checkpoint can free it. A full disk leads to a SQLITE_FULL crash loop. The article proposes five mitigations: limit request body size, trim message lists, set max_page_count and quotas, offline VACUUM, and monitor per-thread growth. It emphasizes this is a design-level risk rather than a CVE, default examples lack protection, and public chatbots should actively configure safeguards.
Introduction: An Overlooked Design-Level Risk
If you're building a chatbot with LangChain's create_agent and SqliteSaver and have simply copied the official documentation example, your application may be exposed to a serious storage amplification risk. A red team researcher posted a PSA (public safety announcement) on Reddit, pointing out that this default configuration amplifies untrusted input 10.4x on disk, and the growth is quadratic (O(n²)) — meaning the deeper the conversation, the more terrifying the disk usage.
This is not a CVE, nor a code bug, but a design-level resource exhaustion risk. Every public chatbot that allows users to control message size inherits this risk unless you actively take mitigation measures.

Experimental Reproduction: The Truth Behind the 10.4x Amplification
The researcher set up a victim application locally, using LangChain 1.4.7, langchain-core 1.7.1, langgraph 1.2.18, langgraph-checkpoint-sqlite 3.1.2, and Python 3.14.7, connected exactly as in the documentation pattern. The attack simulation was simple: send 6 sessions, each with 5 messages, each message 600 KB of input, totaling 18.4 MB of input data. The result was 192.4 MB written to disk — a 10.4x amplification.
What's worse is the growth pattern. Because the checkpointer writes the entire conversation state at every superstep, the k-th message persists roughly k times its own payload. In a 10-message session with 600 KB per message, the disk increment per message grows from about 4 MB to about 13 MB. This quadratic growth means an attacker can exhaust disk exponentially simply by sending longer conversations.
The attack cost is extremely low: just one unauthenticated HTTP request per message. Input is checkpointed before inference, so persistence occurs regardless of whether the LLM responds, times out, or errors out. This means even if your LLM service is unavailable, an attacker can still keep writing to disk.
Checkpointer and Superstep Mechanism: LangGraph's checkpointer is responsible for persisting the entire graph state on every state change. A superstep is the basic unit of graph execution, and a checkpoint is generated at the end of each superstep. SqliteSaver inserts each checkpoint as a new row rather than overwriting the old state, so all historical states are retained. This means that as conversation turns increase, each new checkpoint contains the complete message list up to that point, causing disk writes to accumulate linearly with the number of messages, while total storage grows quadratically. This design supports time travel and fault recovery, but in multi-user scenarios it amplifies storage overhead.
Why Deleting Sessions Won't Save You
You might think: as long as you periodically delete old sessions (e.g., a TTL thread deleter), you can control disk usage. But experiments show that DELETE operations reclaim 0 bytes. freelist_count rises, but page_count remains unchanged, and the SQLite file does not shrink. Only running VACUUM together with PRAGMA wal_checkpoint(TRUNCATE) can truly free space (37.9 MB reclaimed in the lab).
If you run a TTL deleter and assume disk space will be freed, your monitoring will deceive you. More dangerously, when the disk fills up, SQLite returns a SQLITE_FULL error, causing a permanent crash loop that cannot be recovered through your own API.
SQLite's freelist and VACUUM Mechanism: When SQLite deletes rows, it does not immediately shrink the database file; instead, it adds the freed pages to a freelist for reuse by subsequent inserts. freelist_count indicates the number of free pages, while page_count is the total number of pages. Delete operations only increase freelist_count; page_count remains unchanged, so the file size does not shrink. The VACUUM command rebuilds the database file, reclaims free pages, and defragments, thereby truly reducing the file size. In WAL mode, you also need to run PRAGMA wal_checkpoint(TRUNCATE) to write the WAL file contents back to the main database and truncate the WAL; otherwise, space remains occupied. When monitoring file size, you must measure after these two steps, or you will misjudge.
Risks Not Reproduced: Good News and Credibility
The researcher also verified whether certain attack vectors inherited from a previous n8n experiment were effective. In n8n, loading the full state of all flooded sessions had caused heap-OOM. But in LangGraph, loading about 24.6 MB of attacker text, over 3 rounds of testing, VmHWM stabilized at 235 MB, with no memory amplification. LangGraph's msgpack/pydantic parsing did not amplify memory the way n8n's includeData=true did.
Another unreproduced issue was n8n's "empty success" pattern (the LLM consumed all tokens on reasoning, returning content='' but marked as successful). With max_tokens=4096 set, the experiment yielded clean LLM timeouts (2–16 KB) rather than false successes. These unreproduced risks actually increase the credibility of the experiment, showing that the researcher did not exaggerate.
VmHWM and Memory Amplification: VmHWM (Virtual Memory High Water Mark) is a metric of a process's peak resident set size (RSS), recording the maximum physical memory usage reached during the process's lifetime. When assessing memory amplification risk, VmHWM reflects the worst case better than current RSS. In the experiment, after loading 24.6 MB of attacker text, VmHWM stabilized at 235 MB, indicating that LangGraph's msgpack/pydantic parsing did not inflate the data into multiple copies like n8n's includeData=true. msgpack is a compact binary serialization format, and pydantic is used for data validation; together they typically do not cause significant memory amplification, in contrast to n8n's JSON deep-copy behavior.
Mitigation Measures: None Effective Under Default Configuration
The researcher listed five truly effective mitigation measures and emphasized that none of them exist under the default configuration:
- Limit request body size at the proxy or application layer — this is the most effective control. The checkpointer stores whatever the request carries, so limiting input size directly cuts off the attack.
- Trim the message list before invocation (rolling window) — converts O(n²) to O(n). Keep only the most recent messages to avoid historical accumulation.
- Set
PRAGMA max_page_countas a disk fuse, and apply filesystem quotas to the state directory. - Schedule offline VACUUM (note the WAL trap: you must measure after
wal_checkpoint(TRUNCATE), otherwise monitoring lies). - Monitor database growth per thread versus request count — quadratic growth is very easy to alert on.
These measures are configuration- or architecture-level and do not require modifying LangChain source code. But the problem is that the default documentation example does not include any of them.
PRAGMA max_page_count and Filesystem Quotas: PRAGMA max_page_count sets the maximum number of pages allowed for an SQLite database; when exceeded, write operations return a SQLITE_FULL error, preventing unbounded disk usage. However, this limit applies only to a single database file; an attacker could still bypass it by creating multiple databases or exploiting WAL files. Therefore, you need to combine it with filesystem quotas (such as Linux quota or cgroup disk limits) to impose a hard cap on the state directory. In addition, offline VACUUM should be scheduled during off-peak hours and ensured to run after wal_checkpoint(TRUNCATE); otherwise, the WAL file may continue to grow. Monitoring database growth per thread versus request count can help detect abnormal patterns early.
This Is Not a CVE, But Every Public Chatbot Should Be Alert
The researcher explicitly states that this is not a CVE, nor a code vulnerability. Full-state checkpointing is the checkpointer's intended design. The problem is that when untrusted users control message size, deployments inherit this design-level resource exhaustion risk.
For any public-facing chatbot, especially those that allow users to send long messages or upload large blocks of text, this risk is real. An attacker does not need authentication; simply sending HTTP requests can gradually exhaust disk. And the crash loop after the disk fills up can make the service completely unavailable.
Currently, the community is discussing how to limit checkpoint growth in production, such as using rolling-window checkpointers, PostgreSQL + quotas, or whether newer versions of LangGraph have built-in solutions. If you have relevant experience, consider joining the discussion.
Conclusion: Design Security Requires Active Configuration
This case reminds us that framework default configurations often sacrifice security for demonstration convenience. As developers, we need to understand the underlying mechanisms and proactively add safeguards. Storage amplification is just one of many resource exhaustion risks, but its quadratic growth characteristic and low attack cost make it particularly dangerous. Check your chatbot, limit input size, trim message history, monitor disk growth — these simple steps may avert a disaster.

Related articles

Private Key Set to 4? CA's Elementary Mistake Leaves TLS Exposed
CA eMudra set private keys to tiny values like 4, making TLS certificates brute-forceable. A Computerphile video demonstrates the full process from extracting public keys to forging certificates, and explores the fragility of the PKI trust chain.

How Gen Turns Data into Decisions with ChatGPT Work
In an OpenAI user case, business leader Gen shares how ChatGPT Work turns data into decisions: within hours of launch, teams can judge whether integrations and onboarding flows work, shifting decision iteration from hundreds of trial-and-error attempts to a fast feedback loop.

ChatGPT vs. Claude In-Depth Comparison: Strong at Coding but Weak at Research — How to Bridge the Gap?
A user considers switching from ChatGPT to Claude, finding Claude stronger at coding but shallower at research. We analyze the differences and share tips to make Claude's research deeper.