Skip to content

Commit

Permalink
feat: add npm audit signatures
Browse files Browse the repository at this point in the history
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?
  • Loading branch information
feelepxyz committed Apr 29, 2022
1 parent 7a85827 commit d905a5e
Show file tree
Hide file tree
Showing 4 changed files with 605 additions and 2 deletions.
45 changes: 43 additions & 2 deletions lib/commands/audit.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const Arborist = require('@npmcli/arborist')
const auditReport = require('npm-audit-report')
const reifyFinish = require('../utils/reify-finish.js')
const auditError = require('../utils/audit-error.js')

const ArboristWorkspaceCmd = require('../arborist-cmd.js')
const auditError = require('../utils/audit-error.js')
const reifyFinish = require('../utils/reify-finish.js')
const VerifySignatures = require('../utils/verify-signatures.js')

class Audit extends ArboristWorkspaceCmd {
static description = 'Run a security audit'
Expand Down Expand Up @@ -37,6 +39,14 @@ class Audit extends ArboristWorkspaceCmd {
}

async exec (args) {
if (args[0] === 'signatures') {
await this.auditSignatures()
} else {
await this.auditAdvisories(args)
}
}

async auditAdvisories (args) {
const reporter = this.npm.config.get('json') ? 'json' : 'detail'
const opts = {
...this.npm.flatOptions,
Expand All @@ -59,6 +69,37 @@ class Audit extends ArboristWorkspaceCmd {
this.npm.output(result.report)
}
}

async auditSignatures () {
const reporter = this.npm.config.get('json') ? 'json' : 'detail'
const opts = {
...this.npm.flatOptions,
path: this.npm.prefix,
reporter,
workspaces: this.workspaceNames,
}

const arb = new Arborist(opts)
const tree = await arb.loadActual()
let filterSet = new Set()
if (opts.workspaces && opts.workspaces.length) {
filterSet =
arb.workspaceDependencySet(
tree,
opts.workspaces,
this.npm.flatOptions.includeWorkspaceRoot
)
} else if (!this.npm.flatOptions.workspacesEnabled) {
filterSet =
arb.excludeWorkspacesDependencySet(tree)
}

const verify = new VerifySignatures(tree, filterSet, this.npm, { ...opts })
await verify.run()
const result = verify.report()
process.exitCode = process.exitCode || result.exitCode
this.npm.output(result.report)
}
}

module.exports = Audit

0 comments on commit d905a5e

Please sign in to comment.