Skip to content

fix(scores): keep worst artifact per PURL in hover popup - #64

Merged
John-David Dalton (jdalton) merged 1 commit into
mainfrom
jdalton/surf-276-score-artifact-worst
Aug 2, 2026
Merged

fix(scores): keep worst artifact per PURL in hover popup#64
John-David Dalton (jdalton) merged 1 commit into
mainfrom
jdalton/surf-276-score-artifact-worst

Conversation

@jdalton

@jdalton John-David Dalton (jdalton) commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Closes SURF-276.

The Socket hover popup was hiding malware. A customer opened a Python file that depended on smscallbomber, and the popup showed a package score of 99 with no malware warning. The Socket research page for the same package showed 40 and flagged it. A developer trusting the popup would have installed a package Socket already knows is malicious.

The cause is that one package version can have more than one artifact — PyPI publishes both a source distribution ("sdist") and a pre-built "wheel" — and the extension was keeping whichever artifact happened to arrive last. For this package the clean wheel arrived after the malicious sdist and overwrote it. The fix keeps the worst-scoring artifact per package instead, which is what the website shows, so the popup and the research page now agree.

Why the wrong artifact won — one line per artifact, and each line overwrote the last

When the extension needs scores, it asks the Socket API endpoint POST /v0/purl?alerts=true&compact=false. That endpoint answers with newline-delimited JSON: one line per artifact. A single package version can come back on several lines with different scores and alerts.

The code read those lines in a loop and wrote each one into the cache under the package name. Because every line overwrote the previous one, the last line won.

For smscallbomber@2.7 the API returns two artifacts:

Artifact Overall score Malware alert
tar-gz (sdist — the malware lives in its setup.py) 0 yes, critical
py3-none-any-whl (wheel) 99 none

The wheel line arrives last, so it overwrote the sdist, and the popup showed 99 with no malware. The research page instead shows the worst artifact, which is why it showed 40.

The change — buffer the stream, then keep the lowest score per package

Collect all the streamed artifacts first, then keep the worst-scoring one per package: the lowest score.overall, with an artifact that has no score treated as least-bad so a real score always wins. When two artifacts for the same package tie, the first one seen is kept.

The selection logic is extracted into a small pure function, worstArtifactsByPurl in src/ui/purl-alerts-and-scores/select-artifacts.ts, so it can be unit-tested without loading the VSCode runtime (the manager module opens an output channel the moment it is imported). manager.ts now buffers the streamed lines and calls this function.

How the root cause was confirmed — the live endpoint returns exactly the two lines described above

Querying the live endpoint for pkg:pypi/smscallbomber returns exactly two NDJSON lines with the same inputPurl, one with score.overall: 0 and a critical malware alert (the sdist) and one with score.overall: 0.99 and no malware (the wheel). The popup in the customer screenshot matches the wheel line exactly.

What is covered by tests — including the ordering case that is the whole bug

Ran: tsc --noEmit, oxlint, oxfmt --check, and the test suite — all pass locally.

test/select-artifacts.test.mts covers:

  • the SURF-276 case — the malware sdist (score 0) wins over the clean wheel (score 0.99) regardless of which arrives first;
  • an artifact with a real score beating one with a missing score;
  • multiple distinct packages kept independently;
  • a single artifact returned unchanged;
  • empty input.
CI is red for an unrelated reason — a fleet-infrastructure failure that affects every PR in this repo

The red Check / Test jobs are a fleet-infrastructure failure in the shared bootstrap step (setup-and-install), which runs before any of this code. The same base tree is green on the main push run but red on pull_request runs; it affects every PR in the repo, not this change. Flagged to the team separately.


Note

Medium Risk
Changes how security scores and alerts are chosen for the hover UI—a trust-critical path—but the logic is isolated, well-tested, and aligns display with socket.dev’s worst-artifact behavior.

Overview
Fixes SURF-276: the VS Code hover could show a high score and no malware warning when /v0/purl streamed multiple artifacts for one PURL (e.g. malicious PyPI sdist vs clean wheel), because the last streamed line overwrote the cache.

The manager now buffers the full stream, then applies one entry per input PURL via worstArtifactsByPurl (lowest score.overall; missing scores treated as least bad). Abort handling is adjusted so a timed-out batch does not write after bailPendingCacheEntries has already errored entries.

Selection logic lives in select-artifacts.ts for unit tests without loading the VS Code manager; tests cover stream order, ties, multiple PURLs, and unscored artifacts.

Reviewed by Cursor Bugbot for commit d789459. Configure here.

The /v0/purl endpoint streams one line per artifact, so a single package
version can return several artifacts with different scores and alerts (a PyPI
sdist and wheel, for example). The cache write loop overwrote each entry under
the package name, so the last line won — a clean wheel could hide a malicious
sdist, showing a 99 score with no malware warning while socket.dev flagged the
package (SURF-276).

Collect all streamed artifacts, then keep the worst-scoring one per PURL (lowest
score.overall; a missing score sorts last so a real score always wins). The
selection logic is extracted into a pure worstArtifactsByPurl function so it can
be unit-tested without the VSCode runtime.

Closes SURF-276.
@jdalton
John-David Dalton (jdalton) force-pushed the jdalton/surf-276-score-artifact-worst branch from d789459 to 76c2183 Compare August 2, 2026 22:34
@jdalton
John-David Dalton (jdalton) merged commit 96b5a44 into main Aug 2, 2026
13 checks passed
@jdalton
John-David Dalton (jdalton) deleted the jdalton/surf-276-score-artifact-worst branch August 2, 2026 23:14
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