A supply chain security CLI that scans every repository in a GitHub organization for compromised, malicious, or vulnerable package versions using the GitHub Dependency Graph SBOM API.
Provide a CSV of known-bad packages and versions; ChainWatch walks every repo's SBOM (via gh CLI), performs ecosystem-aware matching, and surfaces any hits — with optional JSON and HTML reports.
- Python 3.11+
- GitHub CLI installed and authenticated (
gh auth login) - Dependency graph enabled on target repos (Settings → Security & analysis → Dependency graph)
# Basic scan — print findings to stdout
python chainwatch.py --csv compromised.csv --org MY_ORG
# Write a JSON report
python chainwatch.py --csv compromised.csv --org MY_ORG --output report.json
# Write a self-contained HTML security report
python chainwatch.py --csv compromised.csv --org MY_ORG --html-report report.html
# Skip archived repos and increase parallelism
python chainwatch.py --csv compromised.csv --org MY_ORG --skip-archived --concurrency 8ChainWatch accepts two CSV formats. Both require an Ecosystem column.
Ecosystem,Package,Compromised Versions
npm,timeago.js,"4.1.2, 4.2.2"
pypi,requests,2.28.0
maven,log4j-core,"2.14.0, 2.14.1"
Ecosystem,Namespace,Name,Version,Artifact,Published,Detected
npm,,timeago.js,4.2.2,...
npm,@openclaw-cn,feishu,0.2.11,...
In Format B, a non-empty Namespace is combined with Name as namespace/name (e.g. @openclaw-cn/feishu).
Rows sharing the same (ecosystem, name) pair are merged; duplicate versions are deduplicated automatically. Rows missing Ecosystem are skipped with a warning.
| Flag | Default | Description |
|---|---|---|
--csv |
required | Path to the compromised packages CSV |
--org |
required | GitHub organization slug |
--output |
— | Write a JSON report to this path |
--html-report |
— | Write a self-contained HTML security report to this path |
--skip-archived |
false | Skip archived repositories |
--concurrency |
5 | Parallel workers (1–10) |
- Load the compromised packages CSV into an in-memory lookup keyed on
(ecosystem, package_name). - List all repositories in the org via
gh repo list(up to 10,000 repos; public and private). - Fetch each repo's SPDX SBOM from the GitHub Dependency Graph API (
GET /repos/{org}/{repo}/dependency-graph/sbom). - Match — for each SBOM package, the ecosystem is extracted from its PURL before lookup. A
pypi/requestsentry in the CSV will never match annpm/requestsdependency. - Report findings to stdout (and optionally to JSON / HTML).
Repos without an SBOM (dependency graph disabled, no manifests) are silently skipped and counted separately.
--------------------------------------------------------------
COMPROMISED PACKAGE SCAN REPORT
--------------------------------------------------------------
Org : my-org
Total repos : 120
Scanned : 98 | Skipped (no SBOM): 22 | Errors: 0
Findings : 3
--------------------------------------------------------------
🚨 HITS FOUND (3):
Repository Package Version Ecosystem
------------------------------------------------------------
my-org/frontend timeago.js 4.2.2 npm
my-org/data-pipeline requests 2.28.0 pypi
my-org/legacy-api log4j-core 2.14.1 maven
{
"org": "my-org",
"summary": {
"repos_scanned": 98,
"repos_skipped": 22,
"repos_errored": 0,
"total_findings": 3
},
"findings": [
{
"org": "my-org",
"repo": "my-org/frontend",
"package_name": "timeago.js",
"matched_version": "4.2.2",
"ecosystem": "npm",
"purl": "pkg:npm/timeago.js@4.2.2"
}
],
"scanned_repos": ["..."],
"skipped_repos": ["..."]
}A self-contained dark-themed HTML file with a summary dashboard, a findings table (with repo links and PURLs), a per-ecosystem color-coded badge system, a scrollable scanned-repos list, and a remediation checklist. No external dependencies — open it in any browser.
| Code | Meaning |
|---|---|
0 |
Scan complete, no findings |
1 |
One or more compromised packages found |
The non-zero exit on findings makes ChainWatch suitable for use as a CI gate.
# 1. Build a watchlist CSV from known-bad packages
echo "Ecosystem,Package,Compromised Versions" > watchlist.csv
echo "npm,malicious-pkg,3.1.4" >> watchlist.csv
# 2. Scan the org
python chainwatch.py \
--csv watchlist.csv \
--org my-org \
--html-report ./incident-$(date +%F).html \
--output ./incident-$(date +%F).json
# 3. Review — affected repos appear in stdout and in the report files
# If malware is confirmed, report to Slack #ask-security- Matching is exact on version string. Semver ranges are not evaluated.
- Only dependencies detected by GitHub's Dependency Graph are visible. Vendored code, non-standard manifests, and repos with the dependency graph disabled will not appear.
gh repo listcaps at 10,000 repos per call. Organizations larger than that are not currently supported.- The SBOM ecosystem field is derived from the PURL; packages without a PURL
externalRefare skipped to avoid false positives.