11cb0ef41Sopenharmony_ciconst npmAuditReport = require('npm-audit-report') 21cb0ef41Sopenharmony_ciconst fetch = require('npm-registry-fetch') 31cb0ef41Sopenharmony_ciconst localeCompare = require('@isaacs/string-locale-compare')('en') 41cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg') 51cb0ef41Sopenharmony_ciconst pacote = require('pacote') 61cb0ef41Sopenharmony_ciconst pMap = require('p-map') 71cb0ef41Sopenharmony_ciconst tufClient = require('@sigstore/tuf') 81cb0ef41Sopenharmony_ci 91cb0ef41Sopenharmony_ciconst ArboristWorkspaceCmd = require('../arborist-cmd.js') 101cb0ef41Sopenharmony_ciconst auditError = require('../utils/audit-error.js') 111cb0ef41Sopenharmony_ciconst log = require('../utils/log-shim.js') 121cb0ef41Sopenharmony_ciconst reifyFinish = require('../utils/reify-finish.js') 131cb0ef41Sopenharmony_ci 141cb0ef41Sopenharmony_ciconst sortAlphabetically = (a, b) => localeCompare(a.name, b.name) 151cb0ef41Sopenharmony_ci 161cb0ef41Sopenharmony_ciclass VerifySignatures { 171cb0ef41Sopenharmony_ci constructor (tree, filterSet, npm, opts) { 181cb0ef41Sopenharmony_ci this.tree = tree 191cb0ef41Sopenharmony_ci this.filterSet = filterSet 201cb0ef41Sopenharmony_ci this.npm = npm 211cb0ef41Sopenharmony_ci this.opts = opts 221cb0ef41Sopenharmony_ci this.keys = new Map() 231cb0ef41Sopenharmony_ci this.invalid = [] 241cb0ef41Sopenharmony_ci this.missing = [] 251cb0ef41Sopenharmony_ci this.checkedPackages = new Set() 261cb0ef41Sopenharmony_ci this.auditedWithKeysCount = 0 271cb0ef41Sopenharmony_ci this.verifiedSignatureCount = 0 281cb0ef41Sopenharmony_ci this.verifiedAttestationCount = 0 291cb0ef41Sopenharmony_ci this.exitCode = 0 301cb0ef41Sopenharmony_ci } 311cb0ef41Sopenharmony_ci 321cb0ef41Sopenharmony_ci async run () { 331cb0ef41Sopenharmony_ci const start = process.hrtime.bigint() 341cb0ef41Sopenharmony_ci 351cb0ef41Sopenharmony_ci // Find all deps in tree 361cb0ef41Sopenharmony_ci const { edges, registries } = this.getEdgesOut(this.tree.inventory.values(), this.filterSet) 371cb0ef41Sopenharmony_ci if (edges.size === 0) { 381cb0ef41Sopenharmony_ci throw new Error('found no installed dependencies to audit') 391cb0ef41Sopenharmony_ci } 401cb0ef41Sopenharmony_ci 411cb0ef41Sopenharmony_ci const tuf = await tufClient.initTUF({ 421cb0ef41Sopenharmony_ci cachePath: this.opts.tufCache, 431cb0ef41Sopenharmony_ci retry: this.opts.retry, 441cb0ef41Sopenharmony_ci timeout: this.opts.timeout, 451cb0ef41Sopenharmony_ci }) 461cb0ef41Sopenharmony_ci await Promise.all([...registries].map(registry => this.setKeys({ registry, tuf }))) 471cb0ef41Sopenharmony_ci 481cb0ef41Sopenharmony_ci const progress = log.newItem('verifying registry signatures', edges.size) 491cb0ef41Sopenharmony_ci const mapper = async (edge) => { 501cb0ef41Sopenharmony_ci progress.completeWork(1) 511cb0ef41Sopenharmony_ci await this.getVerifiedInfo(edge) 521cb0ef41Sopenharmony_ci } 531cb0ef41Sopenharmony_ci await pMap(edges, mapper, { concurrency: 20, stopOnError: true }) 541cb0ef41Sopenharmony_ci 551cb0ef41Sopenharmony_ci // Didn't find any dependencies that could be verified, e.g. only local 561cb0ef41Sopenharmony_ci // deps, missing version, not on a registry etc. 571cb0ef41Sopenharmony_ci if (!this.auditedWithKeysCount) { 581cb0ef41Sopenharmony_ci throw new Error('found no dependencies to audit that were installed from ' + 591cb0ef41Sopenharmony_ci 'a supported registry') 601cb0ef41Sopenharmony_ci } 611cb0ef41Sopenharmony_ci 621cb0ef41Sopenharmony_ci const invalid = this.invalid.sort(sortAlphabetically) 631cb0ef41Sopenharmony_ci const missing = this.missing.sort(sortAlphabetically) 641cb0ef41Sopenharmony_ci 651cb0ef41Sopenharmony_ci const hasNoInvalidOrMissing = invalid.length === 0 && missing.length === 0 661cb0ef41Sopenharmony_ci 671cb0ef41Sopenharmony_ci if (!hasNoInvalidOrMissing) { 681cb0ef41Sopenharmony_ci process.exitCode = 1 691cb0ef41Sopenharmony_ci } 701cb0ef41Sopenharmony_ci 711cb0ef41Sopenharmony_ci if (this.npm.config.get('json')) { 721cb0ef41Sopenharmony_ci this.npm.output(JSON.stringify({ 731cb0ef41Sopenharmony_ci invalid, 741cb0ef41Sopenharmony_ci missing, 751cb0ef41Sopenharmony_ci }, null, 2)) 761cb0ef41Sopenharmony_ci return 771cb0ef41Sopenharmony_ci } 781cb0ef41Sopenharmony_ci const end = process.hrtime.bigint() 791cb0ef41Sopenharmony_ci const elapsed = end - start 801cb0ef41Sopenharmony_ci 811cb0ef41Sopenharmony_ci const auditedPlural = this.auditedWithKeysCount > 1 ? 's' : '' 821cb0ef41Sopenharmony_ci const timing = `audited ${this.auditedWithKeysCount} package${auditedPlural} in ` + 831cb0ef41Sopenharmony_ci `${Math.floor(Number(elapsed) / 1e9)}s` 841cb0ef41Sopenharmony_ci this.npm.output(timing) 851cb0ef41Sopenharmony_ci this.npm.output('') 861cb0ef41Sopenharmony_ci 871cb0ef41Sopenharmony_ci const verifiedBold = this.npm.chalk.bold('verified') 881cb0ef41Sopenharmony_ci if (this.verifiedSignatureCount) { 891cb0ef41Sopenharmony_ci if (this.verifiedSignatureCount === 1) { 901cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 911cb0ef41Sopenharmony_ci this.npm.output(`${this.verifiedSignatureCount} package has a ${verifiedBold} registry signature`) 921cb0ef41Sopenharmony_ci } else { 931cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 941cb0ef41Sopenharmony_ci this.npm.output(`${this.verifiedSignatureCount} packages have ${verifiedBold} registry signatures`) 951cb0ef41Sopenharmony_ci } 961cb0ef41Sopenharmony_ci this.npm.output('') 971cb0ef41Sopenharmony_ci } 981cb0ef41Sopenharmony_ci 991cb0ef41Sopenharmony_ci if (this.verifiedAttestationCount) { 1001cb0ef41Sopenharmony_ci if (this.verifiedAttestationCount === 1) { 1011cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1021cb0ef41Sopenharmony_ci this.npm.output(`${this.verifiedAttestationCount} package has a ${verifiedBold} attestation`) 1031cb0ef41Sopenharmony_ci } else { 1041cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1051cb0ef41Sopenharmony_ci this.npm.output(`${this.verifiedAttestationCount} packages have ${verifiedBold} attestations`) 1061cb0ef41Sopenharmony_ci } 1071cb0ef41Sopenharmony_ci this.npm.output('') 1081cb0ef41Sopenharmony_ci } 1091cb0ef41Sopenharmony_ci 1101cb0ef41Sopenharmony_ci if (missing.length) { 1111cb0ef41Sopenharmony_ci const missingClr = this.npm.chalk.bold(this.npm.chalk.red('missing')) 1121cb0ef41Sopenharmony_ci if (missing.length === 1) { 1131cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1141cb0ef41Sopenharmony_ci this.npm.output(`1 package has a ${missingClr} registry signature but the registry is providing signing keys:`) 1151cb0ef41Sopenharmony_ci } else { 1161cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1171cb0ef41Sopenharmony_ci this.npm.output(`${missing.length} packages have ${missingClr} registry signatures but the registry is providing signing keys:`) 1181cb0ef41Sopenharmony_ci } 1191cb0ef41Sopenharmony_ci this.npm.output('') 1201cb0ef41Sopenharmony_ci missing.map(m => 1211cb0ef41Sopenharmony_ci this.npm.output(`${this.npm.chalk.red(`${m.name}@${m.version}`)} (${m.registry})`) 1221cb0ef41Sopenharmony_ci ) 1231cb0ef41Sopenharmony_ci } 1241cb0ef41Sopenharmony_ci 1251cb0ef41Sopenharmony_ci if (invalid.length) { 1261cb0ef41Sopenharmony_ci if (missing.length) { 1271cb0ef41Sopenharmony_ci this.npm.output('') 1281cb0ef41Sopenharmony_ci } 1291cb0ef41Sopenharmony_ci const invalidClr = this.npm.chalk.bold(this.npm.chalk.red('invalid')) 1301cb0ef41Sopenharmony_ci // We can have either invalid signatures or invalid provenance 1311cb0ef41Sopenharmony_ci const invalidSignatures = this.invalid.filter(i => i.code === 'EINTEGRITYSIGNATURE') 1321cb0ef41Sopenharmony_ci if (invalidSignatures.length) { 1331cb0ef41Sopenharmony_ci if (invalidSignatures.length === 1) { 1341cb0ef41Sopenharmony_ci this.npm.output(`1 package has an ${invalidClr} registry signature:`) 1351cb0ef41Sopenharmony_ci } else { 1361cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1371cb0ef41Sopenharmony_ci this.npm.output(`${invalidSignatures.length} packages have ${invalidClr} registry signatures:`) 1381cb0ef41Sopenharmony_ci } 1391cb0ef41Sopenharmony_ci this.npm.output('') 1401cb0ef41Sopenharmony_ci invalidSignatures.map(i => 1411cb0ef41Sopenharmony_ci this.npm.output(`${this.npm.chalk.red(`${i.name}@${i.version}`)} (${i.registry})`) 1421cb0ef41Sopenharmony_ci ) 1431cb0ef41Sopenharmony_ci this.npm.output('') 1441cb0ef41Sopenharmony_ci } 1451cb0ef41Sopenharmony_ci 1461cb0ef41Sopenharmony_ci const invalidAttestations = this.invalid.filter(i => i.code === 'EATTESTATIONVERIFY') 1471cb0ef41Sopenharmony_ci if (invalidAttestations.length) { 1481cb0ef41Sopenharmony_ci if (invalidAttestations.length === 1) { 1491cb0ef41Sopenharmony_ci this.npm.output(`1 package has an ${invalidClr} attestation:`) 1501cb0ef41Sopenharmony_ci } else { 1511cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1521cb0ef41Sopenharmony_ci this.npm.output(`${invalidAttestations.length} packages have ${invalidClr} attestations:`) 1531cb0ef41Sopenharmony_ci } 1541cb0ef41Sopenharmony_ci this.npm.output('') 1551cb0ef41Sopenharmony_ci invalidAttestations.map(i => 1561cb0ef41Sopenharmony_ci this.npm.output(`${this.npm.chalk.red(`${i.name}@${i.version}`)} (${i.registry})`) 1571cb0ef41Sopenharmony_ci ) 1581cb0ef41Sopenharmony_ci this.npm.output('') 1591cb0ef41Sopenharmony_ci } 1601cb0ef41Sopenharmony_ci 1611cb0ef41Sopenharmony_ci if (invalid.length === 1) { 1621cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1631cb0ef41Sopenharmony_ci this.npm.output(`Someone might have tampered with this package since it was published on the registry!`) 1641cb0ef41Sopenharmony_ci } else { 1651cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1661cb0ef41Sopenharmony_ci this.npm.output(`Someone might have tampered with these packages since they were published on the registry!`) 1671cb0ef41Sopenharmony_ci } 1681cb0ef41Sopenharmony_ci this.npm.output('') 1691cb0ef41Sopenharmony_ci } 1701cb0ef41Sopenharmony_ci } 1711cb0ef41Sopenharmony_ci 1721cb0ef41Sopenharmony_ci getEdgesOut (nodes, filterSet) { 1731cb0ef41Sopenharmony_ci const edges = new Set() 1741cb0ef41Sopenharmony_ci const registries = new Set() 1751cb0ef41Sopenharmony_ci for (const node of nodes) { 1761cb0ef41Sopenharmony_ci for (const edge of node.edgesOut.values()) { 1771cb0ef41Sopenharmony_ci const filteredOut = 1781cb0ef41Sopenharmony_ci edge.from 1791cb0ef41Sopenharmony_ci && filterSet 1801cb0ef41Sopenharmony_ci && filterSet.size > 0 1811cb0ef41Sopenharmony_ci && !filterSet.has(edge.from.target) 1821cb0ef41Sopenharmony_ci 1831cb0ef41Sopenharmony_ci if (!filteredOut) { 1841cb0ef41Sopenharmony_ci const spec = this.getEdgeSpec(edge) 1851cb0ef41Sopenharmony_ci if (spec) { 1861cb0ef41Sopenharmony_ci // Prefetch and cache public keys from used registries 1871cb0ef41Sopenharmony_ci registries.add(this.getSpecRegistry(spec)) 1881cb0ef41Sopenharmony_ci } 1891cb0ef41Sopenharmony_ci edges.add(edge) 1901cb0ef41Sopenharmony_ci } 1911cb0ef41Sopenharmony_ci } 1921cb0ef41Sopenharmony_ci } 1931cb0ef41Sopenharmony_ci return { edges, registries } 1941cb0ef41Sopenharmony_ci } 1951cb0ef41Sopenharmony_ci 1961cb0ef41Sopenharmony_ci async setKeys ({ registry, tuf }) { 1971cb0ef41Sopenharmony_ci const { host, pathname } = new URL(registry) 1981cb0ef41Sopenharmony_ci // Strip any trailing slashes from pathname 1991cb0ef41Sopenharmony_ci const regKey = `${host}${pathname.replace(/\/$/, '')}/keys.json` 2001cb0ef41Sopenharmony_ci let keys = await tuf.getTarget(regKey) 2011cb0ef41Sopenharmony_ci .then((target) => JSON.parse(target)) 2021cb0ef41Sopenharmony_ci .then(({ keys: ks }) => ks.map((key) => ({ 2031cb0ef41Sopenharmony_ci ...key, 2041cb0ef41Sopenharmony_ci keyid: key.keyId, 2051cb0ef41Sopenharmony_ci pemkey: `-----BEGIN PUBLIC KEY-----\n${key.publicKey.rawBytes}\n-----END PUBLIC KEY-----`, 2061cb0ef41Sopenharmony_ci expires: key.publicKey.validFor.end || null, 2071cb0ef41Sopenharmony_ci }))).catch(err => { 2081cb0ef41Sopenharmony_ci if (err.code === 'TUF_FIND_TARGET_ERROR') { 2091cb0ef41Sopenharmony_ci return null 2101cb0ef41Sopenharmony_ci } else { 2111cb0ef41Sopenharmony_ci throw err 2121cb0ef41Sopenharmony_ci } 2131cb0ef41Sopenharmony_ci }) 2141cb0ef41Sopenharmony_ci 2151cb0ef41Sopenharmony_ci // If keys not found in Sigstore TUF repo, fallback to registry keys API 2161cb0ef41Sopenharmony_ci if (!keys) { 2171cb0ef41Sopenharmony_ci keys = await fetch.json('/-/npm/v1/keys', { 2181cb0ef41Sopenharmony_ci ...this.npm.flatOptions, 2191cb0ef41Sopenharmony_ci registry, 2201cb0ef41Sopenharmony_ci }).then(({ keys: ks }) => ks.map((key) => ({ 2211cb0ef41Sopenharmony_ci ...key, 2221cb0ef41Sopenharmony_ci pemkey: `-----BEGIN PUBLIC KEY-----\n${key.key}\n-----END PUBLIC KEY-----`, 2231cb0ef41Sopenharmony_ci }))).catch(err => { 2241cb0ef41Sopenharmony_ci if (err.code === 'E404' || err.code === 'E400') { 2251cb0ef41Sopenharmony_ci return null 2261cb0ef41Sopenharmony_ci } else { 2271cb0ef41Sopenharmony_ci throw err 2281cb0ef41Sopenharmony_ci } 2291cb0ef41Sopenharmony_ci }) 2301cb0ef41Sopenharmony_ci } 2311cb0ef41Sopenharmony_ci 2321cb0ef41Sopenharmony_ci if (keys) { 2331cb0ef41Sopenharmony_ci this.keys.set(registry, keys) 2341cb0ef41Sopenharmony_ci } 2351cb0ef41Sopenharmony_ci } 2361cb0ef41Sopenharmony_ci 2371cb0ef41Sopenharmony_ci getEdgeType (edge) { 2381cb0ef41Sopenharmony_ci return edge.optional ? 'optionalDependencies' 2391cb0ef41Sopenharmony_ci : edge.peer ? 'peerDependencies' 2401cb0ef41Sopenharmony_ci : edge.dev ? 'devDependencies' 2411cb0ef41Sopenharmony_ci : 'dependencies' 2421cb0ef41Sopenharmony_ci } 2431cb0ef41Sopenharmony_ci 2441cb0ef41Sopenharmony_ci getEdgeSpec (edge) { 2451cb0ef41Sopenharmony_ci let name = edge.name 2461cb0ef41Sopenharmony_ci try { 2471cb0ef41Sopenharmony_ci name = npa(edge.spec).subSpec.name 2481cb0ef41Sopenharmony_ci } catch { 2491cb0ef41Sopenharmony_ci // leave it as edge.name 2501cb0ef41Sopenharmony_ci } 2511cb0ef41Sopenharmony_ci try { 2521cb0ef41Sopenharmony_ci return npa(`${name}@${edge.spec}`) 2531cb0ef41Sopenharmony_ci } catch { 2541cb0ef41Sopenharmony_ci // Skip packages with invalid spec 2551cb0ef41Sopenharmony_ci } 2561cb0ef41Sopenharmony_ci } 2571cb0ef41Sopenharmony_ci 2581cb0ef41Sopenharmony_ci buildRegistryConfig (registry) { 2591cb0ef41Sopenharmony_ci const keys = this.keys.get(registry) || [] 2601cb0ef41Sopenharmony_ci const parsedRegistry = new URL(registry) 2611cb0ef41Sopenharmony_ci const regKey = `//${parsedRegistry.host}${parsedRegistry.pathname}` 2621cb0ef41Sopenharmony_ci return { 2631cb0ef41Sopenharmony_ci [`${regKey}:_keys`]: keys, 2641cb0ef41Sopenharmony_ci } 2651cb0ef41Sopenharmony_ci } 2661cb0ef41Sopenharmony_ci 2671cb0ef41Sopenharmony_ci getSpecRegistry (spec) { 2681cb0ef41Sopenharmony_ci return fetch.pickRegistry(spec, this.npm.flatOptions) 2691cb0ef41Sopenharmony_ci } 2701cb0ef41Sopenharmony_ci 2711cb0ef41Sopenharmony_ci getValidPackageInfo (edge) { 2721cb0ef41Sopenharmony_ci const type = this.getEdgeType(edge) 2731cb0ef41Sopenharmony_ci // Skip potentially optional packages that are not on disk, as these could 2741cb0ef41Sopenharmony_ci // be omitted during install 2751cb0ef41Sopenharmony_ci if (edge.error === 'MISSING' && type !== 'dependencies') { 2761cb0ef41Sopenharmony_ci return 2771cb0ef41Sopenharmony_ci } 2781cb0ef41Sopenharmony_ci 2791cb0ef41Sopenharmony_ci const spec = this.getEdgeSpec(edge) 2801cb0ef41Sopenharmony_ci // Skip invalid version requirements 2811cb0ef41Sopenharmony_ci if (!spec) { 2821cb0ef41Sopenharmony_ci return 2831cb0ef41Sopenharmony_ci } 2841cb0ef41Sopenharmony_ci const node = edge.to || edge 2851cb0ef41Sopenharmony_ci const { version } = node.package || {} 2861cb0ef41Sopenharmony_ci 2871cb0ef41Sopenharmony_ci if (node.isWorkspace || // Skip local workspaces packages 2881cb0ef41Sopenharmony_ci !version || // Skip packages that don't have a installed version, e.g. optonal dependencies 2891cb0ef41Sopenharmony_ci !spec.registry) { // Skip if not from registry, e.g. git package 2901cb0ef41Sopenharmony_ci return 2911cb0ef41Sopenharmony_ci } 2921cb0ef41Sopenharmony_ci 2931cb0ef41Sopenharmony_ci for (const omitType of this.npm.config.get('omit')) { 2941cb0ef41Sopenharmony_ci if (node[omitType]) { 2951cb0ef41Sopenharmony_ci return 2961cb0ef41Sopenharmony_ci } 2971cb0ef41Sopenharmony_ci } 2981cb0ef41Sopenharmony_ci 2991cb0ef41Sopenharmony_ci return { 3001cb0ef41Sopenharmony_ci name: spec.name, 3011cb0ef41Sopenharmony_ci version, 3021cb0ef41Sopenharmony_ci type, 3031cb0ef41Sopenharmony_ci location: node.location, 3041cb0ef41Sopenharmony_ci registry: this.getSpecRegistry(spec), 3051cb0ef41Sopenharmony_ci } 3061cb0ef41Sopenharmony_ci } 3071cb0ef41Sopenharmony_ci 3081cb0ef41Sopenharmony_ci async verifySignatures (name, version, registry) { 3091cb0ef41Sopenharmony_ci const { 3101cb0ef41Sopenharmony_ci _integrity: integrity, 3111cb0ef41Sopenharmony_ci _signatures, 3121cb0ef41Sopenharmony_ci _attestations, 3131cb0ef41Sopenharmony_ci _resolved: resolved, 3141cb0ef41Sopenharmony_ci } = await pacote.manifest(`${name}@${version}`, { 3151cb0ef41Sopenharmony_ci verifySignatures: true, 3161cb0ef41Sopenharmony_ci verifyAttestations: true, 3171cb0ef41Sopenharmony_ci ...this.buildRegistryConfig(registry), 3181cb0ef41Sopenharmony_ci ...this.npm.flatOptions, 3191cb0ef41Sopenharmony_ci }) 3201cb0ef41Sopenharmony_ci const signatures = _signatures || [] 3211cb0ef41Sopenharmony_ci const result = { 3221cb0ef41Sopenharmony_ci integrity, 3231cb0ef41Sopenharmony_ci signatures, 3241cb0ef41Sopenharmony_ci attestations: _attestations, 3251cb0ef41Sopenharmony_ci resolved, 3261cb0ef41Sopenharmony_ci } 3271cb0ef41Sopenharmony_ci return result 3281cb0ef41Sopenharmony_ci } 3291cb0ef41Sopenharmony_ci 3301cb0ef41Sopenharmony_ci async getVerifiedInfo (edge) { 3311cb0ef41Sopenharmony_ci const info = this.getValidPackageInfo(edge) 3321cb0ef41Sopenharmony_ci if (!info) { 3331cb0ef41Sopenharmony_ci return 3341cb0ef41Sopenharmony_ci } 3351cb0ef41Sopenharmony_ci const { name, version, location, registry, type } = info 3361cb0ef41Sopenharmony_ci if (this.checkedPackages.has(location)) { 3371cb0ef41Sopenharmony_ci // we already did or are doing this one 3381cb0ef41Sopenharmony_ci return 3391cb0ef41Sopenharmony_ci } 3401cb0ef41Sopenharmony_ci this.checkedPackages.add(location) 3411cb0ef41Sopenharmony_ci 3421cb0ef41Sopenharmony_ci // We only "audit" or verify the signature, or the presence of it, on 3431cb0ef41Sopenharmony_ci // packages whose registry returns signing keys 3441cb0ef41Sopenharmony_ci const keys = this.keys.get(registry) || [] 3451cb0ef41Sopenharmony_ci if (keys.length) { 3461cb0ef41Sopenharmony_ci this.auditedWithKeysCount += 1 3471cb0ef41Sopenharmony_ci } 3481cb0ef41Sopenharmony_ci 3491cb0ef41Sopenharmony_ci try { 3501cb0ef41Sopenharmony_ci const { integrity, signatures, attestations, resolved } = await this.verifySignatures( 3511cb0ef41Sopenharmony_ci name, version, registry 3521cb0ef41Sopenharmony_ci ) 3531cb0ef41Sopenharmony_ci 3541cb0ef41Sopenharmony_ci // Currently we only care about missing signatures on registries that provide a public key 3551cb0ef41Sopenharmony_ci // We could make this configurable in the future with a strict/paranoid mode 3561cb0ef41Sopenharmony_ci if (signatures.length) { 3571cb0ef41Sopenharmony_ci this.verifiedSignatureCount += 1 3581cb0ef41Sopenharmony_ci } else if (keys.length) { 3591cb0ef41Sopenharmony_ci this.missing.push({ 3601cb0ef41Sopenharmony_ci integrity, 3611cb0ef41Sopenharmony_ci location, 3621cb0ef41Sopenharmony_ci name, 3631cb0ef41Sopenharmony_ci registry, 3641cb0ef41Sopenharmony_ci resolved, 3651cb0ef41Sopenharmony_ci version, 3661cb0ef41Sopenharmony_ci }) 3671cb0ef41Sopenharmony_ci } 3681cb0ef41Sopenharmony_ci 3691cb0ef41Sopenharmony_ci // Track verified attestations separately to registry signatures, as all 3701cb0ef41Sopenharmony_ci // packages on registries with signing keys are expected to have registry 3711cb0ef41Sopenharmony_ci // signatures, but not all packages have provenance and publish attestations. 3721cb0ef41Sopenharmony_ci if (attestations) { 3731cb0ef41Sopenharmony_ci this.verifiedAttestationCount += 1 3741cb0ef41Sopenharmony_ci } 3751cb0ef41Sopenharmony_ci } catch (e) { 3761cb0ef41Sopenharmony_ci if (e.code === 'EINTEGRITYSIGNATURE' || e.code === 'EATTESTATIONVERIFY') { 3771cb0ef41Sopenharmony_ci this.invalid.push({ 3781cb0ef41Sopenharmony_ci code: e.code, 3791cb0ef41Sopenharmony_ci message: e.message, 3801cb0ef41Sopenharmony_ci integrity: e.integrity, 3811cb0ef41Sopenharmony_ci keyid: e.keyid, 3821cb0ef41Sopenharmony_ci location, 3831cb0ef41Sopenharmony_ci name, 3841cb0ef41Sopenharmony_ci registry, 3851cb0ef41Sopenharmony_ci resolved: e.resolved, 3861cb0ef41Sopenharmony_ci signature: e.signature, 3871cb0ef41Sopenharmony_ci predicateType: e.predicateType, 3881cb0ef41Sopenharmony_ci type, 3891cb0ef41Sopenharmony_ci version, 3901cb0ef41Sopenharmony_ci }) 3911cb0ef41Sopenharmony_ci } else { 3921cb0ef41Sopenharmony_ci throw e 3931cb0ef41Sopenharmony_ci } 3941cb0ef41Sopenharmony_ci } 3951cb0ef41Sopenharmony_ci } 3961cb0ef41Sopenharmony_ci} 3971cb0ef41Sopenharmony_ci 3981cb0ef41Sopenharmony_ciclass Audit extends ArboristWorkspaceCmd { 3991cb0ef41Sopenharmony_ci static description = 'Run a security audit' 4001cb0ef41Sopenharmony_ci static name = 'audit' 4011cb0ef41Sopenharmony_ci static params = [ 4021cb0ef41Sopenharmony_ci 'audit-level', 4031cb0ef41Sopenharmony_ci 'dry-run', 4041cb0ef41Sopenharmony_ci 'force', 4051cb0ef41Sopenharmony_ci 'json', 4061cb0ef41Sopenharmony_ci 'package-lock-only', 4071cb0ef41Sopenharmony_ci 'package-lock', 4081cb0ef41Sopenharmony_ci 'omit', 4091cb0ef41Sopenharmony_ci 'include', 4101cb0ef41Sopenharmony_ci 'foreground-scripts', 4111cb0ef41Sopenharmony_ci 'ignore-scripts', 4121cb0ef41Sopenharmony_ci ...super.params, 4131cb0ef41Sopenharmony_ci ] 4141cb0ef41Sopenharmony_ci 4151cb0ef41Sopenharmony_ci static usage = ['[fix|signatures]'] 4161cb0ef41Sopenharmony_ci 4171cb0ef41Sopenharmony_ci static async completion (opts) { 4181cb0ef41Sopenharmony_ci const argv = opts.conf.argv.remain 4191cb0ef41Sopenharmony_ci 4201cb0ef41Sopenharmony_ci if (argv.length === 2) { 4211cb0ef41Sopenharmony_ci return ['fix', 'signatures'] 4221cb0ef41Sopenharmony_ci } 4231cb0ef41Sopenharmony_ci 4241cb0ef41Sopenharmony_ci switch (argv[2]) { 4251cb0ef41Sopenharmony_ci case 'fix': 4261cb0ef41Sopenharmony_ci case 'signatures': 4271cb0ef41Sopenharmony_ci return [] 4281cb0ef41Sopenharmony_ci default: 4291cb0ef41Sopenharmony_ci throw Object.assign(new Error(argv[2] + ' not recognized'), { 4301cb0ef41Sopenharmony_ci code: 'EUSAGE', 4311cb0ef41Sopenharmony_ci }) 4321cb0ef41Sopenharmony_ci } 4331cb0ef41Sopenharmony_ci } 4341cb0ef41Sopenharmony_ci 4351cb0ef41Sopenharmony_ci async exec (args) { 4361cb0ef41Sopenharmony_ci if (args[0] === 'signatures') { 4371cb0ef41Sopenharmony_ci await this.auditSignatures() 4381cb0ef41Sopenharmony_ci } else { 4391cb0ef41Sopenharmony_ci await this.auditAdvisories(args) 4401cb0ef41Sopenharmony_ci } 4411cb0ef41Sopenharmony_ci } 4421cb0ef41Sopenharmony_ci 4431cb0ef41Sopenharmony_ci async auditAdvisories (args) { 4441cb0ef41Sopenharmony_ci const fix = args[0] === 'fix' 4451cb0ef41Sopenharmony_ci if (this.npm.config.get('package-lock') === false && fix) { 4461cb0ef41Sopenharmony_ci throw this.usageError('fix can not be used without a package-lock') 4471cb0ef41Sopenharmony_ci } 4481cb0ef41Sopenharmony_ci const reporter = this.npm.config.get('json') ? 'json' : 'detail' 4491cb0ef41Sopenharmony_ci const Arborist = require('@npmcli/arborist') 4501cb0ef41Sopenharmony_ci const opts = { 4511cb0ef41Sopenharmony_ci ...this.npm.flatOptions, 4521cb0ef41Sopenharmony_ci audit: true, 4531cb0ef41Sopenharmony_ci path: this.npm.prefix, 4541cb0ef41Sopenharmony_ci reporter, 4551cb0ef41Sopenharmony_ci workspaces: this.workspaceNames, 4561cb0ef41Sopenharmony_ci } 4571cb0ef41Sopenharmony_ci 4581cb0ef41Sopenharmony_ci const arb = new Arborist(opts) 4591cb0ef41Sopenharmony_ci await arb.audit({ fix }) 4601cb0ef41Sopenharmony_ci if (fix) { 4611cb0ef41Sopenharmony_ci await reifyFinish(this.npm, arb) 4621cb0ef41Sopenharmony_ci } else { 4631cb0ef41Sopenharmony_ci // will throw if there's an error, because this is an audit command 4641cb0ef41Sopenharmony_ci auditError(this.npm, arb.auditReport) 4651cb0ef41Sopenharmony_ci const result = npmAuditReport(arb.auditReport, { 4661cb0ef41Sopenharmony_ci ...opts, 4671cb0ef41Sopenharmony_ci chalk: this.npm.chalk, 4681cb0ef41Sopenharmony_ci }) 4691cb0ef41Sopenharmony_ci process.exitCode = process.exitCode || result.exitCode 4701cb0ef41Sopenharmony_ci this.npm.output(result.report) 4711cb0ef41Sopenharmony_ci } 4721cb0ef41Sopenharmony_ci } 4731cb0ef41Sopenharmony_ci 4741cb0ef41Sopenharmony_ci async auditSignatures () { 4751cb0ef41Sopenharmony_ci if (this.npm.global) { 4761cb0ef41Sopenharmony_ci throw Object.assign( 4771cb0ef41Sopenharmony_ci new Error('`npm audit signatures` does not support global packages'), { 4781cb0ef41Sopenharmony_ci code: 'EAUDITGLOBAL', 4791cb0ef41Sopenharmony_ci } 4801cb0ef41Sopenharmony_ci ) 4811cb0ef41Sopenharmony_ci } 4821cb0ef41Sopenharmony_ci 4831cb0ef41Sopenharmony_ci log.verbose('loading installed dependencies') 4841cb0ef41Sopenharmony_ci const Arborist = require('@npmcli/arborist') 4851cb0ef41Sopenharmony_ci const opts = { 4861cb0ef41Sopenharmony_ci ...this.npm.flatOptions, 4871cb0ef41Sopenharmony_ci path: this.npm.prefix, 4881cb0ef41Sopenharmony_ci workspaces: this.workspaceNames, 4891cb0ef41Sopenharmony_ci } 4901cb0ef41Sopenharmony_ci 4911cb0ef41Sopenharmony_ci const arb = new Arborist(opts) 4921cb0ef41Sopenharmony_ci const tree = await arb.loadActual() 4931cb0ef41Sopenharmony_ci let filterSet = new Set() 4941cb0ef41Sopenharmony_ci if (opts.workspaces && opts.workspaces.length) { 4951cb0ef41Sopenharmony_ci filterSet = 4961cb0ef41Sopenharmony_ci arb.workspaceDependencySet( 4971cb0ef41Sopenharmony_ci tree, 4981cb0ef41Sopenharmony_ci opts.workspaces, 4991cb0ef41Sopenharmony_ci this.npm.flatOptions.includeWorkspaceRoot 5001cb0ef41Sopenharmony_ci ) 5011cb0ef41Sopenharmony_ci } else if (!this.npm.flatOptions.workspacesEnabled) { 5021cb0ef41Sopenharmony_ci filterSet = 5031cb0ef41Sopenharmony_ci arb.excludeWorkspacesDependencySet(tree) 5041cb0ef41Sopenharmony_ci } 5051cb0ef41Sopenharmony_ci 5061cb0ef41Sopenharmony_ci const verify = new VerifySignatures(tree, filterSet, this.npm, { ...opts }) 5071cb0ef41Sopenharmony_ci await verify.run() 5081cb0ef41Sopenharmony_ci } 5091cb0ef41Sopenharmony_ci} 5101cb0ef41Sopenharmony_ci 5111cb0ef41Sopenharmony_cimodule.exports = Audit 512