fix(scores): keep worst artifact per PURL in hover popup - #64
Merged
John-David Dalton (jdalton) merged 1 commit intoAug 2, 2026
Merged
Conversation
John-David Dalton (jdalton)
force-pushed
the
jdalton/surf-276-score-artifact-worst
branch
from
July 24, 2026 13:42
ad581f4 to
bf20e68
Compare
John-David Dalton (jdalton)
force-pushed
the
main
branch
from
July 24, 2026 14:50
4398e9f to
cc455f7
Compare
John-David Dalton (jdalton)
force-pushed
the
jdalton/surf-276-score-artifact-worst
branch
2 times, most recently
from
July 24, 2026 21:59
d694413 to
c24b018
Compare
John-David Dalton (jdalton)
force-pushed
the
jdalton/surf-276-score-artifact-worst
branch
from
July 25, 2026 13:34
c24b018 to
29495ce
Compare
John-David Dalton (jdalton)
force-pushed
the
jdalton/surf-276-score-artifact-worst
branch
from
August 2, 2026 18:47
29495ce to
d789459
Compare
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.
John-David Dalton (jdalton)
force-pushed
the
jdalton/surf-276-score-artifact-worst
branch
from
August 2, 2026 22:34
d789459 to
76c2183
Compare
John-David Dalton (jdalton)
deleted the
jdalton/surf-276-score-artifact-worst
branch
August 2, 2026 23:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.7the API returns two artifacts:tar-gz(sdist — the malware lives in itssetup.py)py3-none-any-whl(wheel)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,
worstArtifactsByPurlinsrc/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.tsnow 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/smscallbomberreturns exactly two NDJSON lines with the sameinputPurl, one withscore.overall: 0and a criticalmalwarealert (the sdist) and one withscore.overall: 0.99and 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.mtscovers:CI is red for an unrelated reason — a fleet-infrastructure failure that affects every PR in this repo
The red
Check/Testjobs 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 themainpush run but red onpull_requestruns; 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/purlstreamed 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(lowestscore.overall; missing scores treated as least bad). Abort handling is adjusted so a timed-out batch does not write afterbailPendingCacheEntrieshas already errored entries.Selection logic lives in
select-artifacts.tsfor 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.