Skip to content

Improve supply chain attack scan reporting and failure handling - #32

Merged
JosephMaynard merged 6 commits into
masterfrom
feat/supply-chain-attack-scan-improvements
May 12, 2026
Merged

JosephMaynard merged 6 commits into
masterfrom
feat/supply-chain-attack-scan-improvements

Conversation

@JosephMaynard

@JosephMaynard JosephMaynard commented May 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add supply chain attack scan improvements across the CLI, aggregation, and reporting layers.
  • Expand npm registry metadata handling and related finding/explanation logic.
  • Update the report UI and README to reflect the new behavior.
  • Add and adjust tests for aggregator, CLI, fail-on logic, and registry metadata parsing.

Testing

  • Automated test coverage updated for the affected CLI, aggregator, fail-on, and metadata code paths.
  • Report UI changes validated through the existing test suite and local build checks.
  • Not run (not requested).

Summary by CodeRabbit

  • New Features

    • Compare-mode fail-on rules for newly introduced execution/packaging/registry traits with per-violation details and CI guardrail workflow.
    • Targeted registry metadata enrichment for suspicious packages (capped lookups, skipped in offline mode) and derived registry risk signals.
    • Enhanced detection and reporting of local execution and packaging signals; UI/report now shows Packaging, Local execution, and Registry metadata sections.
  • Documentation

    • Clarified offline behavior, compare mode, usage, and recommended CI baseline workflow.
  • Tests

    • Extensive coverage for compare behavior, registry enrichment, packaging detection, and execution-signal detection.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds packaging and local execution signal detection, candidate-limited npm registry metadata enrichment, compare-mode "new-*" policy evaluation, and end-to-end integration across types, aggregator, runners, CLI, UI, findings, tests, and README docs.

Changes

Supply-Chain Signal Detection and Compare-Mode Framework

Layer / File(s) Summary
Core Type System Extension for Signals and Enrichment
src/types.ts, report-ui/types.ts
PackagingSignal and RegistryRiskSignal unions added; DependencyExecutionInfo gains optional signals; new DependencyPackagingInfo and registry enrichment shapes model packaging and registry metadata.
Packaging Signal Detection and Bundled Dependency Analysis
src/aggregator.ts, src/aggregator.test.ts
Aggregators detect bundleDependencies/shrinkwrap, normalize bundledDependencies, compute packaging signals, and emit DependencyRecord.packaging with signals and bundledDependencies.
Local Execution Signal Detection and Bounded File Inspection
src/aggregator.ts, src/aggregator.test.ts
Exports detectLocalExecutionSignals and collectPackageExecutionSignals; applies bounded file/byte/package limits, derives candidate entry/bin/export targets, inspects a capped set of files, broadens static heuristics (network, env, home, SSH, child-process), and merges script-derived and package-derived execution signals.
NPM Registry Metadata Enrichment and Risk Signal Derivation
src/runners/npmRegistryMetadata.ts, src/runners/npmRegistryMetadata.test.ts
New runner parses npm view --json, derives registry risk signals (recency, reactivation, short history, major/patch patterns), selects suspicious candidates (scored, capped), fetches metadata in bounded batches, and attaches per-dependency supplyChain.registry enrichment (attempted/ok, candidate reasons, metadata, signals).
Compare-Mode Delta Policy Evaluation and Rule Expansion
src/failOn.ts, src/failOn.test.ts
Adds many new-* compare-mode rules to FailOnRule, extends PolicyViolation with details, and implements evaluateComparePolicyViolations(previous, current, rules) to emit violations for newly introduced execution/packaging/registry/install/bin/direct traits by diffing scans.
CLI Registry Enrichment Wiring and Compare-Mode Integration
src/cli.ts, src/cli.test.ts
CLI wires enrichAggregatedWithRegistryMetadata when enabled, updates --offline semantics to include registry-backed checks, prints per-violation details, builds findings from enrichment output, and combines scan + compare violations for compare.
Report UI, Explain Output, and Findings Generation
report-ui/main.ts, src/explain.ts, src/findings.ts, report-ui/types.ts
Report UI renders new "Package contents" and "Registry metadata" subsections; formatExplainOutput() shows local execution, packaging, and registry signals; findings include local-execution-signals, packaging-signals, and per-registry registry-* findings.
Documentation: Capabilities, Workflow, and Rules Reference
README.md
README updated with CI-oriented compare --fail-on example and new-* rule table, clarified --offline behavior, targeted registry enrichment step (capped/skipped with --offline), packaging/local execution signal descriptions, and CI baseline workflow guidance.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I sniffed the tree of packages wide,
Found bundled seeds and scripts that hide.
A registry whisper, a new-signal cheer—
Compare-mode watches what did appear.
Rabbit hops, the report is near.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main objective of the changeset: improving supply chain attack scan reporting and failure handling, which aligns with the substantial additions to registry enrichment, execution/packaging signal detection, and compare-mode policy evaluation.
Docstring Coverage ✅ Passed Docstring coverage is 83.95% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/supply-chain-attack-scan-improvements

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cli.ts (1)

1352-1355: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update --fail-on help to advertise the new compare-mode rules.

parseFailOnRules() now accepts the new-* delta rules, but the CLI help still lists only the old set. That makes the new feature hard to discover from the terminal.

Suggested fix
   --open             Open the generated report using the system default application
   --fail-on <rules>  Fail with exit code 1 when selected rules are violated
-                     Supported: reachable-vuln, production-vuln, high-severity-vuln,
-                                licence-mismatch, copyleft-detected, unknown-licence,
-                                supply-chain-source
+                     Supported: ${SUPPORTED_FAIL_ON_RULES.join(', ')}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli.ts` around lines 1352 - 1355, The --fail-on option help text is out
of date and doesn't advertise the new compare-mode "new-*" delta rules accepted
by parseFailOnRules(); update the CLI option help string (the --fail-on
description in src/cli.ts where the option is declared) to list the
corresponding new-* rules (e.g., new-production-vuln, new-high-severity-vuln,
new-licence-mismatch, new-copyleft-detected, new-unknown-licence,
new-supply-chain-source) alongside the existing rules so terminal help reflects
parseFailOnRules() supported values and formatting remains consistent with the
existing multi-line description.
🧹 Nitpick comments (2)
src/runners/npmRegistryMetadata.ts (1)

269-271: ⚡ Quick win

Registry lookups are fully sequential; this will stretch scan time unnecessarily.

Running fetches one-by-one makes total latency roughly sum of each request. A small bounded parallelism would keep scan time predictable.

Suggested patch
-  for (const candidate of candidates) {
-    results.set(candidate.name, await fetcher(candidate.name));
-  }
+  await Promise.all(
+    candidates.map(async (candidate) => {
+      results.set(candidate.name, await fetcher(candidate.name));
+    })
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runners/npmRegistryMetadata.ts` around lines 269 - 271, The current loop
performs registry fetches sequentially by awaiting fetcher(candidate.name)
inside the for-loop, which slows scans; change this to bounded parallelism:
create promises for candidates using fetcher(candidate.name) but run them with a
concurrency limit (e.g., use a batching loop, Promise.allSettled on slices, or a
p-limit helper) and then populate results.set(candidate.name, ...) from the
settled results; reference the existing identifiers candidates, fetcher, and
results and ensure errors from failed promises are handled/logged and do not
block other results.
README.md (1)

39-39: ⚡ Quick win

Consider breaking down the long feature description.

Line 39 packs many distinct signal types into a single sentence (>200 characters), which may be harder for users to parse. The phrase "capped registry metadata heuristics for already suspicious packages" could also be clearer.

📝 Suggested simplification
-- **Local and targeted supply-chain review signals** — flags git/local/tarball sources, missing integrity, unexpected registry hosts, install-time behavior, bounded local execution capability signals, packaging signals, optional npm signature/provenance verification, and capped registry metadata heuristics for already suspicious packages
+- **Local and targeted supply-chain review signals** — flags git/local/tarball sources, missing integrity, unexpected registry hosts, install-time behavior (lifecycle hooks), local execution capability signals, packaging signals, optional npm signature/provenance verification, and targeted registry metadata enrichment for suspicious packages (capped to 10 lookups)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 39, The long bullet starting with "**Local and targeted
supply-chain review signals**" is overloaded and hard to parse; split it into
3–4 shorter bullets or sentences each describing one signal group (e.g., source
flags like git/local/tarball; integrity/registry and host anomalies;
install-time and bounded local execution signals; packaging and optional npm
signature/provenance verification). Replace the phrase "capped registry metadata
heuristics for already suspicious packages" with a clearer wording such as
"limited registry-metadata heuristics applied only to packages already flagged
as suspicious" and ensure each new line is concise and parallel in structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aggregator.ts`:
- Around line 1923-1943: isInspectableSourceFile currently rejects extensionless
entry/bin targets so readInspectablePackageFile skips them; update
isInspectableSourceFile (and any checks in readInspectablePackageFile and
looksMinified usage) to treat extensionless paths as inspectable when the target
file exists and is a text/javascript-like file: detect lack of extension via
path.extname(filePath) === '' and then stat the resolvedPath, read a small head
of the file, ensure it's not binary (check for null bytes or use a simple text
heuristic) and that looksMinified(text) is false before returning true; ensure
readInspectablePackageFile no longer filters out extensionless filePath values
but still enforces the resolvedPath sandbox check and maxBytes limits; apply the
same change to the other similar checksite referenced in the review.

In `@src/cli.test.ts`:
- Around line 598-618: The test's fake npm executable (created at fakeNpmPath)
lacks a Windows wrapper so spawn('npm', ...) fails on Windows; add creation of
an npm.cmd file in the same binDir alongside the fakeNpmPath that forwards all
arguments to the Node script (the fake npm file) so PATHEXT resolution finds it
on Windows; ensure the wrapper is written to path.join(binDir, 'npm.cmd') and
made executable/accessible before writing package.json so tests invoke the fake
npm on Windows as well as UNIX.

In `@src/cli.ts`:
- Around line 2128-2140: The targeted registry enrichment call
enrichAggregatedWithRegistryMetadata inside the opts.outdated branch can throw
and currently will abort executeAnalysis; wrap that call in a try/catch so
failures are non-fatal: on error log or spinner.warn a concise message including
the error, set a safe default (e.g. registryEnrichment with succeeded:0 and
attempted:0) and continue so buildDependencyFindings and the rest of
executeAnalysis still run; ensure you still call buildDependencyFindings only
when registryEnrichment.attempted > 0 as before.

In `@src/runners/npmRegistryMetadata.ts`:
- Around line 189-193: The current candidate capping sorts by package name using
byName.entries(), which lets alphabetical ordering exclude higher-risk packages;
change the sort to prioritize suspiciousness (e.g., sort entries by reasons.size
or a computed risk score descending) and only use name.localeCompare as a
tie-breaker—update the pipeline that starts with Array.from(byName.entries())
and the subsequent .sort(...) so .slice(0, limit) keeps the top N most
suspicious packages before mapping to { name, reasons }.

---

Outside diff comments:
In `@src/cli.ts`:
- Around line 1352-1355: The --fail-on option help text is out of date and
doesn't advertise the new compare-mode "new-*" delta rules accepted by
parseFailOnRules(); update the CLI option help string (the --fail-on description
in src/cli.ts where the option is declared) to list the corresponding new-*
rules (e.g., new-production-vuln, new-high-severity-vuln, new-licence-mismatch,
new-copyleft-detected, new-unknown-licence, new-supply-chain-source) alongside
the existing rules so terminal help reflects parseFailOnRules() supported values
and formatting remains consistent with the existing multi-line description.

---

Nitpick comments:
In `@README.md`:
- Line 39: The long bullet starting with "**Local and targeted supply-chain
review signals**" is overloaded and hard to parse; split it into 3–4 shorter
bullets or sentences each describing one signal group (e.g., source flags like
git/local/tarball; integrity/registry and host anomalies; install-time and
bounded local execution signals; packaging and optional npm signature/provenance
verification). Replace the phrase "capped registry metadata heuristics for
already suspicious packages" with a clearer wording such as "limited
registry-metadata heuristics applied only to packages already flagged as
suspicious" and ensure each new line is concise and parallel in structure.

In `@src/runners/npmRegistryMetadata.ts`:
- Around line 269-271: The current loop performs registry fetches sequentially
by awaiting fetcher(candidate.name) inside the for-loop, which slows scans;
change this to bounded parallelism: create promises for candidates using
fetcher(candidate.name) but run them with a concurrency limit (e.g., use a
batching loop, Promise.allSettled on slices, or a p-limit helper) and then
populate results.set(candidate.name, ...) from the settled results; reference
the existing identifiers candidates, fetcher, and results and ensure errors from
failed promises are handled/logged and do not block other results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d534c3c4-3dd3-4398-a6c9-9af92e3c084c

📥 Commits

Reviewing files that changed from the base of the PR and between d05b41a and d7154dd.

📒 Files selected for processing (15)
  • README.md
  • report-ui/main.ts
  • report-ui/types.ts
  • src/aggregator.test.ts
  • src/aggregator.ts
  • src/cli.test.ts
  • src/cli.ts
  • src/explain.ts
  • src/failOn.test.ts
  • src/failOn.ts
  • src/findings.ts
  • src/report-assets.ts
  • src/runners/npmRegistryMetadata.test.ts
  • src/runners/npmRegistryMetadata.ts
  • src/types.ts

Comment thread src/aggregator.ts Outdated
Comment thread src/cli.test.ts
Comment thread src/cli.ts
Comment thread src/runners/npmRegistryMetadata.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

108-108: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify --offline behavior in the options table

Line 108 is now incomplete relative to the rest of this README: it says --offline skips only npm audit/npm outdated, but later sections also state signature checks and targeted registry enrichment are skipped.

Suggested doc fix
-| `--offline` | Skip `npm audit` and `npm outdated` (useful for offline/air-gapped scans) |
+| `--offline` | Skip registry-dependent checks (`npm audit`, `npm outdated`, optional signature checks, and targeted registry enrichment) (useful for offline/air-gapped scans) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 108, Update the README options table entry for `--offline`
to reflect its full behavior: change the short description to say it skips npm
audit and npm outdated as well as signature verification checks and targeted
registry enrichment (used for registry lookups), and ensure wording matches
later sections by using the exact flag name `--offline` and mentioning
"signature checks" and "targeted registry enrichment" so the table and detailed
docs are consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aggregator.ts`:
- Around line 1563-1589: normalizeBundledDependencies currently only handles
array forms and misses the case where pkg.bundleDependencies or
pkg.bundledDependencies is the boolean true (meaning “bundle all deps”); update
normalizeBundledDependencies to detect when either field is exactly true and
return a sentinel entry (e.g. ['*'] or another agreed-upon string) so
derivePackagingInfo will still add the 'bundled-dependencies' signal; keep the
existing trimming/uniq/sort behavior for array inputs and ensure
derivePackagingInfo continues to add bundledDependencies when the returned array
has length > 0.
- Around line 1884-1904: packageEntryTargets currently only handles string and
one-level object exports and ignores arrays and deeper nesting; update
packageEntryTargets to recursively traverse pkg.exports (and any nested
objects/arrays) to collect all string export paths: implement a helper (or make
add accept arrays/objects) that, given a value, if it's a string calls
normalizePackageRelativePath and adds it, if it's an array iterates and
recurses, and if it's an object iterates Object.values and recurses; keep using
normalizePackageRelativePath and the existing fallback to add 'index.js' when no
targets found.

---

Outside diff comments:
In `@README.md`:
- Line 108: Update the README options table entry for `--offline` to reflect its
full behavior: change the short description to say it skips npm audit and npm
outdated as well as signature verification checks and targeted registry
enrichment (used for registry lookups), and ensure wording matches later
sections by using the exact flag name `--offline` and mentioning "signature
checks" and "targeted registry enrichment" so the table and detailed docs are
consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2aca15b-bd15-4775-acd4-b6bacb1d9fa8

📥 Commits

Reviewing files that changed from the base of the PR and between d7154dd and 89bb128.

📒 Files selected for processing (7)
  • README.md
  • src/aggregator.test.ts
  • src/aggregator.ts
  • src/cli.test.ts
  • src/cli.ts
  • src/runners/npmRegistryMetadata.test.ts
  • src/runners/npmRegistryMetadata.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/runners/npmRegistryMetadata.test.ts
  • src/aggregator.test.ts
  • src/runners/npmRegistryMetadata.ts
  • src/cli.ts
  • src/cli.test.ts

Comment thread src/aggregator.ts
Comment thread src/aggregator.ts
@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Note

Docstrings generation - SUCCESS
Generated docstrings and committed to branch feat/supply-chain-attack-scan-improvements (commit: a79be7374ebb471b3ce812329c321efbd36f480a)

Docstrings generation was requested by @JosephMaynard.

The following files were modified:

* `report-ui/main.ts`
* `src/aggregator.ts`
* `src/cli.ts`
* `src/explain.ts`
* `src/failOn.ts`
* `src/findings.ts`
* `src/runners/npmRegistryMetadata.ts`

These files were ignored:
* `src/aggregator.test.ts`
* `src/cli.test.ts`
* `src/failOn.test.ts`
* `src/runners/npmRegistryMetadata.test.ts`

These file types are not supported:
* `README.md`

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/findings.ts (1)

184-199: ⚡ Quick win

Deduplicate registry signals before emitting findings.

If dep.supplyChain.registry.signals contains duplicates, this loop emits duplicate findings with the same id. Deduping avoids noisy output and potential downstream key collisions.

♻️ Proposed fix
-    for (const signal of dep.supplyChain?.registry?.signals || []) {
+    for (const signal of new Set(dep.supplyChain?.registry?.signals || [])) {
       const registry = dep.supplyChain?.registry;
       findings.push(baseFinding(dep, `registry-${signal}`, {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/findings.ts` around lines 184 - 199, The loop over
dep.supplyChain?.registry?.signals can emit duplicate findings; deduplicate the
signals before creating findings to avoid duplicate ids. Replace iterating
directly over dep.supplyChain?.registry?.signals with a de-duplicated list
(e.g., Array.from(new Set(...)) or other stable-ordered dedupe) and then iterate
that list when calling baseFinding and pushing to findings; keep using
REGISTRY_SIGNAL_TITLES, baseFinding, registry variable, and the same
evidence/recommendation logic so behavior is unchanged except duplicates are
removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/findings.ts`:
- Around line 184-199: The loop over dep.supplyChain?.registry?.signals can emit
duplicate findings; deduplicate the signals before creating findings to avoid
duplicate ids. Replace iterating directly over
dep.supplyChain?.registry?.signals with a de-duplicated list (e.g.,
Array.from(new Set(...)) or other stable-ordered dedupe) and then iterate that
list when calling baseFinding and pushing to findings; keep using
REGISTRY_SIGNAL_TITLES, baseFinding, registry variable, and the same
evidence/recommendation logic so behavior is unchanged except duplicates are
removed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fe325d47-836b-4249-bb54-312d42d4cc6c

📥 Commits

Reviewing files that changed from the base of the PR and between e39ccea and a79be73.

📒 Files selected for processing (7)
  • report-ui/main.ts
  • src/aggregator.ts
  • src/cli.ts
  • src/explain.ts
  • src/failOn.ts
  • src/findings.ts
  • src/runners/npmRegistryMetadata.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/explain.ts
  • src/cli.ts
  • src/failOn.ts
  • report-ui/main.ts
  • src/aggregator.ts
  • src/runners/npmRegistryMetadata.ts

@JosephMaynard
JosephMaynard merged commit cf9b0c7 into master May 12, 2026
1 check passed
@JosephMaynard
JosephMaynard deleted the feat/supply-chain-attack-scan-improvements branch May 12, 2026 23:53
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.

1 participant