langdoctor 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. langdoctor-0.1.0/.github/workflows/ci.yml +40 -0
  2. langdoctor-0.1.0/.github/workflows/release.yml +80 -0
  3. langdoctor-0.1.0/.gitignore +15 -0
  4. langdoctor-0.1.0/.pre-commit-hooks.yaml +7 -0
  5. langdoctor-0.1.0/CHANGELOG.md +40 -0
  6. langdoctor-0.1.0/CLAUDE.md +109 -0
  7. langdoctor-0.1.0/LICENSE +21 -0
  8. langdoctor-0.1.0/PKG-INFO +226 -0
  9. langdoctor-0.1.0/README.md +196 -0
  10. langdoctor-0.1.0/action.yml +67 -0
  11. langdoctor-0.1.0/assets/demo.gif +0 -0
  12. langdoctor-0.1.0/assets/demo.tape +31 -0
  13. langdoctor-0.1.0/examples/vulnerable-agent/Dockerfile +5 -0
  14. langdoctor-0.1.0/examples/vulnerable-agent/app.py +33 -0
  15. langdoctor-0.1.0/examples/vulnerable-agent/requirements.txt +5 -0
  16. langdoctor-0.1.0/langdoctor-spec.md +298 -0
  17. langdoctor-0.1.0/pyproject.toml +73 -0
  18. langdoctor-0.1.0/src/langdoctor/__init__.py +7 -0
  19. langdoctor-0.1.0/src/langdoctor/__main__.py +6 -0
  20. langdoctor-0.1.0/src/langdoctor/advisories.py +139 -0
  21. langdoctor-0.1.0/src/langdoctor/analysis.py +106 -0
  22. langdoctor-0.1.0/src/langdoctor/checks/__init__.py +59 -0
  23. langdoctor-0.1.0/src/langdoctor/checks/checkpointer.py +105 -0
  24. langdoctor-0.1.0/src/langdoctor/checks/config.py +123 -0
  25. langdoctor-0.1.0/src/langdoctor/checks/exposure.py +100 -0
  26. langdoctor-0.1.0/src/langdoctor/checks/hygiene.py +77 -0
  27. langdoctor-0.1.0/src/langdoctor/checks/secrets.py +89 -0
  28. langdoctor-0.1.0/src/langdoctor/checks/versions.py +104 -0
  29. langdoctor-0.1.0/src/langdoctor/cli.py +126 -0
  30. langdoctor-0.1.0/src/langdoctor/data/advisories.json +191 -0
  31. langdoctor-0.1.0/src/langdoctor/engine.py +66 -0
  32. langdoctor-0.1.0/src/langdoctor/finding.py +49 -0
  33. langdoctor-0.1.0/src/langdoctor/output/__init__.py +55 -0
  34. langdoctor-0.1.0/src/langdoctor/output/console.py +126 -0
  35. langdoctor-0.1.0/src/langdoctor/output/json_out.py +20 -0
  36. langdoctor-0.1.0/src/langdoctor/output/markdown.py +58 -0
  37. langdoctor-0.1.0/src/langdoctor/output/sarif.py +83 -0
  38. langdoctor-0.1.0/src/langdoctor/scanner.py +208 -0
  39. langdoctor-0.1.0/src/langdoctor/settings.py +31 -0
  40. langdoctor-0.1.0/src/langdoctor/suppress.py +50 -0
  41. langdoctor-0.1.0/tests/fixtures/clean_project/requirements.txt +5 -0
  42. langdoctor-0.1.0/tests/fixtures/vulnerable_project/app.py +8 -0
  43. langdoctor-0.1.0/tests/fixtures/vulnerable_project/requirements.txt +6 -0
  44. langdoctor-0.1.0/tests/test_advisories.py +77 -0
  45. langdoctor-0.1.0/tests/test_checkpointer.py +83 -0
  46. langdoctor-0.1.0/tests/test_cli.py +94 -0
  47. langdoctor-0.1.0/tests/test_config.py +85 -0
  48. langdoctor-0.1.0/tests/test_engine.py +60 -0
  49. langdoctor-0.1.0/tests/test_exposure.py +85 -0
  50. langdoctor-0.1.0/tests/test_finding.py +42 -0
  51. langdoctor-0.1.0/tests/test_hygiene.py +56 -0
  52. langdoctor-0.1.0/tests/test_output.py +83 -0
  53. langdoctor-0.1.0/tests/test_scanner.py +74 -0
  54. langdoctor-0.1.0/tests/test_secrets.py +60 -0
  55. langdoctor-0.1.0/tests/test_settings.py +28 -0
  56. langdoctor-0.1.0/tests/test_suppress.py +75 -0
  57. langdoctor-0.1.0/tests/test_versions.py +67 -0
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ci-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ lint:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+ - run: python -m pip install -e ".[dev]"
24
+ - run: ruff check .
25
+
26
+ test:
27
+ name: py${{ matrix.python-version }} · ${{ matrix.os }}
28
+ runs-on: ${{ matrix.os }}
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ os: [ubuntu-latest, macos-latest, windows-latest]
33
+ python-version: ["3.10", "3.12", "3.13"]
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: actions/setup-python@v5
37
+ with:
38
+ python-version: ${{ matrix.python-version }}
39
+ - run: python -m pip install -e ".[dev]"
40
+ - run: pytest -q
@@ -0,0 +1,80 @@
1
+ name: Release
2
+
3
+ # Publishing uses PyPI Trusted Publishing (OIDC) — no API token is stored.
4
+ # The trusted publisher is configured for: repo elaz48/langdoctor,
5
+ # workflow release.yml, environment pypi. Those three MUST NOT change or
6
+ # publishing will fail.
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ test:
17
+ name: py${{ matrix.python-version }} · ${{ matrix.os }}
18
+ runs-on: ${{ matrix.os }}
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ os: [ubuntu-latest, macos-latest, windows-latest]
23
+ python-version: ["3.10", "3.12", "3.13"]
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+ - run: python -m pip install -e ".[dev]"
30
+ - run: pytest -q
31
+
32
+ publish:
33
+ needs: test
34
+ runs-on: ubuntu-latest
35
+ environment: pypi
36
+ permissions:
37
+ id-token: write # OIDC token for PyPI Trusted Publishing
38
+ contents: write # create the GitHub Release
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: "3.12"
45
+
46
+ - name: Guard — tag must equal package __version__
47
+ run: |
48
+ set -euo pipefail
49
+ TAG="${GITHUB_REF_NAME#v}"
50
+ VERSION="$(python -c "import re,pathlib; \
51
+ t=pathlib.Path('src/langdoctor/__init__.py').read_text(); \
52
+ print(re.search(r'__version__ = \"([^\"]+)\"', t).group(1))")"
53
+ echo "tag=$TAG package_version=$VERSION"
54
+ if [ "$TAG" != "$VERSION" ]; then
55
+ echo "::error::git tag v$TAG does not match package __version__ $VERSION"
56
+ exit 1
57
+ fi
58
+
59
+ - name: Build with uv
60
+ run: |
61
+ python -m pip install uv
62
+ uv build
63
+
64
+ - name: Publish to PyPI (Trusted Publishing)
65
+ uses: pypa/gh-action-pypi-publish@release/v1 # langdoctor: ignore=LD502 (PyPA publisher; pin to a SHA for max hardening)
66
+
67
+ - name: Create GitHub Release
68
+ env:
69
+ GH_TOKEN: ${{ github.token }}
70
+ run: |
71
+ set -euo pipefail
72
+ VER="${GITHUB_REF_NAME#v}"
73
+ NOTES="$(awk -v ver="$VER" '
74
+ /^## \[/ { if (seen) exit; if (index($0, "[" ver "]")) { seen=1; next } }
75
+ seen
76
+ ' CHANGELOG.md)"
77
+ [ -z "$NOTES" ] && NOTES="See CHANGELOG.md."
78
+ gh release create "${GITHUB_REF_NAME}" dist/* \
79
+ --title "${GITHUB_REF_NAME}" \
80
+ --notes "$NOTES"
@@ -0,0 +1,15 @@
1
+ # ECC / Claude Code project harness (local-only)
2
+ .claude/
3
+
4
+ # Python
5
+ .venv/
6
+ venv/
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.egg-info/
10
+ build/
11
+ dist/
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .coverage
15
+ htmlcov/
@@ -0,0 +1,7 @@
1
+ - id: langdoctor
2
+ name: langdoctor
3
+ description: "Scan the project for known LangGraph/LangChain CVEs, insecure configs, and production footguns."
4
+ entry: langdoctor
5
+ language: python
6
+ pass_filenames: false
7
+ always_run: true
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to langdoctor are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-07-04
10
+
11
+ Initial release.
12
+
13
+ ### Added
14
+ - **Known-CVE checks (data-driven, offline).** 12 advisories for
15
+ LangGraph / LangChain / Langflow (LD101–LD111 + the LD150 Langflow catch-all),
16
+ shipped in `advisories.json` (schema v2): OSV-style `affected_ranges`
17
+ (including dual-line fixes), CVSS-derived severity with a manual override,
18
+ `exploited_in_the_wild` (KEV) flags, and `aliases`.
19
+ - **Code-pattern checks.** Checkpointer/state (LD201–204), graph/runtime config
20
+ (LD301–304), secrets & exposure (LD401–403, LD203), and hygiene (LD501–502) —
21
+ via cached stdlib-`ast` analysis and file heuristics.
22
+ - **Project scanner** for `requirements.txt`, `pyproject.toml` (PEP 621 +
23
+ poetry), `uv.lock`, and `poetry.lock`, plus Python-source discovery.
24
+ - **CLI**: `langdoctor [PATH]`, `list-checks`, `--version`; `--fail-on`,
25
+ `--strict`, `--ignore`, `--quiet`; exit codes 0 (clean) / 1 (findings) / 2
26
+ (scan error).
27
+ - **Output formats**: console, json, sarif (2.1.0 for GitHub code scanning, with
28
+ CVSS-backed `security-severity`), and markdown.
29
+ - **Config & suppression**: `[tool.langdoctor]` (`fail-on`/`ignore`/`exclude`)
30
+ and inline `# langdoctor: ignore[=IDs]` matched on LD id / CVE / alias
31
+ (case-insensitive). Suppressed findings are counted, never hidden.
32
+ - **Integrations**: composite GitHub Action (`action.yml`, install pinned to the
33
+ action's own ref) and `.pre-commit-hooks.yaml`.
34
+
35
+ ### Fixed
36
+ - Scanner `DEFAULT_EXCLUDES` matched any path component, silently excluding a
37
+ top-level `.env` file and breaking LD402/LD403 detection.
38
+
39
+ [Unreleased]: https://github.com/elaz48/langdoctor/compare/v0.1.0...HEAD
40
+ [0.1.0]: https://github.com/elaz48/langdoctor/releases/tag/v0.1.0
@@ -0,0 +1,109 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for Claude Code working in this repository. The authoritative brief is
4
+ `langdoctor-spec.md`; this file distills the parts that must stay consistent
5
+ across sessions.
6
+
7
+ ## Project
8
+
9
+ **langdoctor** — a production-readiness and security audit CLI for
10
+ LangGraph / LangChain / Langflow projects. "brew doctor for your agent stack."
11
+ Scans a project for known CVEs, insecure configs, and production footguns —
12
+ in seconds, offline, no API key. MIT licensed (© 2026 elaz48).
13
+
14
+ ## Design principles (non-negotiable)
15
+
16
+ 1. **Deterministic.** No AI/LLM API calls. Same input → same output, always.
17
+ 2. **Offline-first.** Zero network calls during a scan. CVE data ships in the
18
+ package (`src/langdoctor/data/advisories.json`).
19
+ 3. **Zero-config.** `pipx run langdoctor` / `uvx langdoctor` in a project dir must
20
+ produce useful output with no setup.
21
+ 4. **CI-native.** Meaningful exit codes, JSON/SARIF/markdown output, quiet mode.
22
+ 5. **Fast.** Full scan of a typical project under ~2s.
23
+
24
+ Non-goals (v1): runtime monitoring, prompt-injection detection, LLM-based
25
+ analysis, frameworks other than LangChain/LangGraph/Langflow.
26
+
27
+ ## Architecture map
28
+
29
+ `scanner` (discover project files) → `engine` (run registered checks) →
30
+ `Finding`s → `output/` (console | json | sarif | markdown). Full tree in spec §3.
31
+
32
+ - **Checks are data-driven where possible.** Version/CVE checks read
33
+ `advisories.json`; code-pattern checks are Python functions registered via
34
+ `@register_check(...)`.
35
+ - Keep dependencies minimal and pure-Python: `rich`, `packaging`,
36
+ `tomli` (only < 3.11; use stdlib `tomllib` on 3.11+), stdlib `ast`.
37
+
38
+ ## Advisories are DATA, not code
39
+
40
+ Adding a new CVE = edit `advisories.json` + add one vulnerable/clean fixture
41
+ pair + ship a **patch** release. Never hardcode a CVE threshold in Python. The
42
+ advisory DB is the product's live surface and its marketing engine (spec §9).
43
+
44
+ `advisories.json` is **schema_version 2** (spec §5). Key rules:
45
+ - Version applicability via `affected_ranges` (OSV-style `{introduced, fixed}`
46
+ list, compared with `packaging.version`). `fixed_in` is a human summary ONLY —
47
+ never the comparison source of truth. This models dual-line fixes (e.g. LD105
48
+ patched on both 0.3.x and 1.x).
49
+ - **Severity is derived from `cvss_score`** via `severity_buckets`
50
+ (≥9.0 critical / ≥7.0 high / ≥4.0 medium / ≥0.1 low). `severity_override`
51
+ wins when set (CNA-vs-NVD disagreements happen — see LD105).
52
+ - `exploited_in_the_wild` (KEV) bumps display priority above all non-KEV
53
+ findings regardless of CVSS, with a 🔴 KEV badge.
54
+ - `aliases` (GHSA/PYSEC/duplicate CVE IDs): lookup, `--ignore`, and inline
55
+ `# langdoctor: ignore=` suppression must match the LD id, primary CVE, OR any
56
+ alias. The engine must tolerate unknown fields (forward compatibility).
57
+
58
+ ## Check-ID conventions
59
+
60
+ Every advisory/check has a stable ID `LD###`, categorized by hundreds:
61
+ - `1xx` known vulnerabilities (data-driven), `2xx` checkpointer & state,
62
+ `3xx` graph & runtime config, `4xx` secrets & exposure, `5xx` hygiene.
63
+ - IDs are permanent identifiers — never renumber a shipped ID; deprecate instead.
64
+ - Users suppress by ID; docs deep-link by ID.
65
+
66
+ ## False-positive policy
67
+
68
+ Heuristic checks (e.g. LD203, LD302) MUST print "heuristic" in output and MUST
69
+ NOT affect CI exit code by default — only under `--strict`. Trust is the product.
70
+
71
+ ## Verification discipline (learned in Phase 0)
72
+
73
+ **OSV.dev lags NVD and vendor advisories.** CVE-2026-5027 (LD111) was real and
74
+ actively exploited but absent from OSV at build time. When adding/verifying an
75
+ advisory: cross-check ≥2 sources (OSV + NVD + GitHub Security Advisories /
76
+ vendor), record every identifier in `aliases`, and pull CVSS base scores from
77
+ NVD (do not hand-compute). Numbers are "verified-as-of a date," never permanent.
78
+
79
+ ## Commands
80
+
81
+ Python 3.10+ (ecosystem floor, verified). Cross-platform: `pathlib` everywhere,
82
+ no shelling out, explicit UTF-8 (`encoding="utf-8", errors="replace"`), respect
83
+ `NO_COLOR` and non-TTY.
84
+
85
+ | Task | Command |
86
+ |------|---------|
87
+ | Run | `python -m langdoctor [PATH]` (editable: `pip install -e .`) |
88
+ | Test | `pytest` (target suite < 30s) |
89
+ | Lint | `ruff check .` / `ruff format .` |
90
+ | Build | `uv build` |
91
+
92
+ ## Release / deployment constraints (IMPORTANT)
93
+
94
+ - **PyPI Trusted Publishing (OIDC, no stored token).** The release workflow MUST
95
+ match the pre-configured Trusted Publisher exactly or publishing fails:
96
+ owner `elaz48`, repo `langdoctor`, workflow filename `release.yml`,
97
+ environment `pypi`. Release trigger: push tag `vX.Y.Z` → full test matrix →
98
+ `uv build` → publish → GitHub Release with changelog.
99
+ - **CI matrix:** ubuntu / macos / windows × Python 3.10 / 3.12 / 3.13; ruff + pytest.
100
+ - **Website (`langdoctor.dev`)** is a single self-contained static HTML file
101
+ (spec §10), uploaded **manually via FTP to PHP/DirectAdmin shared hosting**.
102
+ Do NOT set up GitHub Pages or any Actions-based site deploy.
103
+
104
+ ## Working agreement
105
+
106
+ Build phase-by-phase per spec §11 (skeleton → full catalog → output/integration
107
+ → polish/release → website). Pause for review at the end of each phase. Follow
108
+ the spec's architecture, check catalog, and CLI exactly unless there's a
109
+ concrete technical reason to deviate — then flag it, don't silently change course.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 elaz48
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: langdoctor
3
+ Version: 0.1.0
4
+ Summary: Production-readiness and security audit CLI for LangGraph / LangChain projects — brew doctor for your agent stack.
5
+ Project-URL: Homepage, https://langdoctor.dev
6
+ Project-URL: Repository, https://github.com/elaz48/langdoctor
7
+ Project-URL: Issues, https://github.com/elaz48/langdoctor/issues
8
+ Author: Balazs Hende
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agents,cli,devsecops,langchain,langflow,langgraph,llm-security,sast,security
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: packaging>=23.0
24
+ Requires-Dist: rich>=13.0
25
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.6; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # langdoctor 🩺
32
+
33
+ > **Scan your LangGraph/LangChain project for known CVEs, insecure configs, and production footguns — in seconds, offline, no API key.**
34
+ >
35
+ > _brew doctor for your agent stack._
36
+
37
+ [![PyPI](https://img.shields.io/pypi/v/langdoctor.svg)](https://pypi.org/project/langdoctor/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/langdoctor.svg)](https://pypi.org/project/langdoctor/)
39
+ [![CI](https://github.com/elaz48/langdoctor/actions/workflows/ci.yml/badge.svg)](https://github.com/elaz48/langdoctor/actions/workflows/ci.yml)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
41
+
42
+ ![langdoctor scanning a vulnerable project](https://raw.githubusercontent.com/elaz48/langdoctor/main/assets/demo.gif)
43
+
44
+ ## Quickstart
45
+
46
+ ```bash
47
+ pipx run langdoctor # or: uvx langdoctor
48
+ ```
49
+
50
+ Run it in your project directory. langdoctor reads your dependency files and
51
+ source, reports issues grouped by severity, and exits non-zero when it finds
52
+ something at or above your threshold.
53
+
54
+ ```bash
55
+ langdoctor # scan the current directory
56
+ langdoctor path/to/project # scan a specific directory
57
+ langdoctor list-checks # every check, with IDs and severities
58
+ langdoctor --version # tool version + advisory DB date
59
+ ```
60
+
61
+ ## Why
62
+
63
+ 2026 has been a rough year for the agent stack:
64
+
65
+ - **Langflow is under active attack.** `CVE-2025-3248` (unauthenticated RCE) was
66
+ added to the [CISA KEV catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
67
+ and [exploited to deploy the Flodrix botnet](https://www.trendmicro.com/en_us/research/25/f/langflow-vulnerability-flodric-botnet.html);
68
+ `CVE-2026-5027` (path-traversal → RCE) is [being exploited across ~7,000 exposed instances](https://www.bleepingcomputer.com/news/security/path-traversal-flaw-in-ai-dev-platform-langflow-exploited-in-attacks/).
69
+ - **LangGraph checkpointer CVEs** — SQL injection and unsafe deserialization in
70
+ the SQLite/base checkpointers can chain to RCE via forged checkpoint rows.
71
+ - **Supply-chain attacks** on the CI actions and packages the ecosystem depends
72
+ on (the tj-actions and LiteLLM incidents) mean a pinned-to-a-tag action or an
73
+ unpinned dependency is a real risk surface.
74
+
75
+ These are exactly the things a framework-specific linter can catch before you
76
+ ship. langdoctor ships the CVE data with the package and runs fully offline.
77
+
78
+ ## What it checks
79
+
80
+ <!-- BEGIN check-table (generated from `langdoctor list-checks`) -->
81
+ | ID | Category | Severity | What it catches |
82
+ | --- | --- | --- | --- |
83
+ | `LD101` | Known CVEs | high | SQL injection via metadata filter key in SQLite checkpointer |
84
+ | `LD102` | Known CVEs | medium | Unsafe msgpack deserialization in checkpoint loading |
85
+ | `LD103` | Known CVEs | medium | Unsafe JSON deserialization in checkpoint loading |
86
+ | `LD104` | Known CVEs | high | Path traversal in legacy load_prompt functions |
87
+ | `LD105` | Known CVEs | critical | Serialization-injection secret extraction in dumps/loads |
88
+ | `LD106` | Known CVEs | critical 🔴KEV | Unauthenticated RCE in /api/v1/validate/code (Langflow) |
89
+ | `LD107` | Known CVEs | medium | BaseCache deserialization of untrusted data may lead to RCE |
90
+ | `LD108` | Known CVEs | high | RCE in "json" mode of JsonPlusSerializer |
91
+ | `LD109` | Known CVEs | high | SQL injection in the SQLite store implementation |
92
+ | `LD110` | Known CVEs | high | SQL injection via filter key in SqliteStore |
93
+ | `LD111` | Known CVEs | high 🔴KEV | Path traversal → RCE via /api/v2/files upload (Langflow) |
94
+ | `LD150` | Known CVEs | high | Langflow older than the current secure baseline (1.10.1) |
95
+ | `LD201` | Checkpointer & state | high | MemorySaver used in a production-bound project |
96
+ | `LD202` | Checkpointer & state | medium | SqliteSaver may collapse under write concurrency |
97
+ | `LD203` | Checkpointer & state | high (heuristic) | Checkpoint history filtered by user-controlled input |
98
+ | `LD204` | Checkpointer & state | medium | Compiled graph with interrupts has no checkpointer |
99
+ | `LD301` | Graph & runtime config | medium | No recursion_limit configured for a LangGraph run |
100
+ | `LD302` | Graph & runtime config | low (heuristic) | LLM client created without a timeout |
101
+ | `LD303` | Graph & runtime config | info | Deprecated pre-1.0 LangChain import |
102
+ | `LD304` | Graph & runtime config | high | Legacy load_prompt() usage |
103
+ | `LD401` | Secrets & exposure | critical | Hardcoded API key in source |
104
+ | `LD402` | Secrets & exposure | high | .env file present but not gitignored |
105
+ | `LD403` | Secrets & exposure | critical | Langflow auto-login not explicitly disabled |
106
+ | `LD501` | Hygiene | medium | Dependencies are not pinned |
107
+ | `LD502` | Hygiene | medium | GitHub Actions uses an unpinned third-party action |
108
+ <!-- END check-table -->
109
+
110
+ Severities for CVE checks are derived from the CVSS score; `🔴KEV` marks
111
+ [Known-Exploited](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
112
+ vulnerabilities, which are always surfaced first.
113
+
114
+ ## CI integration
115
+
116
+ ### GitHub Action
117
+
118
+ ```yaml
119
+ # .github/workflows/langdoctor.yml
120
+ name: langdoctor
121
+ on: [push, pull_request]
122
+ permissions:
123
+ contents: read
124
+ security-events: write # to upload SARIF
125
+ jobs:
126
+ scan:
127
+ runs-on: ubuntu-latest
128
+ steps:
129
+ - uses: actions/checkout@v4
130
+ - uses: elaz48/langdoctor@v1
131
+ with:
132
+ fail-on: high
133
+ - uses: github/codeql-action/upload-sarif@v3
134
+ if: always()
135
+ with:
136
+ sarif_file: langdoctor.sarif
137
+ ```
138
+
139
+ Findings show up in your repo's **Security → Code scanning** tab.
140
+
141
+ ### pre-commit
142
+
143
+ ```yaml
144
+ # .pre-commit-config.yaml
145
+ repos:
146
+ - repo: https://github.com/elaz48/langdoctor
147
+ rev: v0.1.0
148
+ hooks:
149
+ - id: langdoctor
150
+ ```
151
+
152
+ ### GitLab CI
153
+
154
+ ```yaml
155
+ langdoctor:
156
+ image: python:3.12-slim
157
+ script:
158
+ - pip install langdoctor
159
+ - langdoctor --format sarif > gl-langdoctor.sarif
160
+ artifacts:
161
+ reports:
162
+ sast: gl-langdoctor.sarif
163
+ ```
164
+
165
+ ## Configuration
166
+
167
+ Configure defaults in `pyproject.toml`; command-line flags take precedence.
168
+
169
+ ```toml
170
+ [tool.langdoctor]
171
+ fail-on = "high" # critical | high | medium | low | never
172
+ ignore = ["LD203", "LD302"]
173
+ exclude = ["examples", "vendor"]
174
+ ```
175
+
176
+ Suppress a single finding inline — by LD id, CVE, or any alias:
177
+
178
+ ```python
179
+ API_KEY = get_key() # langdoctor: ignore=LD401
180
+ ```
181
+
182
+ ```text
183
+ langgraph==1.0.5 # langdoctor: ignore=LD102
184
+ ```
185
+
186
+ Suppressed findings are always reported as a `suppressed: N` count so nothing
187
+ disappears silently.
188
+
189
+ ## Output formats
190
+
191
+ `--format console` (default) · `json` · `sarif` · `markdown`
192
+
193
+ - **sarif** — GitHub code scanning; `security-severity` carries the real CVSS.
194
+ - **markdown** — paste straight into a PR or issue.
195
+ - **json** — machine-readable, with a per-severity summary.
196
+
197
+ Exit codes: `0` clean (or below `--fail-on`), `1` findings at/above threshold,
198
+ `2` scan error. Heuristic checks never affect the exit code unless `--strict`.
199
+
200
+ ## What langdoctor is NOT
201
+
202
+ - ❌ **Not a runtime guard.** It scans code and dependencies; it does not sit in
203
+ your request path.
204
+ - ❌ **Not an LLM firewall.** It does no prompt-injection or content analysis.
205
+ - ❌ **Not a replacement for Semgrep/Snyk.** It's the *framework-specific* layer
206
+ those miss — LangGraph/LangChain/Langflow footguns, by ID, with fixes.
207
+
208
+ It is deterministic (no AI calls), offline (CVE data ships in the package), and
209
+ zero-config.
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ pip install -e ".[dev]"
215
+ pytest
216
+ ruff check .
217
+ ```
218
+
219
+ Requires Python 3.10+. See [`CLAUDE.md`](CLAUDE.md) for design principles, the
220
+ check-ID conventions, and the "advisories are data" model.
221
+
222
+ ## License
223
+
224
+ MIT © 2026 elaz48
225
+
226
+ <sub>Topics: langgraph · langchain · langflow · security · sast · ai-agents · llm-security · devsecops · cli</sub>