Skip to content

feat: Supply chain attack prevention — version quarantine, new package & low download checks#16

Merged
ProduktEntdecker merged 3 commits intomainfrom
feat/15-supply-chain-protection
Apr 2, 2026
Merged

feat: Supply chain attack prevention — version quarantine, new package & low download checks#16
ProduktEntdecker merged 3 commits intomainfrom
feat/15-supply-chain-protection

Conversation

@ProduktEntdecker
Copy link
Copy Markdown
Owner

@ProduktEntdecker ProduktEntdecker commented Apr 2, 2026

Summary

Adds three supply chain heuristics that run in parallel with existing OSV/CVE checks — zero additional latency:

  • Version Quarantine (72h): Flags recently published versions, suggests previous stable release as alternative. Gives security researchers an edge over exploit virality.
  • New Package Detection (7d): Flags packages created less than a week ago — catches slopsquatting and malicious registrations.
  • Low Downloads (<100/week, npm only): Flags packages with no community adoption — catches hallucinated packages and brand-new malicious packages.

All three would have caught the Axios supply chain attack (March 31, 2026) where plain-crypto-js was hours old, had 0 downloads, and no history.

Design decisions

  • All signals return ask (never deny) — heuristics have false-positive potential, human decides
  • Registry checks fail-open (vs OSV fail-closed) — registry being down doesn't block installs
  • 3s timeout per registry call, runs parallel with 4s OSV → stays within 10s hook budget
  • New registry.ts alongside existing osv.ts — clean separation

Files changed

  • src/registry.ts (new) — npm/PyPI registry API calls + 3 heuristics
  • src/registry.test.ts (new) — 16 tests with mocked fetch
  • src/decision.ts — Added makeFullDecision combining CVE + supply chain signals
  • src/index.ts — Wired parallel registry checks

Test plan

  • All 103 tests pass (87 parser + 16 registry)
  • Build succeeds
  • Manual test with real packages
  • Verify quarantine message suggests previous stable version

Closes #15

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Entscheidungen berücksichtigen jetzt Supply-Chain-Signale (npm, PyPI, Homebrew): Alter, Downloads und mögliche Versionsquarantäne zusätzlich zur CVE-Analyse.
    • Entscheidungsergebnis kann von "allow" zu "ask" eskalieren, wenn Signale Bedenken melden.
  • Verhalten

    • OSV-Fehler blockieren weiterhin Installationen (fail-closed); Registry-Fehler werden permissiv behandelt (fail-open) und führen nicht automatisch zur Ablehnung.
  • Tests

    • Umfangreiche Tests für Registry-Detektion, Quarantäne, neue Pakete, Download-Schwellen und kombinierte Szenarien.

…kage detection, low download check

Add three heuristics that run in parallel with existing OSV/CVE checks
to catch supply chain attacks like the Axios compromise (March 2026):

- Version Quarantine (72h): flags recently published versions and
  suggests the previous stable release as alternative
- New Package Detection (7d): flags packages created less than a week ago
- Low Downloads (<100/week): flags packages with minimal community adoption

All three would have caught the Axios attack vector (plain-crypto-js).
Registry checks fail-open and use 'ask' (never 'deny') to avoid
false-positive blocking. Zero additional latency — runs parallel to OSV.

Closes #15

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5d135478-ddb5-46f2-a4d9-1f651e001df6

📥 Commits

Reviewing files that changed from the base of the PR and between 6e23ea5 and cad8c09.

📒 Files selected for processing (1)
  • src/registry.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/registry.ts

Walkthrough

Fügt parallele Supply‑Chain‑Registry‑Checks (npm, PyPI, Homebrew) und eine neue Entscheidungsfunktion makeFullDecision hinzu, die CVE‑Ergebnisse mit Supply‑Chain‑Signals zusammenführt. Registry‑Checks sind fail‑open; OSV‑Checks bleiben fail‑closed. Signale können Entscheidungen von allow → ask verändern oder Gründe ergänzen.

Changes

Cohort / File(s) Summary
Core Decision Logic
src/decision.ts
Neu: makeFullDecision(vulnerabilities: Vulnerability[], signals: SupplyChainSignal[]): DecisionResult und import type { SupplyChainSignal } from './registry.js'. Kombiniert CVE‑Entscheidungen mit Registry‑Signalen nach Prioritätsregeln (deny > ask; allow+signals → ask); fügt Gründe zusammen oder ersetzt sie.
Main Orchestration
src/index.ts
Parallelisiert OSV‑ und Registry‑Checks via Promise.all, behält OSV fail‑closed, behandelt Registry fail‑open (ignoriert Fehler), und verwendet makeFullDecision(allVulnerabilities, allSignals) statt makeDecision.
Registry Implementation & Types
src/registry.ts
Neu: checkRegistryMetadata(name, version?, ecosystem) plus exportierte Typen Ecosystem, SupplyChainSignal, RegistryResult. Implementiert Heuristiken: Version‑Quarantine (<72h), New‑Package (<7d), Low‑Downloads (<100/Woche, npm). Timeouts, pre‑release‑Filter, Vorschlag für vorherige stabile Version, fail‑open Verhalten.
Registry Tests
src/registry.test.ts
Neu: Vitest‑Suite mit globalem fetch‑Mock für npm/ PyPI/ Homebrew: Tests für Quarantine, previous stable resolution, new‑package, low‑downloads, scoped name encoding, pre‑release Ausschluss, kombinierte Signale und fail‑open bei Fetch‑Fehlern.

Sequence Diagram

sequenceDiagram
    rect rgba(200,200,255,0.5)
    actor Client
    end
    rect rgba(200,255,200,0.5)
    participant Index
    end
    rect rgba(255,200,200,0.5)
    participant OSVCheck
    participant RegistryCheck
    end
    rect rgba(255,255,200,0.5)
    participant Decision
    end

    Client->>Index: install(name, version, ecosystem)

    par OSV & Registry Parallel
        Index->>OSVCheck: checkOSV(name, version, ecosystem)
        OSVCheck-->>Index: vulnerabilities | error (fail‑closed)
    and
        Index->>RegistryCheck: checkRegistryMetadata(name, version, ecosystem)
        RegistryCheck-->>Index: RegistryResult{status, signals, previousStableVersion} (fail‑open → empty signals on error)
    end

    Index->>Decision: makeFullDecision(vulnerabilities, signals)

    alt CVE denies
        Decision-->>Client: deny (append registry details if signals exist)
    else CVE allows & signals present
        Decision-->>Client: ask (reason from supply‑chain signals)
    else CVE asks
        Decision-->>Client: ask (merge CVE + registry reasons)
    else no signals
        Decision-->>Client: allow/ask (CVE result only)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
Ein Häschen schnuppert an Paketen sacht,
Quarantäne wacht in dunkler Nacht.
Neu registriert, zu frisch, zu klein—
Ich bitte: prüf nach, lass Menschen rein.
Sicher hüpfen Builds ins Morgenlicht.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding supply chain attack prevention through version quarantine, new package detection, and low download checks.
Linked Issues check ✅ Passed All coding requirements from issue #15 are implemented: version quarantine (72h) [#15], new package detection (7d) [#15], low download checks [#15], parallel execution [#15], fail-open behavior [#15], decision logic combining signals [#15], and comprehensive tests [#15].
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #15 objectives: new registry.ts and registry.test.ts modules, updated decision.ts for signal merging, and modified index.ts for parallel registry checks.

✏️ 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/15-supply-chain-protection

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/index.ts (1)

23-23: Unused import: makeDecision is no longer called directly.

The makeDecision function is imported but only makeFullDecision is used in this file (line 211). makeDecision is called internally by makeFullDecision in decision.ts, so this import is redundant here.

Suggested fix
-import { makeDecision, makeFullDecision, type Vulnerability } from './decision.js';
+import { makeFullDecision, type Vulnerability } from './decision.js';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.ts` at line 23, The import line currently brings in makeDecision,
makeFullDecision, and the Vulnerability type even though makeDecision is unused;
remove makeDecision from the import to eliminate the unused symbol and keep
makeFullDecision and Vulnerability (i.e., update the import that references
makeDecision/makeFullDecision/Vulnerability to only import makeFullDecision and
type Vulnerability) so there are no unused imports reported.
src/registry.test.ts (1)

204-238: Consider adding boundary value tests.

The tests cover < 100 and >= 100 (with 50000), but no test verifies the exact boundary at 100 downloads. Similar pattern applies to 72h quarantine and 7d new-package thresholds. While the current tests provide good coverage, exact boundary tests can catch off-by-one errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/registry.test.ts` around lines 204 - 238, Add boundary-value tests around
the threshold checks: in registry.test.ts add a case in the "npm — low
downloads" suite that calls checkRegistryMetadata with npmDownloadsResponse(100)
and asserts the low-downloads signal behaves exactly as expected at 100
(presence/absence and severity/detail). Similarly add tests for the 72-hour
quarantine and 7-day new-package thresholds by constructing registry responses
with package creation/publish timestamps exactly at those boundaries and
asserting checkRegistryMetadata returns/does not return the "quarantine" and
"new-package" signals as intended; use the same helpers (npmDownloadsResponse,
npmRegistryResponse) and the checkRegistryMetadata function to locate where to
add these specs.
src/registry.ts (1)

38-38: Pre-release pattern may not catch all conventions.

The regex covers common patterns but misses some conventions like 1.0.0-0 (numeric pre-release), 1.0.0-SNAPSHOT, or 1.0.0.dev1 (PEP 440 dev releases without hyphen). Consider if broader coverage is needed.

What are all the common pre-release version string patterns for npm and PyPI packages?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/registry.ts` at line 38, The PRE_RELEASE_PATTERN constant in registry.ts
is too narrow and misses variants like numeric pre-releases (e.g., -0),
SNAPSHOT, and PEP 440 style dev releases (e.g., .dev1) or dot-separated
identifiers; update PRE_RELEASE_PATTERN to match additional common conventions
including numeric prerelease tokens (e.g., -\d+), SNAPSHOT (case-insensitive),
PEP 440 .devN and .a/.b/.rc forms, and allow both hyphen and dot separators
while keeping existing alpha/beta/rc/canary/dev/preview/next/experimental
matches; modify the PRE_RELEASE_PATTERN regular expression to cover these cases
and ensure it remains case-insensitive and well-documented in a nearby comment
for future maintenance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/registry.ts`:
- Line 204: The current fallback for resolvedVersion uses
Object.keys(releases).pop(), which assumes ordering; instead, when version is
not provided inspect the releases map to pick the release whose files contain
the newest upload_time_iso_8601. Implement logic in the code path around
resolvedVersion: iterate releases entries (version string -> file array), for
each file parse upload_time_iso_8601 (or upload_time) to find that version's
latest timestamp, track the version with the overall most recent timestamp, and
set resolvedVersion to that version; keep the existing behavior if the releases
object is empty or timestamps are missing by falling back to a safe default.

---

Nitpick comments:
In `@src/index.ts`:
- Line 23: The import line currently brings in makeDecision, makeFullDecision,
and the Vulnerability type even though makeDecision is unused; remove
makeDecision from the import to eliminate the unused symbol and keep
makeFullDecision and Vulnerability (i.e., update the import that references
makeDecision/makeFullDecision/Vulnerability to only import makeFullDecision and
type Vulnerability) so there are no unused imports reported.

In `@src/registry.test.ts`:
- Around line 204-238: Add boundary-value tests around the threshold checks: in
registry.test.ts add a case in the "npm — low downloads" suite that calls
checkRegistryMetadata with npmDownloadsResponse(100) and asserts the
low-downloads signal behaves exactly as expected at 100 (presence/absence and
severity/detail). Similarly add tests for the 72-hour quarantine and 7-day
new-package thresholds by constructing registry responses with package
creation/publish timestamps exactly at those boundaries and asserting
checkRegistryMetadata returns/does not return the "quarantine" and "new-package"
signals as intended; use the same helpers (npmDownloadsResponse,
npmRegistryResponse) and the checkRegistryMetadata function to locate where to
add these specs.

In `@src/registry.ts`:
- Line 38: The PRE_RELEASE_PATTERN constant in registry.ts is too narrow and
misses variants like numeric pre-releases (e.g., -0), SNAPSHOT, and PEP 440
style dev releases (e.g., .dev1) or dot-separated identifiers; update
PRE_RELEASE_PATTERN to match additional common conventions including numeric
prerelease tokens (e.g., -\d+), SNAPSHOT (case-insensitive), PEP 440 .devN and
.a/.b/.rc forms, and allow both hyphen and dot separators while keeping existing
alpha/beta/rc/canary/dev/preview/next/experimental matches; modify the
PRE_RELEASE_PATTERN regular expression to cover these cases and ensure it
remains case-insensitive and well-documented in a nearby comment for future
maintenance.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c9dab68-8cbe-4116-baf6-473e4576c91f

📥 Commits

Reviewing files that changed from the base of the PR and between 1626065 and fc3afd0.

📒 Files selected for processing (4)
  • src/decision.ts
  • src/index.ts
  • src/registry.test.ts
  • src/registry.ts

Comment thread src/registry.ts Outdated
PyPI's JSON API does not guarantee the order of releases in the
releases object. Replace Object.keys().pop() with timestamp-based
resolution to find the actual latest version and previous stable version.

Addresses CodeRabbit review on PR #16.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/registry.ts`:
- Around line 101-107: The current Promise.all call with fetchWithTimeout for
registryResp and downloadsResp causes any downloads failure to reject the whole
batch and drop registry signals in checkRegistryMetadata; replace the
Promise.all with Promise.allSettled for the two fetches, extract registryResp
from its settled result even if downloadsResp failed, and then treat
downloadsResp failure (rejected or non-OK) as "downloads unavailable" without
throwing—update the downloads check logic to read the settled downloads result,
parse it only when status === "fulfilled" and response.ok, and proceed to
compute registry signals from registryResp regardless of downloads outcome.
- Line 38: PRE_RELEASE_PATTERN currently only matches hyphenated pre-release
tags (npm style); update the PRE_RELEASE_PATTERN definition so it also detects
PEP 440 pre-release forms used by PyPI (e.g., suffixes like aN, bN, rcN, and
.devN without a hyphen) and keep the existing hyphenated alternatives; modify
the single PRE_RELEASE_PATTERN constant (used by checkPyPI) to include both
hyphenated keywords and PEP 440-style suffixes so checkPyPI correctly treats
those versions as pre-releases.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb4e5562-1196-4e9c-ae8a-8957eff2f5ca

📥 Commits

Reviewing files that changed from the base of the PR and between fc3afd0 and 6e23ea5.

📒 Files selected for processing (1)
  • src/registry.ts

Comment thread src/registry.ts Outdated
Comment thread src/registry.ts Outdated
- Extend PRE_RELEASE_PATTERN to match PEP 440 suffixes (1.0a1, 1.0b2,
  1.0rc1, 1.0.dev1) alongside npm-style (-alpha, -beta)
- Use Promise.allSettled instead of Promise.all so downloads API failure
  doesn't discard registry signals (quarantine, new-package)

Addresses CodeRabbit re-review on PR #16.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ProduktEntdecker ProduktEntdecker merged commit fb961f0 into main Apr 2, 2026
4 checks passed
@ProduktEntdecker ProduktEntdecker deleted the feat/15-supply-chain-protection branch April 2, 2026 17:22
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.

feat: Supply chain attack prevention — version quarantine, new package detection, low download check

1 participant