You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(scan): add LLM-based malicious-code scanner for installed packages
Adds `cull scan PATH...` alongside the existing `cull check`. Discovers
installed npm/Python packages from node_modules / site-packages, filters
to scannable source/manifest files, splits each file into character-based
sliding-window chunks (full coverage, including minified bundles), and
classifies every chunk through an OpenAI-compatible chat-completions API
(Gemini 3.1 Flash-Lite by default; any local server such as Ollama,
llama.cpp, vLLM, or LM Studio works via --base-url / --model).
- Two-phase flow: preflight estimate (packages, files, chunks, tokens,
cost) before any LLM call; --estimate-only to stop there;
--budget-usd to abort once actual spend exceeds the cap.
- Verdict cache at ~/.cache/cull/verdicts.json keyed by
sha256(chunk):model:prompt_version, so model swaps and prompt bumps
invalidate automatically and identical chunks dedup across packages.
- Concurrent scanning via ThreadPoolExecutor (default 8 workers);
cancels pending futures on budget abort and flushes cache from a
finally so partial-run results are never lost.
- JSON / Markdown report output (-o report.{json,md}) plus a stdout
--json mode for piping.
- Dockerfile.sandbox + scripts/fetch-datadog-npm-samples.sh for safely
benchmarking against Datadog's malicious-package dataset.
- 24 unit tests covering chunker coverage on minified payloads, filter
rules, verdict merging/dedup, cache round-trip, and pricing.
- README documents both the default (Gemini) and local-model setup,
the dataset benchmark workflow, and what is/isn't stored in cache.
Pure stdlib, no new runtime dependencies.
Find compromised npm packages across your infrastructure. Only Python stdlib, no dependencies.
3
+
Find compromised packages and suspicious package code. Only Python stdlib, no dependencies.
4
4
5
5
## Install
6
6
@@ -12,63 +12,124 @@ python3 -m pip install .
12
12
13
13
We deliberately do not publish to PyPI to avoid creating another supply-chain distribution point for a security tool. Install from a reviewed git clone instead.
14
14
15
-
## Usage
15
+
## Commands
16
+
17
+
### `cull check`
18
+
19
+
Deterministically search for known compromised package names/versions across lock files, `node_modules`, GitHub code search, Docker images, GCR, and Artifact Registry.
Checks lock files (`pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `bun.lock`), `node_modules`, GitHub code search, and Docker image layers (legacy + OCI). Version-aware — distinguishes compromised versions from safe pins. Exit code `0` when clean, `1` when a compromised package is found, and `2` when the scan could not complete reliably.
--budget-usd 1.00 # abort if estimate or actual cost exceeds budget
67
+
--concurrency 4 # default 8
68
+
--no-cache # disable ~/.cache/cull/verdicts.json
69
+
--include-tests # include test/spec dirs
70
+
-o report.json # full JSON report
71
+
-o report.md # full Markdown report
72
+
--json # write JSON result to stdout
38
73
```
39
74
40
-
Searches lock files via [code search API](https://docs.github.com/en/rest/search/search#search-code). Token can also be passed via `--github-token`.
75
+
Default model is `gemini-3.1-flash-lite-preview` against `https://generativelanguage.googleapis.com/v1beta/openai`, reading `GEMINI_API_KEY`. Swap any OpenAI-compatible provider with `--model`, `--base-url`, and `--api-key-env`.
76
+
77
+
See `[examples/ngx-perfect-scrollbar.md](examples/ngx-perfect-scrollbar.md)` for a real report against a known-malicious Shai-Hulud sample.
41
78
42
-
**Creating a PAT** — [github.com/settings/tokens](https://github.com/settings/tokens): classic → `repo` scope, fine-grained → set resource owner to your org, grant `Contents: Read-only`.
79
+
### Local model
43
80
44
-
### Docker images
81
+
Any local server that speaks the OpenAI `/v1/chat/completions` protocol (Ollama, llama.cpp `llama-server`, vLLM, LM Studio, …) works. Point `cull` at it with `--base-url` and `--model`. Most local servers ignore the API key — set any non-empty value so `cull` proceeds.
Requires `docker` CLI. Remote images are auto-pulled; use `--no-pull` to skip.
91
+
Local providers usually report no token usage, so the cost column will read `$0.0000`. The verdict cache is keyed by model id, so switching providers re-scans every chunk.
52
92
53
-
### Google Cloud
93
+
## Sandbox
94
+
95
+
Build a minimal Docker sandbox with `cull` installed:
Requires `gcloud` CLI with `gcloud auth login` and `gcloud auth configure-docker REGION-docker.pkg.dev`.
102
+
## Benchmark against Datadog's malicious package dataset
61
103
62
-
## Requirements
104
+
Datadog publishes ~3,000 real-world malicious npm packages as password-protected zips ([dataset](https://github.com/DataDog/malicious-software-packages-dataset)). The password is `infected`
63
105
64
-
Python 3.9+. Optional CLIs: `docker`, `gcloud`. If you request a scan target whose CLI is missing or whose backend calls fail, `cull` reports an error and exits non-zero instead of silently treating that target as clean.
106
+
Fetch the npm samples (sparse-checkout, no full repo history):
65
107
66
-
## Security
108
+
```bash
109
+
scripts/fetch-datadog-npm-samples.sh
110
+
```
67
111
68
-
We intentionally do not publish this to PyPI. The goal is to avoid creating another supply-chain distribution point for a security tool. Install from a reviewed git clone instead.
112
+
Extract and scan **inside the sandbox** — the archives are real malware, never `npm install` them on your host.
69
113
70
-
Stdlib only — nothing else to supply-chain. External CLIs invoked only when their flags are used. Images are exported via `docker save` / `docker pull` — never `docker run`.
PRs welcome — GitLab, Bitbucket, AWS ECR, and Azure ACR are natural next targets.
135
+
Stdlib only. `cull scan` reads installed package files and sends selected source chunks to the configured LLM provider. It does not execute package code.
0 commit comments