Skip to content

feat(core): replace Socket.dev supply-chain check with OSV (osv.dev) - #1080

Open
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1783375218-osv-supply-chain
Open

feat(core): replace Socket.dev supply-chain check with OSV (osv.dev)#1080
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1783375218-osv-supply-chain

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces the Socket.dev dependency check (the SupplyChain service) with Google's OSV (osv.dev). The old check hit an unofficial, undocumented free endpoint (firewall-api.socket.dev/purl/...) and gated on a proprietary 0–100 composite score. OSV is an official, documented, no-auth API that aggregates GHSA/CVE advisories and the OpenSSF malicious-packages dataset, so diagnostics now name concrete advisory IDs and gate on real vulnerability severity.

Why OSV over Socket

  • Official documented API vs. Socket's unofficial keyless endpoint (fragility + ToS risk).
  • Authoritative + transparent: reports GHSA-… / CVE-… / MAL-… IDs and their severities instead of a black-box score.
  • Covers both known vulnerabilities and known malware (verified: malicious npm packages surface as CRITICAL).
  • Trade-off (intentional): drops Socket's heuristic/behavioral risk scoring (install scripts, obfuscation, typosquatting, quality/maintenance). OSV only knows catalogued vulns/malware.

Behavior / gating change

Score-threshold gating is replaced with severity gating:

// before: fail if worst security axis score < minScore (default 50)
// after:  fail if a dep has a known vuln with severity >= failOn (default "high")
//         malware advisories (MAL-* / "Malicious Package") are always CRITICAL

Severity is normalized to low | moderate | high | critical, resolved in order:

  1. database_specific.severity (GHSA),
  2. CVSS v3 vector/score → bucketed base score,
  3. malware → always critical,
  4. unknown → moderate.

Config (public surface) — breaking

interface SupplyChainConfig {
  enabled?: boolean;                 // default true
  severity?: "error" | "warning";    // diagnostic severity, unchanged, default "error"
  includeDevDependencies?: boolean;  // default true
  failOn?: "low" | "moderate" | "high" | "critical"; // NEW, default "high"  (replaces minScore)
}
  • supplyChain.minScore (number) → removed, replaced by supplyChain.failOn (severity).
  • Diagnostic identity renamed: plugin socketosv, rule low-supply-chain-scoreknown-vulnerability. JSON report schemaVersion unchanged (only the plugin/rule string values differ).

Request strategy (packages/core/src/check-supply-chain.ts)

  • Dependency discovery unchanged (direct deps + optional devDeps from package.json, concrete floor-version resolution, protocol/wildcard/dist-tag skipping, next ignored, line/col anchoring).
  • Lookup: per-PURL cache → one POST /v1/querybatch as a cheap prefilter over cold-miss deps → for each dep the batch flags as having ≥1 advisory, one atomic POST /v1/query returning that package's complete vuln set (each record's severity / database_specific.severity) in a single response. Concurrency-bounded by SUPPLY_CHAIN_FETCH_CONCURRENCY.
  • Resilience (why not per-advisory /v1/vulns/{id}): an earlier revision expanded each advisory ID via a separate /v1/vulns/{id} request; a single transient failure there silently dropped that advisory (potentially a CRITICAL) and wrote the incomplete set to the 24h cache — an under-report that then stuck (flagged by Cursor Bugbot as "Caches empty OSV vuln lists", now resolved). The atomic per-package query fixes this: network calls retry (SUPPLY_CHAIN_FETCH_MAX_RETRIES, exponential backoff, each attempt bounded by FETCH_TIMEOUT_MS), and a package whose query ultimately fails is fail-open and never cached (no diagnostic this run, retried next run) — a partial/poisoned result is never persisted.
  • All fail-open semantics preserved: whole-check SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS[]. An OSV outage never sinks a scan.
  • One diagnostic per affected package, anchored at its package.json line/col, category: "Security", urlhttps://osv.dev/vulnerability/{id}.
  • Constants swapped: SOCKET_* removed, OSV_API_BASE / OSV_VULN_PAGE_BASE added.

Testing

  • Rewrote supply-chain tests (querybatch + per-package /v1/query stubs): severity gating at each failOn level, malware→critical, dist-tag/wildcard/protocol skipping, devDeps toggle, cache hit/miss/stale/REACT_DOCTOR_NO_CACHE, fail-open on whole-check timeout, and a regression test that a failed package query yields no diagnostic and no cache write, with a later successful run recovering the advisory.
  • pnpm build, pnpm typecheck, pnpm lint pass. @react-doctor/core: 1185/1185 tests pass.
  • End-to-end via the built CLI against the live OSV API (lodash@4.17.11): 8/8 no-cache runs stably report all 4 high+critical advisories (incl. the CRITICAL GHSA-jf85-cpcp-j695).

Link to Devin session: https://app.devin.ai/sessions/2f944c3dfeb147b9a6da4940bd598585
Requested by: @aidenybai


Note

Medium Risk
Breaking public config (minScore removal) and diagnostic identity changes affect CI and integrations; security scanning now depends on OSV API behavior and severity parsing instead of Socket scores.

Overview
Replaces the Socket.dev supply-chain check with OSV (api.osv.dev) across the core checker, CLI, config schema, and tests. Dependencies are no longer scored on Socket’s composite 0–100 axes; they are checked against catalogued advisories (GHSA/CVE/MAL) with severity gating via supplyChain.failOn (default "high") instead of minScore (removed).

The checker uses querybatch plus per-package /v1/query, retries failed HTTP calls, and keeps per-PURL on-disk cache (empty results cached; failed queries are not). Diagnostics use plugin osv, rule known-vulnerability, and list advisory IDs with links to osv.dev. Malware advisories are always treated as critical.

Breaking config/report surface: supplyChain.minScoresupplyChain.failOn; diagnostic plugin/rule strings change. Orchestration (background overlap, fail-open timeouts, diff manifest behavior) is unchanged aside from Socket → OSV wording.

Reviewed by Cursor Bugbot for commit f428673. Bugbot is set up for automated code reviews on this repo. Configure here.

Co-Authored-By: Aiden Bai <aiden.bai05@gmail.com>
@aidenybai aidenybai self-assigned this Jul 6, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pkg-pr-new

pkg-pr-new Bot commented Jul 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/eslint-plugin-react-doctor@1080
npm i https://pkg.pr.new/oxlint-plugin-react-doctor@1080
npm i https://pkg.pr.new/react-doctor@1080

commit: f428673

Comment thread packages/core/src/check-supply-chain.ts Outdated
…artial vuln results

Co-Authored-By: Aiden Bai <aiden.bai05@gmail.com>
(vulnerability) =>
vulnerability.id.toUpperCase().startsWith("MAL-") ||
vulnerability.summary.toLowerCase().includes("malicious package"),
);

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.

Malware help misses details text

Medium Severity

Malware handling is inconsistent: resolveVulnerabilitySeverity treats an advisory as malware when isMalwareAdvisory finds malicious package in summary or details, but buildOsvDiagnostic’s hasMalware only checks the cached summary (which prefers summary over details). Advisories with that phrase only in details can get critical gating yet generic “upgrade” help and “known vulnerability” wording instead of compromised-package guidance.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4075b4d. Configure here.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Runtime test results — OSV supply-chain check

Ran the built CLI against fixture package.jsons hitting the live OSV API (shell-only, so no recording — CLI output is the evidence), plus the reliability regression unit test.

Test Result
Known-vuln dep (lodash@4.17.11) flagged with correct OSV identity ✅ passed
Clean dep (left-pad@1.3.0) not flagged (no false positive) ✅ passed
failOn gates by severity (not mere presence) ✅ passed
Reliability: failed query never poisons the 24h cache ✅ passed
Test 1 — lodash@4.17.11, default failOn: "high" (primary flow)
osv diag count: 1
 plugin= osv | rule= known-vulnerability | sev= error | cat= Security
 msg: `lodash@4.17.11` has 4 critical-severity known vulnerabilities: GHSA-jf85-cpcp-j695, GHSA-35jh-r3h4-6jhm, GHSA-p6mc-m468-83gw, GHSA-r5fr-rjxr-66jc.
 url: https://osv.dev/vulnerability/GHSA-jf85-cpcp-j695
exit code = 1

Exactly one Security diagnostic, plugin=osv / rule=known-vulnerability / severity=error, worst severity critical, all 4 high+critical advisories including the CRITICAL GHSA-jf85-cpcp-j695, links to osv.dev, blocking exit 1.

Test 2 — clean dep left-pad@1.3.0
osv diag count: 0 (expect 0)
ok: True
exit code = 0
Test 3 — severity threshold gating (ejs@2.5.7, has moderate + critical)
failOn=critical -> advisory_count=1 | GHSA-phwq-j96m-2c2q
failOn=high     -> advisory_count=1 | GHSA-phwq-j96m-2c2q
failOn=moderate -> advisory_count=2 | GHSA-phwq-j96m-2c2q, GHSA-ghr5-ch3p-vcr6

count(moderate)=2 > count(high/critical)=1 — the moderate GHSA-ghr5-ch3p-vcr6 only appears once the threshold is lowered, proving gating is severity-based, not presence-based.

Test 4 — reliability regression (deterministic unit test)

check-supply-chain.test.ts > "fails open on a package query error without caching a partial result" — stubs a 503 on /v1/query, asserts no diagnostic and no cache write, then a successful run recovers the advisory. This is the issue Cursor Bugbot flagged ("Caches empty OSV vuln lists"), now resolved.

 Test Files  1 passed (1)
      Tests  1 passed | 13 skipped (14)

CI: 19/19 checks green; full @react-doctor/core suite 1185/1185 pass.

Devin session

Co-Authored-By: Aiden Bai <aiden.bai05@gmail.com>

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f428673. Configure here.


return Array.from({ length: dependencyCount }, (_, index) =>
parseOsvQueryBatchResult(results[index]),
);

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.

Short batch results cache false negatives

Medium Severity

parseOsvQueryBatchResponse maps each dependency to results[index] without requiring results.length to match the query count. Missing entries become empty id lists, those packages skip /v1/query, and the checker writes an empty vulnerability list to the 24-hour cache as if the package were clean.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f428673. Configure here.

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