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.
- langdoctor-0.1.0/.github/workflows/ci.yml +40 -0
- langdoctor-0.1.0/.github/workflows/release.yml +80 -0
- langdoctor-0.1.0/.gitignore +15 -0
- langdoctor-0.1.0/.pre-commit-hooks.yaml +7 -0
- langdoctor-0.1.0/CHANGELOG.md +40 -0
- langdoctor-0.1.0/CLAUDE.md +109 -0
- langdoctor-0.1.0/LICENSE +21 -0
- langdoctor-0.1.0/PKG-INFO +226 -0
- langdoctor-0.1.0/README.md +196 -0
- langdoctor-0.1.0/action.yml +67 -0
- langdoctor-0.1.0/assets/demo.gif +0 -0
- langdoctor-0.1.0/assets/demo.tape +31 -0
- langdoctor-0.1.0/examples/vulnerable-agent/Dockerfile +5 -0
- langdoctor-0.1.0/examples/vulnerable-agent/app.py +33 -0
- langdoctor-0.1.0/examples/vulnerable-agent/requirements.txt +5 -0
- langdoctor-0.1.0/langdoctor-spec.md +298 -0
- langdoctor-0.1.0/pyproject.toml +73 -0
- langdoctor-0.1.0/src/langdoctor/__init__.py +7 -0
- langdoctor-0.1.0/src/langdoctor/__main__.py +6 -0
- langdoctor-0.1.0/src/langdoctor/advisories.py +139 -0
- langdoctor-0.1.0/src/langdoctor/analysis.py +106 -0
- langdoctor-0.1.0/src/langdoctor/checks/__init__.py +59 -0
- langdoctor-0.1.0/src/langdoctor/checks/checkpointer.py +105 -0
- langdoctor-0.1.0/src/langdoctor/checks/config.py +123 -0
- langdoctor-0.1.0/src/langdoctor/checks/exposure.py +100 -0
- langdoctor-0.1.0/src/langdoctor/checks/hygiene.py +77 -0
- langdoctor-0.1.0/src/langdoctor/checks/secrets.py +89 -0
- langdoctor-0.1.0/src/langdoctor/checks/versions.py +104 -0
- langdoctor-0.1.0/src/langdoctor/cli.py +126 -0
- langdoctor-0.1.0/src/langdoctor/data/advisories.json +191 -0
- langdoctor-0.1.0/src/langdoctor/engine.py +66 -0
- langdoctor-0.1.0/src/langdoctor/finding.py +49 -0
- langdoctor-0.1.0/src/langdoctor/output/__init__.py +55 -0
- langdoctor-0.1.0/src/langdoctor/output/console.py +126 -0
- langdoctor-0.1.0/src/langdoctor/output/json_out.py +20 -0
- langdoctor-0.1.0/src/langdoctor/output/markdown.py +58 -0
- langdoctor-0.1.0/src/langdoctor/output/sarif.py +83 -0
- langdoctor-0.1.0/src/langdoctor/scanner.py +208 -0
- langdoctor-0.1.0/src/langdoctor/settings.py +31 -0
- langdoctor-0.1.0/src/langdoctor/suppress.py +50 -0
- langdoctor-0.1.0/tests/fixtures/clean_project/requirements.txt +5 -0
- langdoctor-0.1.0/tests/fixtures/vulnerable_project/app.py +8 -0
- langdoctor-0.1.0/tests/fixtures/vulnerable_project/requirements.txt +6 -0
- langdoctor-0.1.0/tests/test_advisories.py +77 -0
- langdoctor-0.1.0/tests/test_checkpointer.py +83 -0
- langdoctor-0.1.0/tests/test_cli.py +94 -0
- langdoctor-0.1.0/tests/test_config.py +85 -0
- langdoctor-0.1.0/tests/test_engine.py +60 -0
- langdoctor-0.1.0/tests/test_exposure.py +85 -0
- langdoctor-0.1.0/tests/test_finding.py +42 -0
- langdoctor-0.1.0/tests/test_hygiene.py +56 -0
- langdoctor-0.1.0/tests/test_output.py +83 -0
- langdoctor-0.1.0/tests/test_scanner.py +74 -0
- langdoctor-0.1.0/tests/test_secrets.py +60 -0
- langdoctor-0.1.0/tests/test_settings.py +28 -0
- langdoctor-0.1.0/tests/test_suppress.py +75 -0
- 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,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.
|
langdoctor-0.1.0/LICENSE
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/langdoctor/)
|
|
38
|
+
[](https://pypi.org/project/langdoctor/)
|
|
39
|
+
[](https://github.com/elaz48/langdoctor/actions/workflows/ci.yml)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
|
|
42
|
+

|
|
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>
|