Skip to content

Commit ea8bd5b

Browse files
committed
feat: add npm audit signatures
Starting to implemenent [RFC: Improve signature verification](npm/rfcs#550) Adds a new sub-command to `audit`: `npm audit signatures` (following [`npm audit licenses`](#3452)) This command will verify registry signatures stored in the packument against a public key on the registry. It currently supports: - Any registry that implements `host/-/npm/v1/keys` endpoint and provides `signatures` in the packument `dist` object - Validates public keys are not expired, compared to the version created date - Errors when encountering packages with missing signatures when the registry returns keys at `host/-/npm/v1/keys` - Errors when encountering invalid signatures - json/human format output TODO - [ ] Fix tests and implement test cases - [ ] Expired public key - [ ] No public keys - [ ] Missing signatures with a public key on the registry - [ ] Missing signatures without a public key on the registry - [ ] Install with valid signatures - [ ] Install with invalid signatures - [ ] Third party registry with signatures and keys - [ ] Tests for the different formats (json, human) - [ ] Tests to omit type of dependency (e.g dev deps) - [ ] Fetch signatures and integrity from `pacote.manifest` - [ ] Caching story for public keys? Currently cached for one week, assumes we'll double sign for longer when rotating keys - [ ] Validate early return conditionals for arb nodes, a lot of cases silently return, e.g. no version, are these correct? - [ ] What other checks do we want? - [ ] Strict mode to error if any signatures are missing when a registry does not return public keys? - [ ] Do we want to explitly trust keys from third party registries and store in .npmrc?
1 parent f985dbb commit ea8bd5b

File tree

4 files changed

+605
-2
lines changed

4 files changed

+605
-2
lines changed

lib/commands/audit.js

+43-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
const Arborist = require('@npmcli/arborist')
22
const auditReport = require('npm-audit-report')
3-
const reifyFinish = require('../utils/reify-finish.js')
4-
const auditError = require('../utils/audit-error.js')
3+
54
const ArboristWorkspaceCmd = require('../arborist-cmd.js')
5+
const auditError = require('../utils/audit-error.js')
6+
const reifyFinish = require('../utils/reify-finish.js')
7+
const VerifySignatures = require('../utils/verify-signatures.js')
68

79
class Audit extends ArboristWorkspaceCmd {
810
static description = 'Run a security audit'
@@ -37,6 +39,14 @@ class Audit extends ArboristWorkspaceCmd {
3739
}
3840

3941
async exec (args) {
42+
if (args[0] === 'signatures') {
43+
await this.auditSignatures()
44+
} else {
45+
await this.auditAdvisories(args)
46+
}
47+
}
48+
49+
async auditAdvisories (args) {
4050
const reporter = this.npm.config.get('json') ? 'json' : 'detail'
4151
const opts = {
4252
...this.npm.flatOptions,
@@ -59,6 +69,37 @@ class Audit extends ArboristWorkspaceCmd {
5969
this.npm.output(result.report)
6070
}
6171
}
72+
73+
async auditSignatures () {
74+
const reporter = this.npm.config.get('json') ? 'json' : 'detail'
75+
const opts = {
76+
...this.npm.flatOptions,
77+
path: this.npm.prefix,
78+
reporter,
79+
workspaces: this.workspaceNames,
80+
}
81+
82+
const arb = new Arborist(opts)
83+
const tree = await arb.loadActual()
84+
let filterSet = new Set()
85+
if (opts.workspaces && opts.workspaces.length) {
86+
filterSet =
87+
arb.workspaceDependencySet(
88+
tree,
89+
opts.workspaces,
90+
this.npm.flatOptions.includeWorkspaceRoot
91+
)
92+
} else if (!this.npm.flatOptions.workspacesEnabled) {
93+
filterSet =
94+
arb.excludeWorkspacesDependencySet(tree)
95+
}
96+
97+
const verify = new VerifySignatures(tree, filterSet, this.npm, { ...opts })
98+
await verify.run()
99+
const result = verify.report()
100+
process.exitCode = process.exitCode || result.exitCode
101+
this.npm.output(result.report)
102+
}
62103
}
63104

64105
module.exports = Audit

0 commit comments

Comments
 (0)