Skip to content

Install ThreatCrush from npm; drop the PRD's non-existent hosts - #5

Merged
ralyodio merged 3 commits into
masterfrom
real-scanner
Aug 1, 2026
Merged

Install ThreatCrush from npm; drop the PRD's non-existent hosts#5
ralyodio merged 3 commits into
masterfrom
real-scanner

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

cli.threatcrush.com and api.sh1pt.com don't resolve — every run so far scanned nothing.

ThreatCrush actually ships as @profullstack/threatcrush on npm (bin: threatcrush), which is what threatcrush.com/install.sh installs after bootstrapping mise + Node. In CI that bootstrap is redundant, so this runs npm install -g directly, with the official installer as fallback.

Pinned to Node 20 — the CLI needs better-sqlite3, and 20 is the newest runtime with reliable prebuilds. On Node 24 it falls through to a node-gyp source build (that's how I found it).

Also adds a Record the CLI interface step: the scan flags came from the PRD and were never verified against a real binary, so the log now prints --help for the CLI and its scan subcommand. Correct the scan step from that evidence.

🤖 Generated with Claude Code

The PRD's distribution URLs do not exist. Neither cli.threatcrush.com nor
api.sh1pt.com resolves in public DNS, so every run so far took the failure
path and scanned nothing.

ThreatCrush actually ships as an npm package, @profullstack/threatcrush
(bin: threatcrush), which is what https://threatcrush.com/install.sh installs
after bootstrapping mise and Node. In CI that bootstrap is redundant, so the
workflow now runs npm install -g directly and keeps the official installer as
a fallback.

Pinned to Node 20: the CLI depends on better-sqlite3, and 20 is the newest
runtime with reliable prebuilds. On Node 24 the install falls through to a
node-gyp source build, which is how this was diagnosed locally.

Also adds a 'Record the CLI interface' step. The scan flags in this workflow
came from the PRD and have never been checked against a real binary, so the
run log now prints --help for both the CLI and its scan subcommand. Correct
the scan step from that evidence rather than from the PRD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

ThreatCrush scan results

9 finding(s) from threatcrush.

Severity Count
High / Critical 8
Medium 1

Detection coverage — FAIL

Metric Result
True positive rate 15.58% (12/77)
False positive rate 0.0% (0/39)
Missed detections 65

Findings

Severity Rule CWE Location
High / Critical threatcrush.aws-access-key vulns/secrets/aws-credentials-hardcoded.env:23
High / Critical threatcrush.stripe-key vulns/secrets/aws-credentials-hardcoded.env:36
High / Critical threatcrush.github-token vulns/secrets/github-pat-in-code.js:25
High / Critical threatcrush.slack-token vulns/secrets/slack-webhook-url.py:40
High / Critical threatcrush.sensitive-file vulns/secrets/aws-credentials-hardcoded.env:1
High / Critical threatcrush.database-url vulns/secrets/aws-credentials-hardcoded.env:30
High / Critical threatcrush.database-url vulns/secrets/aws-credentials-hardcoded.env:32
High / Critical threatcrush.generic-secret vulns/secrets/slack-webhook-url.py:41
Medium threatcrush.hex-token-32 vulns/secrets/slack-webhook-url.py:41

Every file under vulns/ contains intentional vulnerabilities used to
measure scanner accuracy. Findings there are the expected outcome, not a
regression. See docs/VULNERABILITY_CATALOG.md.

Posted by scripts/generate-report.py.

claude added 2 commits August 1, 2026 06:05
Run 30686062988 installed the scanner successfully, then the scan died with
'error: unknown option --format' — and the job still went green. Two separate
faults, both fixed here.

1. The scan invocation was wrong. Confirmed from the published bundle:

     .command("scan").argument("[path]", "Path to scan", ".")

   No options exist. --format/--output/--config/--fail-on/--verbose are all
   inventions of the PRD, and scan takes a PATH, not a pull request URL. That
   settles PRD Open Question 1: there is no native PR-level scanning. Diff-only
   scoping now happens in the workflow, which scans just the changed files
   under vulns/ on a pull request.

2. The failsafe only checked whether the CLI installed, so a scan that ran and
   failed still passed. It now requires that the scan produced usable SARIF.
   A green check must mean the corpus was actually scanned.

Because the CLI cannot emit SARIF, scripts/threatcrush-to-sarif.py translates
its output. It fails closed: if the format is unrecognised it exits non-zero
and dumps the first 40 lines rather than emitting empty SARIF, since '0
findings' is indistinguishable from a clean scan and would be a silent false
negative on a repository that exists to contain findings. Handles JSON output
too, in case the CLI gains it.

Verified across six paths: text, JSON, clean-scan, unparseable, missing input,
and end-to-end scoring of converted SARIF through validate-coverage.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Run 30687082490 finally produced real scanner output: 9 findings (4 critical,
4 high, 1 medium), all secrets. Two defects showed up in how they were mapped.

1. Path mapping. The CLI reports paths relative to the directory it was given,
   so scanning 'vulns' yields 'secrets/aws-credentials-hardcoded.env'. Those
   never matched the catalog's repository-relative paths, so all 9 findings
   scored as 'outside corpus' and the true positive rate stayed at 0% despite
   the scan working. --path-prefix restores the prefix.

2. Output parsing. The output is a multi-line block per finding — a severity
   and title line, then 'File:', then 'Info:' — not one finding per line.
   Severity is bare for CRITICAL and bracketed for the rest. 'Code:' lines are
   skipped: they are redacted excerpts, and matching them double-counted every
   finding. Whole-file findings report line :0, which SARIF rejects; clamped.

The scan step now always scans the whole corpus rather than a changed-file
list, so the prefix cannot vary between invocations. The corpus is 31 files
and scans in seconds, so diff-only scoping bought nothing but ambiguity.

Real output is committed as tests/fixtures/threatcrush-scan-output.txt so the
parser has a regression test against actual CLI behaviour rather than a guess.

Measured result: 15.58% TPR (12/77), 0 false positives, 0 unattributed. That
is a genuine reading — ThreatCrush is a secrets scanner, and it catches the
credential fixtures while not attempting the code-level vulnerabilities.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 99af6b0 into master Aug 1, 2026
4 checks passed
@ralyodio
ralyodio deleted the real-scanner branch August 1, 2026 06:09
ralyodio added a commit that referenced this pull request Aug 1, 2026
The PRD's distribution hosts do not resolve and its scan flags do not exist.
ThreatCrush ships as @profullstack/threatcrush, and scan takes a path and no
options. Adds a text-to-SARIF converter that fails closed, and stops the job
reporting success when the scan produced nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants