refactor+test: extract advisory mapping into a pure, unit-tested module#20
Merged
Merged
Conversation
The scan() body inlined the Socket-artifact→Bun-advisory mapping (level, description assembly, overview URL) and the purl parsing. Both are pure, but they lived next to index.ts's module-init token bootstrap (env + settings-file read behind a top-level await), so they could only be exercised through the network/token-gated live and dist suites. Move parseNpmPurl and the new artifactsToAdvisories into a side-effect-free ./advisories module. index.ts re-exports parseNpmPurl for compatibility and scan() now just drains each yielded batch through artifactsToAdvisories. Behavior is identical: an artifact with no alerts or an unparseable inputPurl contributes nothing, action:'error' maps to fatal and anything else to warn.
Adds offline unit coverage for logic that previously only ran through the network/token-gated live and dist suites. - advisories.test.ts: parseNpmPurl (scoped, percent-encoded, missing-version, malformed) and artifactsToAdvisories (fatal/warn mapping, typo-squat hint, description/note/fix assembly, unparseable-purl skip, multi-alert and multi-artifact flattening). 100% of the module. - index.test.ts: Bun.Security.Scanner conformance (version '1', async scan(), parseNpmPurl re-export), a deterministic free-mode scan() end-to-end that doubles only global fetch, and the settings-file token fallback (valid base64 apiToken enters authenticated mode; unreadable file throws). Free mode is forced deterministically by pointing XDG_DATA_HOME/LOCALAPPDATA at an empty temp dir so no real developer token can leak in. Offline unit suite goes 16 -> 44 tests; src/index.ts core mapping goes from 0% offline coverage to fully exercised (advisories 100%, index funcs 100%).
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.
What this does
Finishes the bun-security-scanner modernization. Three of the four goals were already delivered by earlier commits on
main(rolldown bundling, SDK in authenticated mode, a conforming scanner shape); this PR verifies and documents those against the current upstream contracts and delivers the missing piece — real offline test coverage of the core scan logic.The one behavior change is a refactor: the
scan()alert→advisory mapping and the purl parsing move out ofsrc/index.ts(which runs a token bootstrap at import) into a side-effect-freesrc/advisories.ts, so they can be unit tested without network or a token. Runtime behavior is identical.Coverage: offline unit suite 16 → 44 tests. The core mapping in
src/index.tswent from 0% offline coverage (only the network/token-gatedlive/distsuites touched it) to fully exercised.src/advisories.ts(new)src/index.tssrc/modes/authenticated.tssrc/modes/unauthenticated.tssrc/scanner-factory.ts(The uncovered
index.tslines are the win32LOCALAPPDATA-missing throw and the no-XDG_DATA_HOME~/Library/.local/sharefallback — platform branches that don't run on a CI runner withXDG_DATA_HOMEset.)Goal 1 — conforms to the current
Bun.Security.ScannerAPIChecked the scanner against the installed
bun-types@1.3.14bun/security.d.tsand the official oven-sh/security-scanner-template. No API delta — the contract has not broken, andsrc/index.tsalready conforms:version: '1'(string literal, required discriminator).scan(info: { packages: Package[] }): Promise<Advisory[]>.Advisory={ level: 'fatal' | 'warn'; package: string; url: string | null; description: string | null }. The scanner mapsalert.action === 'error'→fatal, everything else →warn, which matches Bun's documented behavior (anyfatalcancels the install with a non-zero exit; otherwisewarnprompts in a TTY / exits in CI).bunfig.tomlcontract is[install.security] scanner = "...", unchanged.tsc --noEmitpasses with the export typed asBun.Security.Scanner, and a new conformance test assertsversion === '1'and thatscanis a function.Goal 2 —
@socketsecurity/sdkusage (and why free mode stays a bounded fetch)Authenticated mode already uses the SDK (
SocketSdk.batchPackageStream,actions=error,warn) — unchanged here.For free mode I evaluated SDK 4.0.3's
checkMalware(), which hits the samefirewall-api.socket.dev/purlendpoint token-lessly (it strips theAuthorizationheader). It is not a drop-in replacement:publicPolicynormalization and, per its own docs, "only return[s] malware-relevant results" — it would drop the non-malware alerts the scanner surfaces (e.g.didYouMeantypo-squatting).MalwareCheckAlertshape it returns has noactionfield, so the scanner could no longer distinguishfatalfromwarn.So free mode keeps its minimal, bounded
fetchto the firewall API, which returns the raw artifact alerts (withaction) the scanner needs. This is the "no SDK-supported unauthenticated path, keep a documented bounded fetch" outcome.Goal 3 — rolldown bundle
Already wired (
.config/repo/rolldown.config.mts,scripts/repo/build.mts): single ESM entry, every runtime dep vendored, onlybun+ node builtins external.bun run buildproducesdist/index.js+dist/index.d.ts. Verified the newsrc/advisories.tsis pulled into the bundle via the import graph (no config change needed), andtest/dist.test.tssmoke-tests the built bundle in both modes.Goal 4 — tests + coverage (the substance of this PR)
test/advisories.test.ts(new):parseNpmPurl(unscoped, scoped, percent-encoded scope, missing version, malformed) andartifactsToAdvisories(fatal/warn mapping, typo-squat hint, description/note/fix assembly, exact description formatting, unparseable-purl skip, one-advisory-per-alert, multi-artifact flattening). Covers 100% of the module.test/index.test.ts(new): scanner conformance; a deterministic free-modescan()end-to-end that doubles onlyglobal fetch(the genuine external HTTP boundary) — free mode is forced by pointingXDG_DATA_HOME/LOCALAPPDATAat an empty temp dir so no real developer token can leak in; and the settings-file token fallback (valid base64apiToken→ authenticated SDK path; unreadable file → throwserror reading Socket settings).No owned-infrastructure mocking: doubles are limited to
global fetchand, for the settings-file authenticated-path assertion, aspyOnof the SDK method (matching the existingauthenticated.test.tsconvention).Verification
bun test— 49 pass, 0 fail across 8 files (thelivesuite self-skips without a token;distsmoke-tests the built bundle).bun run build— succeeds (rolldown bundle + d.ts).tsc --noEmit, fleetoxlint+oxfmt— clean.Deferred / not included
src/modes/unauthenticated.ts. This PR intentionally does not touch that file, so the two don't conflict; fix: bound free-mode firewall-api requests with a timeout (SURF-1041) #19's timeout behavior is preserved.