Dependency vulnerability scanner that tells you which CVEs actually reach your code.
npx thicket # scan cwd
npx thicket ../other-project # scan a local path
npx thicket vercel/next.js # shallow-clone over SSH, then scanScan runs to completion, then a local server starts on an OS-assigned port and your browser opens. Progress goes to stderr so --json stays pipeable.
You need: Node.js 20+, npm, and git. For owner/repo scans, an SSH key that can reach GitHub (ssh -T git@github.com).
# 1. Clone and enter the repo
git clone git@github.com:YOUR_USER/Thicket.git
cd Thicket
# 2. Install deps (also builds the web UI via prepare)
npm install
# 3. Scan this project — browser opens when it finishes
node bin/thicket.js
# Or scan another app / a GitHub slug
node bin/thicket.js ~/code/my-app
node bin/thicket.js axios/axiosUseful one-liners:
node bin/thicket.js --json # machine-readable report on stdout
node bin/thicket.js --json --fail-on high # CI exit code when findings are high+
node bin/thicket.js --no-open --port 8765 # serve UI without opening a browser
node bin/thicket.js --helpOptional AI triage (never required):
export ANTHROPIC_API_KEY=sk-ant-...
node bin/thicket.js --aiLink the CLI on your PATH while developing:
npm link
thicket .| Command | What it does |
|---|---|
thicket |
Scan cwd, open the UI |
thicket ../app |
Scan a local project |
thicket owner/repo |
Clone git@github.com:owner/repo.git (SSH + your agent) and scan |
thicket --json |
Print the report to stdout, no server |
thicket --json --fail-on high |
CI: non-zero exit if any finding is high or critical |
thicket --no-open --port 8765 |
Serve the UI without opening a browser |
thicket --ai |
Add one-sentence triage when ANTHROPIC_API_KEY is set |
--fail-on accepts unknown, low, moderate, high, or critical. The named level is a minimum.
--ai is optional and free to skip. No key means no request and no slowdown. A missing key never fails a scan.
Each finding is tagged imported directly or not directly imported. That is a measurement of static import / require / import() specifiers in first-party source, not a verdict.
- Import analysis is static and text-based. Dynamic requires, aliases, re-exports, and bundler-resolved paths are missed.
- “Not directly imported” is not a safety claim. Treat it as “we did not see this package name in first-party source.”
- Only npm
package-lock.jsonlockfileVersion 3 is supported.yarn.lock,pnpm-lock.yaml, and lockfile v1/v2 are rejected. - Install-path depth is not dependency depth. npm hoists, so a transitive package often sits at path depth 1.
- OSV coverage is only as good as the database. A clean report means no matching advisories, not that the tree is safe.
ISC