secret-blast-radius 0.2.0 → 0.3.0

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.
package/README.md CHANGED
@@ -34,6 +34,8 @@ Rotation order if this machine is ever compromised: 1. persistence 2. npm 3. a
34
34
 
35
35
  *(Illustrative output: fingerprints and paths are examples.)*
36
36
 
37
+ **New in 0.3:** `--history` shows the dates a bad version sat in your lockfile; GlassWorm editor-extension check; npm cache check; RubyGems/Cargo/Composer/Terraform/Pulumi credential files; opt-in `--osv` malicious-package lookup; a rotation plan with verify steps and provider docs.
38
+
37
39
  ## Why this exists
38
40
 
39
41
  The last few weeks, in order:
@@ -63,12 +65,16 @@ Every advisory ends the same way: *"rotate everything the machine could access."
63
65
  | `dotenv` | `.env*` files in your projects (names only, never values) |
64
66
  | `env` | Variables a CI step inherits (`NPM_TOKEN`, `AWS_*`, recognised token formats) |
65
67
  | `paas` | Railway, Vercel, Fly, Render, Netlify CLI tokens; Heroku in `~/.netrc`; which projects deploy where |
68
+ | `ecosystems` | RubyGems `~/.gem/credentials`, Cargo `credentials.toml` (the plaintext `cargo:token` store), Composer `auth.json` (global and project), Terraform `credentials.tfrc.json`, Pulumi `credentials.json`, local `.tfstate` files |
66
69
 
67
70
  **Signs you're already hit:**
68
71
 
69
72
  | Scanner | Detects |
70
73
  |---|---|
71
74
  | `malicious` | Known-bad versions in `package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml` (v5–v9), `yarn.lock` (classic + Berry), `bun.lock`, and in `node_modules`: tensorlake + native packages, `@subql/common@5.8.3`, `@7nohe/openapi-react-query-codegen` (10 versions), the keyv/cacheable wave; the `@opensearch/setup` worm marker |
75
+ | `extensions` | Installed VS Code / Cursor / Windsurf / VSCodium / Kiro / remote-server extensions against 149 GlassWorm IOCs from Socket (malicious, version-scoped, and flagged sleepers), including ones pulled in through `extensionPack` |
76
+ | `npm-cache` | Tarballs of known-bad versions in the npm cache index: proof the machine downloaded one even if no lockfile shows it today |
77
+ | `history` (`--history`) | **Exposure window.** Walks each lockfile's git history and reports when a known-bad version entered and left (`2026-10-08 → 2026-10-10`), so you know which CI runs and machines to treat as exposed. `--since 2026-09-01` narrows it |
72
78
  | `persistence` | `gh-token-monitor` (systemd unit, LaunchAgent, Windows dir, script) |
73
79
  | `host-persistence` | Crontab lines that pipe a server into `bash` (GhostAction `#ghc2`), the `manifest-cache.js --warm` process and `/tmp/tmp.ts018051808.lock` (@subql) |
74
80
  | `repo` | Workflows that contact GhostAction infrastructure, dump `git log -p --all`, or serialise `toJSON(secrets)` to the network; `.vscode/tasks.json` that auto-runs a "font"/image; Claude Code hooks that run disguised files; the tensorlake worm branch |
@@ -80,13 +86,23 @@ Every advisory ends the same way: *"rotate everything the machine could access."
80
86
  | `php` | PHP2Shell-vulnerable PHP in Dockerfiles, compose files, `composer.json` platform pins and the local `php` binary. Distro builds are flagged *high, verify backport* rather than critical |
81
87
  | `certs` | PEM/CRT files and `NODE_EXTRA_CA_CERTS` bundles containing or chaining to the distrusted **emSign ECC Root CA - G3** (eMudhra) |
82
88
  | `install-scripts` | Which dependencies run install scripts, straight from the lockfile |
83
- | `hardening` | No `ignore-scripts`, no dependency cooldown (`min-release-age` / `minimumReleaseAge`) |
89
+ | `hardening` | No `ignore-scripts`; no dependency cooldown, with the right setting for the package manager you use (npm `min-release-age`, pnpm `minimumReleaseAge`, Yarn `npmMinimalAgeGate`, Bun `install.minimumReleaseAge`) |
90
+
91
+ **Opt-in online check:** `--osv` sends the package names and versions from your lockfiles (nothing else) to the [OSV API](https://google.github.io/osv.dev/post-v1-querybatch/) and reports `MAL-` advisories from the [OpenSSF malicious-packages](https://github.com/ossf/malicious-packages) feed. That covers far more than the bundled list. Vulnerability (CVE/GHSA) results are ignored on purpose; use OSV-Scanner for those.
92
+
93
+ **Small extras:** least-privilege hints from a token's public prefix (classic `ghp_` PAT, long-lived `AKIA` key), and MCP servers started with an unpinned `npx -y <pkg>`.
94
+
95
+ ## Rotation plan
96
+
97
+ `--runbook` writes an ordered plan: stop the malware, then registry tokens (npm, PyPI, RubyGems, crates.io), GitHub, cloud and IaC, Kubernetes, deploy platforms, CI env, AI keys, SSH, app secrets. Each step has the exact commands, a **verify** check that the old credential is dead, a **redeploy** reminder where running processes keep old values, and links to the provider's own docs. One example of why the details matter: npm says token revocation can take up to an hour.
98
+
99
+ Trusted publishing and provenance tell you *where* a package was built. They don't tell you it's safe: `@subql/common@5.8.3` and `tensorlake@0.5.144` both had valid provenance.
84
100
 
85
101
  ## Incident mode
86
102
 
87
103
  ```bash
88
104
  npx secret-blast-radius --list-incidents
89
- npx secret-blast-radius --incident tensorlake # or: shai-hulud, subql, ghostaction, railway, php2shell, emudhra, sbi
105
+ npx secret-blast-radius --incident tensorlake # or: glassworm, shai-hulud, subql, ghostaction, railway, php2shell, emudhra, sbi
90
106
  ```
91
107
 
92
108
  Runs only the checks that matter for that incident and prints a sourced checklist, marking what's confirmed and what's still under investigation. `--runbook` turns it into a Markdown checklist you can paste into an incident ticket.
@@ -99,7 +115,7 @@ permissions:
99
115
  security-events: write
100
116
  steps:
101
117
  - uses: actions/checkout@v4
102
- - uses: Krrishkumar125/secret-blast-radius@v0.2.0
118
+ - uses: Krrishkumar125/secret-blast-radius@v0.3.0
103
119
  with:
104
120
  fail-on: critical
105
121
  - uses: github/codeql-action/upload-sarif@v3
@@ -114,9 +130,9 @@ SARIF only gets file locations for findings inside the repo. Home-directory find
114
130
  ## Safety
115
131
 
116
132
  - **Read-only.** It only writes files you name (`--runbook-file`, `--sarif-file`).
117
- - **Offline.** It makes no network requests. The IOC list is bundled (`src/iocs.json`, dated). Add your own with `--ioc-file extra.json`.
133
+ - **Offline by default.** It makes no network requests unless you pass `--osv`, which sends package names and versions only. The IOC lists are bundled (`src/iocs.json`, `src/extension-iocs.json`, dated). Add your own with `--ioc-file extra.json` (`packages` and `extensions` keys).
118
134
  - **Secret values are never printed.** Findings show a location and a `sha256:xxxxxxxx` fingerprint. Every output format is tested for leaks.
119
- - **Zero dependencies.** Helper commands (`php -r`, `crontab -l`, `ps -eo args`) are run read-only with a timeout; `--no-exec` disables them.
135
+ - **Zero dependencies.** Helper commands (`php -v`, `crontab -l`, `ps -eo args`, and `git log`/`git show` for `--history`) are run read-only with a timeout; `--no-exec` disables them.
120
136
 
121
137
  ## Options
122
138
 
@@ -128,6 +144,8 @@ SARIF only gets file locations for findings inside the repo. Home-directory find
128
144
  | `--runbook`, `--runbook-file <f>` | Markdown rotation checklist |
129
145
  | `--fail-on <level>` | Exit 1 at/above `critical`, `high`, `medium`, `low` (default `none`) |
130
146
  | `--only` / `--skip <list>` | Choose scanners (names in the tables above) |
147
+ | `--history`, `--since <date>` | Lockfile exposure window from git history |
148
+ | `--osv` | Online, opt-in OSV malicious-package lookup |
131
149
  | `--ioc-file <f>` | Extra IOCs, same shape as `src/iocs.json` (repeatable) |
132
150
  | `--no-env`, `--no-exec`, `--depth <n>`, `--home <dir>`, `--no-color` | Tuning |
133
151
 
@@ -137,18 +155,33 @@ Exit codes: `0` ok, `1` findings at/above `--fail-on`, `2` usage error.
137
155
 
138
156
  These are skipped on purpose: `${VAR}`, `${{ secrets.X }}`, `op://…`, `[YOUR_KEY]`, `your_api_key_here`, `not-a-secret`, prose values like `this-is-a-secret-value`, credential-free URLs (`redis://localhost`, `file:./dev.db`), `.env.example/.sample/.template/.demo`, `node_modules`, ordinary `folderOpen` tasks, and Claude hooks that run normal tools. Every rule above came from running the tool on real repos (TanStack Router, symfony/demo, vercel/next.js, docker-library/wordpress, Railway templates). If you hit a false positive, please open an issue.
139
157
 
158
+ ## Use it with these tools
159
+
160
+ secret-blast-radius answers one question: *what could have been stolen, and what do I rotate first?* For the rest, use the tools built for it:
161
+
162
+ | Need | Use |
163
+ |---|---|
164
+ | Secrets committed to git history | [gitleaks](https://github.com/gitleaks/gitleaks) or [TruffleHog](https://github.com/trufflesecurity/trufflehog) (TruffleHog can also verify whether a key is live) |
165
+ | Vulnerable dependencies (CVEs) across ecosystems | [OSV-Scanner](https://github.com/google/osv-scanner) |
166
+ | GitHub Actions risks: `pull_request_target`, unpinned actions, template injection, excessive permissions | [zizmor](https://github.com/zizmorcore/zizmor) |
167
+ | Blocking exfiltration from CI runners at runtime | [StepSecurity Harden-Runner](https://github.com/step-security/harden-runner) |
168
+ | Vetting packages before you install them | [Socket](https://socket.dev) |
169
+
140
170
  ## Limits
141
171
 
142
172
  - It checks well-known locations. It can't see your OS keychain (that's the point) or browser profiles.
143
- - The IOC list covers the incidents above, not every malicious package ever published. Pair it with `npm audit`, OSV or Socket.
173
+ - The bundled IOC lists cover the incidents above, not every malicious package ever published. Use `--osv` for the full OpenSSF feed.
174
+ - `--history` follows lockfiles that exist today; a lockfile that was deleted or renamed is not followed. On partial (`--filter=blob:none`) clones it fetches old blobs and is slow.
175
+ - Sleeper extensions were benign when Socket found them. They're flagged as high risk, not proven malicious.
144
176
  - For distro PHP builds it can't tell whether your vendor backported the fix.
145
177
  - A clean report shrinks your blast radius. It doesn't prove a machine is clean.
146
178
 
147
179
  ## Programmatic API
148
180
 
149
181
  ```js
150
- import { scan, runbook, toSarif, INCIDENTS } from 'secret-blast-radius';
182
+ import { scan, scanAsync, runbook, toSarif, INCIDENTS } from 'secret-blast-radius';
151
183
  const result = scan({ projects: ['.'], incident: 'railway', exec: false });
184
+ const withOsv = await scanAsync({ projects: ['.'], osv: true, history: { since: '2026-09-01' } });
152
185
  ```
153
186
 
154
187
  MIT © Krrish Kumar
package/action.yml CHANGED
@@ -36,6 +36,10 @@ runs:
36
36
  SBR_SARIF: ${{ inputs.sarif-file }}
37
37
  SBR_VERSION: ${{ inputs.version }}
38
38
  run: |
39
- args=(--only "$SBR_ONLY" --fail-on "$SBR_FAIL_ON" --sarif-file "$SBR_SARIF")
39
+ ws="$(pwd)"
40
+ case "$SBR_SARIF" in /*|[A-Za-z]:*) sarif="$SBR_SARIF" ;; *) sarif="$ws/$SBR_SARIF" ;; esac
41
+ args=(--only "$SBR_ONLY" --fail-on "$SBR_FAIL_ON" --sarif-file "$sarif")
40
42
  if [ -n "$SBR_INCIDENT" ]; then args+=(--incident "$SBR_INCIDENT"); fi
41
- npx --yes "secret-blast-radius@${SBR_VERSION}" "${args[@]}" .
43
+ # Run from a neutral directory so npx never resolves a local package.json instead of the registry.
44
+ cd "${RUNNER_TEMP:-/tmp}"
45
+ npx --yes "secret-blast-radius@${SBR_VERSION}" "${args[@]}" "$ws"
package/bin/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from 'node:util';
3
3
  import { writeFileSync, readFileSync } from 'node:fs';
4
- import { scan, failsAt, formatText, formatMarkdown, runbook, toSarif, INCIDENTS } from '../src/index.js';
4
+ import { scanAsync, failsAt, formatText, formatMarkdown, runbook, toSarif, INCIDENTS } from '../src/index.js';
5
5
 
6
6
  const HELP = `Usage: secret-blast-radius [options] [project-dir...]
7
7
 
@@ -11,16 +11,21 @@ Read-only. Never prints secret values.
11
11
 
12
12
  Options:
13
13
  --incident <name> Tailored checklist + only the relevant checks. Names:
14
- php2shell, railway, tensorlake, shai-hulud, subql, ghostaction, emudhra, sbi
14
+ php2shell, glassworm, railway, tensorlake, shai-hulud, subql, ghostaction, emudhra, sbi
15
15
  --list-incidents Show incident names and status
16
16
  --json Machine-readable output
17
17
  --sarif [file] SARIF 2.1.0 for GitHub code scanning (stdout, or --sarif-file <f>)
18
18
  --ioc-file <f> Extra IOC JSON (same shape as src/iocs.json); repeatable
19
- --no-exec Never run helper commands (php -r, crontab -l, ps)
19
+ --no-exec Never run helper commands (php -v, crontab -l, ps, git)
20
+ --history Exposure window: walk lockfile git history for known-bad versions (runs git)
21
+ --since <date> Limit --history to commits since a date (e.g. 2026-09-01)
22
+ --osv ONLINE, opt-in: send lockfile package names+versions (nothing else) to
23
+ api.osv.dev and report MAL- malicious-package advisories
20
24
  --runbook [file] Print (or write to file) a Markdown rotation runbook
21
25
  --fail-on <level> Exit 1 if any finding is at/above level: critical|high|medium|low|none (default: none)
22
26
  --only <list> Comma list of scanners: persistence,npm,github,cloud,kubernetes,env,ai,ssh,dotenv,
23
- hardening,malicious,repo,host-persistence,php,paas,certs,install-scripts
27
+ hardening,malicious,repo,host-persistence,php,paas,certs,install-scripts,
28
+ ecosystems,extensions,npm-cache,history
24
29
  --skip <list> Comma list of scanners to skip
25
30
  --no-env Do not inspect environment variables
26
31
  --depth <n> How deep to look for .env files in project dirs (default 4)
@@ -36,7 +41,7 @@ try {
36
41
  allowPositionals: true,
37
42
  options: {
38
43
  json: { type: 'boolean' }, sarif: { type: 'boolean' }, 'sarif-file': { type: 'string' },
39
- incident: { type: 'string' }, 'list-incidents': { type: 'boolean' }, 'ioc-file': { type: 'string', multiple: true }, 'no-exec': { type: 'boolean' }, runbook: { type: 'boolean' }, 'runbook-file': { type: 'string' },
44
+ incident: { type: 'string' }, 'list-incidents': { type: 'boolean' }, 'ioc-file': { type: 'string', multiple: true }, 'no-exec': { type: 'boolean' }, history: { type: 'boolean' }, since: { type: 'string' }, osv: { type: 'boolean' }, runbook: { type: 'boolean' }, 'runbook-file': { type: 'string' },
40
45
  'fail-on': { type: 'string', default: 'none' }, only: { type: 'string' }, skip: { type: 'string' },
41
46
  'no-env': { type: 'boolean' }, depth: { type: 'string' }, home: { type: 'string' },
42
47
  'no-color': { type: 'boolean' }, version: { type: 'boolean', short: 'v' }, help: { type: 'boolean', short: 'h' },
@@ -67,7 +72,8 @@ if (o['no-env']) skip.push('env');
67
72
 
68
73
  let result;
69
74
  try {
70
- result = scan({ projects, home: o.home, only: list(o.only), skip, depth: o.depth ? Number(o.depth) : undefined, incident: o.incident, iocFiles: o['ioc-file'] ?? [], exec: o['no-exec'] ? false : undefined });
75
+ if (o.since && !/^\d{4}-\d{2}-\d{2}/.test(o.since)) throw new Error('--since expects a date like 2026-09-01');
76
+ result = await scanAsync({ osv: o.osv, history: o.history || o.since || list(o.only)?.includes('history') ? { since: o.since } : null, projects, home: o.home, only: list(o.only), skip, depth: o.depth ? Number(o.depth) : undefined, incident: o.incident, iocFiles: o['ioc-file'] ?? [], exec: o['no-exec'] ? false : undefined });
71
77
  failsAt(result, o['fail-on']);
72
78
  } catch (e) {
73
79
  console.error(`secret-blast-radius: ${e.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secret-blast-radius",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "What would a malicious npm install steal from this machine or CI runner? Ranked inventory of reachable credentials, known-worm IOCs (Shai-Hulud, @subql, GhostAction), PHP2Shell and PaaS exposure checks, ordered rotation runbook, SARIF. Read-only, offline, never prints secrets.",
5
5
  "license": "MIT",
6
6
  "author": "Krrish Kumar (https://github.com/Krrishkumar125)",
@@ -26,7 +26,13 @@
26
26
  "php2shell",
27
27
  "ghostaction",
28
28
  "railway",
29
- "github-action"
29
+ "github-action",
30
+ "vscode-extensions",
31
+ "glassworm",
32
+ "osv",
33
+ "rubygems",
34
+ "cargo",
35
+ "terraform"
30
36
  ],
31
37
  "type": "module",
32
38
  "bin": {
@@ -46,7 +52,7 @@
46
52
  "node": ">=18.18"
47
53
  },
48
54
  "scripts": {
49
- "test": "node --test test/scanners.test.js test/supply.test.js test/cli.test.js",
55
+ "test": "node --test test/scanners.test.js test/supply.test.js test/cli.test.js test/v3.test.js",
50
56
  "check": "node --check bin/cli.js && npm test"
51
57
  }
52
58
  }