Add audit command to scan packages for security advisories#19
Open
exo-nikita wants to merge 2 commits into
Open
Add audit command to scan packages for security advisories#19exo-nikita wants to merge 2 commits into
exo-nikita wants to merge 2 commits into
Conversation
Collects package name/version pairs from stasis lockfiles and brotli bundles, deduplicates them, queries the npm bulk advisories endpoint, and prints results as a table sorted by severity.
There was a problem hiding this comment.
Pull request overview
Adds a new stasis audit CLI command intended to collect package versions from stasis artifacts and query npm security advisories.
Changes:
- Added
src/audit.jsfor package extraction, advisory flattening, table formatting, and reporting. - Added
stasis auditcommand wiring in the CLI. - Added audit-focused tests for package collection, formatting, and basic CLI errors.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
bin/stasis.js |
Adds usage text and command dispatch for audit. |
src/audit.js |
Implements package collection, npm advisory lookup, and audit report formatting. |
tests/audit.test.js |
Adds tests for audit helpers and basic CLI error handling. |
Comments suppressed due to low confidence (2)
src/audit.js:30
stasisbundles generated byState.sourceDatacontain loaded source files, formats, and imports, but they do not persist package metadata or package.json files unless those package.json files were themselves imported. As a result, auditing a normal generated bundle will collect zero packages and falsely print "No advisories found", which defeats the bundle-audit path.
for (const [path, source] of Object.entries(json.sources)) {
if (!isPackageJsonPath(path)) continue
const { name, version } = JSON.parse(source)
if (name && version) out.push({ name, version })
src/audit.js:36
- The lockfile shape check is loose enough to accept malformed objects such as
{ version: 0, sources: {} }that do not contain the requiredmodulessection, leading to a successful audit with zero packages. Since generated lockfiles always includemodules, this should reject incomplete lockfiles instead of treating them as clean.
function isLockfileShape(json) {
return Boolean(json && (json.modules || (json.sources && Object.values(json.sources).every((v) => v && typeof v === 'object' && 'name' in v))))
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+50
to
+53
| sources: { | ||
| 'src/entry.js': "export const x = 1\n", | ||
| 'node_modules/foo/package.json': JSON.stringify({ name: 'foo', version: '2.0.0' }), | ||
| 'node_modules/baz/package.json': JSON.stringify({ name: 'baz', version: '0.0.1' }), |
| function extractFromBundle(json) { | ||
| assert.equal(json.version, 0, 'unsupported stasis bundle version') | ||
| const out = [] | ||
| if (!json.sources) return out |
Comment on lines
+111
to
+116
| export async function audit(files) { | ||
| const packages = collectPackages(files) | ||
| if (packages.length === 0) { | ||
| return { packages, advisories: {}, rows: [] } | ||
| } | ||
| const result = await advisories(packages) |
| if (packages.length === 0) { | ||
| return { packages, advisories: {}, rows: [] } | ||
| } | ||
| const result = await advisories(packages) |
| if (packages.length === 0) { | ||
| return { packages, advisories: {}, rows: [] } | ||
| } | ||
| const result = await advisories(packages) |
Comment on lines
+12
to
+18
| for (const section of [json.sources, json.modules]) { | ||
| if (!section) continue | ||
| assert.equal(typeof section, 'object') | ||
| for (const entry of Object.values(section)) { | ||
| const { name, version } = entry | ||
| if (name && version) out.push({ name, version }) | ||
| } |
Comment on lines
+77
to
+81
| rows.push({ | ||
| package: pkg, | ||
| vulnerable: adv.vulnerable_versions ?? '', | ||
| severity: adv.severity ?? '', | ||
| title: adv.title ?? '', |
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.
Summary
This PR adds a new
auditcommand to the stasis CLI that scans packages from lockfiles and bundles for security vulnerabilities using the npm advisories API.Key Changes
New audit module (
src/audit.js): Core functionality for auditing packagescollectPackagesFromFile(): Extracts package name/version from stasis lockfiles or brotli-compressed bundlescollectPackages(): Aggregates packages from multiple files with deduplicationflattenAdvisories(): Transforms advisory results into sortable rows, ordered by severity then package nameformatTable(): Generates aligned tabular output for advisory reportsaudit(): Main function that collects packages and queries npm advisories APIprintAuditReport(): Formats and outputs audit results to stdout/stderrCLI integration (
bin/stasis.js): Addedstasis auditcommandComprehensive test suite (
tests/audit.test.js): 173 lines of tests coveringImplementation Details
https://claude.ai/code/session_012AEjpn3Kc2i8WfHw5ZgPEv