Production-readiness and security audit CLI for LangGraph / LangChain projects — brew doctor for your agent stack.
Project description
langdoctor 🩺
Scan your LangGraph/LangChain project for known CVEs, insecure configs, and production footguns — in seconds, offline, no API key.
brew doctor for your agent stack.
Quickstart
pipx run langdoctor # or: uvx langdoctor
Run it in your project directory. langdoctor reads your dependency files and source, reports issues grouped by severity, and exits non-zero when it finds something at or above your threshold.
langdoctor # scan the current directory
langdoctor path/to/project # scan a specific directory
langdoctor list-checks # every check, with IDs and severities
langdoctor --version # tool version + advisory DB date
Why
2026 has been a rough year for the agent stack:
- Langflow is under active attack.
CVE-2025-3248(unauthenticated RCE) was added to the CISA KEV catalog and exploited to deploy the Flodrix botnet;CVE-2026-5027(path-traversal → RCE) is being exploited across ~7,000 exposed instances. - LangGraph checkpointer CVEs — SQL injection and unsafe deserialization in the SQLite/base checkpointers can chain to RCE via forged checkpoint rows.
- Supply-chain attacks on the CI actions and packages the ecosystem depends on (the tj-actions and LiteLLM incidents) mean a pinned-to-a-tag action or an unpinned dependency is a real risk surface.
These are exactly the things a framework-specific linter can catch before you ship. langdoctor ships the CVE data with the package and runs fully offline.
What it checks
| ID | Category | Severity | What it catches |
|---|---|---|---|
LD101 |
Known CVEs | high | SQL injection via metadata filter key in SQLite checkpointer |
LD102 |
Known CVEs | medium | Unsafe msgpack deserialization in checkpoint loading |
LD103 |
Known CVEs | medium | Unsafe JSON deserialization in checkpoint loading |
LD104 |
Known CVEs | high | Path traversal in legacy load_prompt functions |
LD105 |
Known CVEs | critical | Serialization-injection secret extraction in dumps/loads |
LD106 |
Known CVEs | critical 🔴KEV | Unauthenticated RCE in /api/v1/validate/code (Langflow) |
LD107 |
Known CVEs | medium | BaseCache deserialization of untrusted data may lead to RCE |
LD108 |
Known CVEs | high | RCE in "json" mode of JsonPlusSerializer |
LD109 |
Known CVEs | high | SQL injection in the SQLite store implementation |
LD110 |
Known CVEs | high | SQL injection via filter key in SqliteStore |
LD111 |
Known CVEs | high 🔴KEV | Path traversal → RCE via /api/v2/files upload (Langflow) |
LD150 |
Known CVEs | high | Langflow older than the current secure baseline (1.10.1) |
LD201 |
Checkpointer & state | high | MemorySaver used in a production-bound project |
LD202 |
Checkpointer & state | medium | SqliteSaver may collapse under write concurrency |
LD203 |
Checkpointer & state | high (heuristic) | Checkpoint history filtered by user-controlled input |
LD204 |
Checkpointer & state | medium | Compiled graph with interrupts has no checkpointer |
LD301 |
Graph & runtime config | medium | No recursion_limit configured for a LangGraph run |
LD302 |
Graph & runtime config | low (heuristic) | LLM client created without a timeout |
LD303 |
Graph & runtime config | info | Deprecated pre-1.0 LangChain import |
LD304 |
Graph & runtime config | high | Legacy load_prompt() usage |
LD401 |
Secrets & exposure | critical | Hardcoded API key in source |
LD402 |
Secrets & exposure | high | .env file present but not gitignored |
LD403 |
Secrets & exposure | critical | Langflow auto-login not explicitly disabled |
LD501 |
Hygiene | medium | Dependencies are not pinned |
LD502 |
Hygiene | medium | GitHub Actions uses an unpinned third-party action |
Severities for CVE checks are derived from the CVSS score; 🔴KEV marks
Known-Exploited
vulnerabilities, which are always surfaced first.
CI integration
GitHub Action
# .github/workflows/langdoctor.yml
name: langdoctor
on: [push, pull_request]
permissions:
contents: read
security-events: write # to upload SARIF
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: elaz48/langdoctor@v1
with:
fail-on: high
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: langdoctor.sarif
Findings show up in your repo's Security → Code scanning tab.
pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/elaz48/langdoctor
rev: v0.1.0
hooks:
- id: langdoctor
GitLab CI
langdoctor:
image: python:3.12-slim
script:
- pip install langdoctor
- langdoctor --format sarif > gl-langdoctor.sarif
artifacts:
reports:
sast: gl-langdoctor.sarif
Configuration
Configure defaults in pyproject.toml; command-line flags take precedence.
[tool.langdoctor]
fail-on = "high" # critical | high | medium | low | never
ignore = ["LD203", "LD302"]
exclude = ["examples", "vendor"]
Suppress a single finding inline — by LD id, CVE, or any alias:
API_KEY = get_key() # langdoctor: ignore=LD401
langgraph==1.0.5 # langdoctor: ignore=LD102
Suppressed findings are always reported as a suppressed: N count so nothing
disappears silently.
Output formats
--format console (default) · json · sarif · markdown
- sarif — GitHub code scanning;
security-severitycarries the real CVSS. - markdown — paste straight into a PR or issue.
- json — machine-readable, with a per-severity summary.
Exit codes: 0 clean (or below --fail-on), 1 findings at/above threshold,
2 scan error. Heuristic checks never affect the exit code unless --strict.
What langdoctor is NOT
- ❌ Not a runtime guard. It scans code and dependencies; it does not sit in your request path.
- ❌ Not an LLM firewall. It does no prompt-injection or content analysis.
- ❌ Not a replacement for Semgrep/Snyk. It's the framework-specific layer those miss — LangGraph/LangChain/Langflow footguns, by ID, with fixes.
It is deterministic (no AI calls), offline (CVE data ships in the package), and zero-config.
Development
pip install -e ".[dev]"
pytest
ruff check .
Requires Python 3.10+. See CLAUDE.md for design principles, the
check-ID conventions, and the "advisories are data" model.
License
MIT © 2026 elaz48
Topics: langgraph · langchain · langflow · security · sast · ai-agents · llm-security · devsecops · cli
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langdoctor-0.1.0.tar.gz.
File metadata
- Download URL: langdoctor-0.1.0.tar.gz
- Upload date:
- Size: 542.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43f688cd8c51f793c64e27a6e0bddfd6331cf3f51da4098a26e04c6d2d77bf81
|
|
| MD5 |
1d481bd0b3e815d4115b761ee8a32b3c
|
|
| BLAKE2b-256 |
740dcf2f0ca9fa694791b745e5ba43fdcaeeea962d36c44d32d8b6df75563bc7
|
Provenance
The following attestation bundles were made for langdoctor-0.1.0.tar.gz:
Publisher:
release.yml on elaz48/langdoctor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langdoctor-0.1.0.tar.gz -
Subject digest:
43f688cd8c51f793c64e27a6e0bddfd6331cf3f51da4098a26e04c6d2d77bf81 - Sigstore transparency entry: 2070838894
- Sigstore integration time:
-
Permalink:
elaz48/langdoctor@1e6b8ff6a1f1b6cddfb9e2fe74c5647c83bfa4b5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/elaz48
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1e6b8ff6a1f1b6cddfb9e2fe74c5647c83bfa4b5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file langdoctor-0.1.0-py3-none-any.whl.
File metadata
- Download URL: langdoctor-0.1.0-py3-none-any.whl
- Upload date:
- Size: 35.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92c9517d9e1d8406b6323a73d0d0d2b5b10a4833a67106b3b994c327f76753d1
|
|
| MD5 |
1a93af789d944f3b62f4cd44f1eb09d5
|
|
| BLAKE2b-256 |
ef278d6602e5d71ce07bbe66232ed0f76f251ba95323b986094ecce6ab72680f
|
Provenance
The following attestation bundles were made for langdoctor-0.1.0-py3-none-any.whl:
Publisher:
release.yml on elaz48/langdoctor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langdoctor-0.1.0-py3-none-any.whl -
Subject digest:
92c9517d9e1d8406b6323a73d0d0d2b5b10a4833a67106b3b994c327f76753d1 - Sigstore transparency entry: 2070839017
- Sigstore integration time:
-
Permalink:
elaz48/langdoctor@1e6b8ff6a1f1b6cddfb9e2fe74c5647c83bfa4b5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/elaz48
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1e6b8ff6a1f1b6cddfb9e2fe74c5647c83bfa4b5 -
Trigger Event:
push
-
Statement type: