/usr/local/lib/node_modules/npm/lib/utils
NameSizeModeActions
audit-error.js10780644editdlrm
auth.js32880644editdlrm
cmd-list.js29530644editdlrm
completion.fish15960644editdlrm
completion.sh18980755editdlrm
did-you-mean.js11430644editdlrm
display.js167600644editdlrm
error-message.js157750644editdlrm
explain-dep.js36480644editdlrm
explain-eresolve.js25820644editdlrm
format-bytes.js6240644editdlrm
format-search-stream.js49130644editdlrm
format.js21400644editdlrm
get-identity.js7970644editdlrm
get-workspaces.js17480644editdlrm
installed-deep.js11290644editdlrm
installed-shallow.js5830644editdlrm
is-windows.js1770644editdlrm
log-file.js78600644editdlrm
npm-usage.js20720644editdlrm
oidc.js73680644editdlrm
open-url.js25530644editdlrm
output-error.js7880644editdlrm
ping.js2650644editdlrm
queryable.js96110644editdlrm
read-user-info.js20950644editdlrm
reify-finish.js7530644editdlrm
reify-output.js64750644editdlrm
sbom-cyclonedx.js56560644editdlrm
sbom-spdx.js52880644editdlrm
tar.js35110644editdlrm
timers.js21200644editdlrm
update-workspaces.js9970644editdlrm
validate-lockfile.js10230644editdlrm
verify-signatures.js124990644editdlrm
Edit: /usr/local/lib/node_modules/npm/lib/utils/verify-signatures.js (12499B)
const npmFetch = require('npm-registry-fetch') const localeCompare = require('@isaacs/string-locale-compare')('en') const npa = require('npm-package-arg') const pacote = require('pacote') const tufClient = require('@sigstore/tuf') const { log, output } = require('proc-log') const sortAlphabetically = (a, b) => localeCompare(a.name, b.name) class VerifySignatures { constructor (tree, filterSet, npm, opts) { this.tree = tree this.filterSet = filterSet this.npm = npm this.opts = opts this.keys = new Map() this.invalid = [] this.missing = [] this.checkedPackages = new Set() this.verified = [] this.auditedWithKeysCount = 0 this.verifiedSignatureCount = 0 this.verifiedAttestationCount = 0 this.exitCode = 0 } async run () { const start = process.hrtime.bigint() const { default: pMap } = await import('p-map') // Find all deps in tree const { edges, registries } = this.getEdgesOut(this.tree.inventory.values(), this.filterSet) if (edges.size === 0) { throw new Error('found no installed dependencies to audit') } const tuf = await tufClient.initTUF({ cachePath: this.opts.tufCache, retry: this.opts.retry, timeout: this.opts.timeout, }) await Promise.all([...registries].map(registry => this.setKeys({ registry, tuf }))) log.verbose('verifying registry signatures') await pMap(edges, (e) => this.getVerifiedInfo(e), { concurrency: 20, stopOnError: true }) // Didn't find any dependencies that could be verified, e.g. only local deps, missing version, not on a registry etc. if (!this.auditedWithKeysCount && !this.verifiedAttestationCount) { throw new Error('found no dependencies to audit that were installed from ' + 'a supported registry') } const invalid = this.invalid.sort(sortAlphabetically) const missing = this.missing.sort(sortAlphabetically) const hasNoInvalidOrMissing = invalid.length === 0 && missing.length === 0 if (!hasNoInvalidOrMissing) { process.exitCode = 1 } if (this.npm.config.get('json')) { const result = { invalid, missing } if (this.npm.config.get('include-attestations')) { result.verified = this.verified } output.buffer(result) return } const end = process.hrtime.bigint() const elapsed = end - start const auditedPlural = this.auditedWithKeysCount > 1 ? 's' : '' const timing = `audited ${this.auditedWithKeysCount} package${auditedPlural} in ` + `${Math.floor(Number(elapsed) / 1e9)}s` output.standard(timing) output.standard() const verifiedBold = this.npm.chalk.bold('verified') if (this.verifiedSignatureCount) { if (this.verifiedSignatureCount === 1) { output.standard(`${this.verifiedSignatureCount} package has a ${verifiedBold} registry signature`) } else { output.standard(`${this.verifiedSignatureCount} packages have ${verifiedBold} registry signatures`) } output.standard() } if (this.verifiedAttestationCount) { if (this.verifiedAttestationCount === 1) { output.standard(`${this.verifiedAttestationCount} package has a ${verifiedBold} attestation`) } else { output.standard(`${this.verifiedAttestationCount} packages have ${verifiedBold} attestations`) } if (!this.npm.config.get('include-attestations')) { output.standard('(use --json --include-attestations to view attestation details)') } output.standard() } if (missing.length) { const missingClr = this.npm.chalk.redBright('missing') if (missing.length === 1) { output.standard(`1 package has a ${missingClr} registry signature but the registry is providing signing keys:`) } else { output.standard(`${missing.length} packages have ${missingClr} registry signatures but the registry is providing signing keys:`) } output.standard() missing.map(m => output.standard(`${this.npm.chalk.red(`${m.name}@${m.version}`)} (${m.registry})`) ) } if (invalid.length) { if (missing.length) { output.standard() } const invalidClr = this.npm.chalk.redBright('invalid') // We can have either invalid signatures or invalid provenance const invalidSignatures = this.invalid.filter(i => i.code === 'EINTEGRITYSIGNATURE') if (invalidSignatures.length) { if (invalidSignatures.length === 1) { output.standard(`1 package has an ${invalidClr} registry signature:`) } else { output.standard(`${invalidSignatures.length} packages have ${invalidClr} registry signatures:`) } output.standard() invalidSignatures.map(i => output.standard(`${this.npm.chalk.red(`${i.name}@${i.version}`)} (${i.registry})`) ) output.standard() } const invalidAttestations = this.invalid.filter(i => i.code === 'EATTESTATIONVERIFY') if (invalidAttestations.length) { if (invalidAttestations.length === 1) { output.standard(`1 package has an ${invalidClr} attestation:`) } else { output.standard(`${invalidAttestations.length} packages have ${invalidClr} attestations:`) } output.standard() invalidAttestations.map(i => output.standard(`${this.npm.chalk.red(`${i.name}@${i.version}`)} (${i.registry})`) ) output.standard() } if (invalid.length === 1) { output.standard(`Someone might have tampered with this package since it was published on the registry!`) } else { output.standard(`Someone might have tampered with these packages since they were published on the registry!`) } output.standard() } } getEdgesOut (nodes, filterSet) { const edges = new Set() const registries = new Set() for (const node of nodes) { for (const edge of node.edgesOut.values()) { const filteredOut = edge.from && filterSet && filterSet.size > 0 && !filterSet.has(edge.from.target) if (!filteredOut) { const spec = this.getEdgeSpec(edge) if (spec) { // Prefetch and cache public keys from used registries registries.add(this.getSpecRegistry(spec)) } edges.add(edge) } } } return { edges, registries } } async setKeys ({ registry, tuf }) { const { host, pathname } = new URL(registry) // Strip any trailing slashes from pathname const regKey = `${host}${pathname.replace(/\/$/, '')}/keys.json` let keys = await tuf.getTarget(regKey) .then((target) => JSON.parse(target)) .then(({ keys: ks }) => ks.map((key) => ({ ...key, keyid: key.keyId, pemkey: `-----BEGIN PUBLIC KEY-----\n${key.publicKey.rawBytes}\n-----END PUBLIC KEY-----`, expires: key.publicKey.validFor.end || null, }))).catch(err => { if (err.code === 'TUF_FIND_TARGET_ERROR') { return null } else { throw err } }) // If keys not found in Sigstore TUF repo, fall back to registry keys API if (!keys) { log.warn(`Fetching verification keys using TUF failed. Fetching directly from ${registry}.`) keys = await npmFetch.json('/-/npm/v1/keys', { ...this.npm.flatOptions, registry, }).then(({ keys: ks }) => ks.map((key) => ({ ...key, pemkey: `-----BEGIN PUBLIC KEY-----\n${key.key}\n-----END PUBLIC KEY-----`, }))).catch(err => { if (err.code === 'E404' || err.code === 'E400') { return null } else { throw err } }) } if (keys) { this.keys.set(registry, keys) } } getEdgeType (edge) { return edge.optional ? 'optionalDependencies' : edge.peer ? 'peerDependencies' : edge.dev ? 'devDependencies' : 'dependencies' } getEdgeSpec (edge) { let name = edge.name try { name = npa(edge.spec).subSpec.name } catch { // leave it as edge.name } try { return npa(`${name}@${edge.spec}`) } catch { // Skip packages with invalid spec } } buildRegistryConfig (registry) { const keys = this.keys.get(registry) || [] const parsedRegistry = new URL(registry) const regKey = `//${parsedRegistry.host}${parsedRegistry.pathname}` return { [`${regKey}:_keys`]: keys, } } getSpecRegistry (spec) { return npmFetch.pickRegistry(spec, this.npm.flatOptions) } getValidPackageInfo (edge) { const type = this.getEdgeType(edge) // Skip potentially optional packages that are not on disk, as these could // be omitted during install if (edge.error === 'MISSING' && type !== 'dependencies') { return } const spec = this.getEdgeSpec(edge) // Skip invalid version requirements if (!spec) { return } const node = edge.to || edge const { version } = node.package || {} if (node.isWorkspace || // Skip local workspaces packages !version || // Skip packages that don't have an installed version, e.g. optional dependencies !spec.registry) { // Skip if not from registry, e.g. git package return } for (const omitType of this.npm.config.get('omit')) { if (node[omitType]) { return } } return { name: spec.name, version, type, location: node.location, registry: this.getSpecRegistry(spec), } } async verifySignatures (name, version, registry) { const { _integrity: integrity, _signatures, _attestations, _attestationBundles, _resolved: resolved, } = await pacote.manifest(`${name}@${version}`, { verifySignatures: true, verifyAttestations: true, ...this.buildRegistryConfig(registry), ...this.npm.flatOptions, }) const signatures = _signatures || [] const result = { integrity, signatures, attestations: _attestations, attestationBundles: _attestationBundles, resolved, } return result } async getVerifiedInfo (edge) { const info = this.getValidPackageInfo(edge) if (!info) { return } const { name, version, location, registry, type } = info if (this.checkedPackages.has(location)) { // we already did or are doing this one return } this.checkedPackages.add(location) // We only "audit" or verify the signature, or the presence of it, on packages whose registry returns signing keys const keys = this.keys.get(registry) || [] if (keys.length) { this.auditedWithKeysCount += 1 } try { const { integrity, signatures, attestations, attestationBundles, resolved } = await this.verifySignatures(name, version, registry) // Currently we only care about missing signatures on registries that provide a public key // We could make this configurable in the future with a strict/paranoid mode if (signatures.length) { this.verifiedSignatureCount += 1 } else if (keys.length) { this.missing.push({ integrity, location, name, registry, resolved, version, }) } // Track verified attestations separately to registry signatures, as all packages on registries with signing keys are expected to have registry signatures, but not all packages have provenance and publish attestations. if (attestations) { this.verifiedAttestationCount += 1 if (this.npm.config.get('include-attestations')) { this.verified.push({ name, version, location, registry, attestations, attestationBundles, }) } } } catch (e) { if (e.code === 'EINTEGRITYSIGNATURE' || e.code === 'EATTESTATIONVERIFY') { this.invalid.push({ code: e.code, message: e.message, integrity: e.integrity, keyid: e.keyid, location, name, registry, resolved: e.resolved, signature: e.signature, predicateType: e.predicateType, type, version, }) } else { throw e } } } } module.exports = VerifySignatures