diff options
Diffstat (limited to 'deps/node/deps/npm/lib/install/audit.js')
-rw-r--r-- | deps/node/deps/npm/lib/install/audit.js | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/deps/node/deps/npm/lib/install/audit.js b/deps/node/deps/npm/lib/install/audit.js new file mode 100644 index 00000000..f5bc5ae1 --- /dev/null +++ b/deps/node/deps/npm/lib/install/audit.js @@ -0,0 +1,279 @@ +'use strict' +exports.generate = generate +exports.generateFromInstall = generateFromInstall +exports.submitForInstallReport = submitForInstallReport +exports.submitForFullReport = submitForFullReport +exports.printInstallReport = printInstallReport +exports.printParseableReport = printParseableReport +exports.printFullReport = printFullReport + +const auditReport = require('npm-audit-report') +const npmConfig = require('../config/figgy-config.js') +const figgyPudding = require('figgy-pudding') +const treeToShrinkwrap = require('../shrinkwrap.js').treeToShrinkwrap +const packageId = require('../utils/package-id.js') +const output = require('../utils/output.js') +const npm = require('../npm.js') +const qw = require('qw') +const regFetch = require('npm-registry-fetch') +const perf = require('../utils/perf.js') +const npa = require('npm-package-arg') +const uuid = require('uuid') +const ssri = require('ssri') +const cloneDeep = require('lodash.clonedeep') + +// used when scrubbing module names/specifiers +const runId = uuid.v4() + +const InstallAuditConfig = figgyPudding({ + color: {}, + json: {}, + unicode: {} +}, { + other (key) { + return /:registry$/.test(key) + } +}) + +function submitForInstallReport (auditData) { + const opts = InstallAuditConfig(npmConfig()) + const scopedRegistries = [...opts.keys()].filter( + k => /:registry$/.test(k) + ).map(k => opts[k]) + scopedRegistries.forEach(registry => { + // we don't care about the response so destroy the stream if we can, or leave it flowing + // so it can eventually finish and clean up after itself + regFetch('/-/npm/v1/security/audits/quick', opts.concat({ + method: 'POST', + registry, + gzip: true, + body: auditData + })).then(_ => { + _.body.on('error', () => {}) + if (_.body.destroy) { + _.body.destroy() + } else { + _.body.resume() + } + }, _ => {}) + }) + perf.emit('time', 'audit submit') + return regFetch('/-/npm/v1/security/audits/quick', opts.concat({ + method: 'POST', + gzip: true, + body: auditData + })).then(response => { + perf.emit('timeEnd', 'audit submit') + perf.emit('time', 'audit body') + return response.json() + }).then(result => { + perf.emit('timeEnd', 'audit body') + return result + }) +} + +function submitForFullReport (auditData) { + perf.emit('time', 'audit submit') + const opts = InstallAuditConfig(npmConfig()) + return regFetch('/-/npm/v1/security/audits', opts.concat({ + method: 'POST', + gzip: true, + body: auditData + })).then(response => { + perf.emit('timeEnd', 'audit submit') + perf.emit('time', 'audit body') + return response.json() + }).then(result => { + perf.emit('timeEnd', 'audit body') + result.runId = runId + return result + }) +} + +function printInstallReport (auditResult) { + const opts = InstallAuditConfig(npmConfig()) + return auditReport(auditResult, { + reporter: 'install', + withColor: opts.color, + withUnicode: opts.unicode + }).then(result => output(result.report)) +} + +function printFullReport (auditResult) { + const opts = InstallAuditConfig(npmConfig()) + return auditReport(auditResult, { + log: output, + reporter: opts.json ? 'json' : 'detail', + withColor: opts.color, + withUnicode: opts.unicode + }).then(result => output(result.report)) +} + +function printParseableReport (auditResult) { + const opts = InstallAuditConfig(npmConfig()) + return auditReport(auditResult, { + log: output, + reporter: 'parseable', + withColor: opts.color, + withUnicode: opts.unicode + }).then(result => output(result.report)) +} + +function generate (shrinkwrap, requires, diffs, install, remove) { + const sw = cloneDeep(shrinkwrap) + delete sw.lockfileVersion + sw.requires = scrubRequires(requires) + scrubDeps(sw.dependencies) + + // sw.diffs = diffs || {} + sw.install = (install || []).map(scrubArg) + sw.remove = (remove || []).map(scrubArg) + return generateMetadata().then((md) => { + sw.metadata = md + return sw + }) +} + +const scrubKeys = qw`version` +const deleteKeys = qw`from resolved` + +function scrubDeps (deps) { + if (!deps) return + Object.keys(deps).forEach(name => { + if (!shouldScrubName(name) && !shouldScrubSpec(name, deps[name].version)) return + const value = deps[name] + delete deps[name] + deps[scrub(name)] = value + }) + Object.keys(deps).forEach(name => { + for (let toScrub of scrubKeys) { + if (!deps[name][toScrub]) continue + deps[name][toScrub] = scrubSpec(name, deps[name][toScrub]) + } + for (let toDelete of deleteKeys) delete deps[name][toDelete] + + scrubRequires(deps[name].requires) + scrubDeps(deps[name].dependencies) + }) +} + +function scrubRequires (reqs) { + if (!reqs) return reqs + Object.keys(reqs).forEach(name => { + const spec = reqs[name] + if (shouldScrubName(name) || shouldScrubSpec(name, spec)) { + delete reqs[name] + reqs[scrub(name)] = scrubSpec(name, spec) + } else { + reqs[name] = scrubSpec(name, spec) + } + }) + return reqs +} + +function getScope (name) { + if (name[0] === '@') return name.slice(0, name.indexOf('/')) +} + +function shouldScrubName (name) { + const scope = getScope(name) + const cfg = npm.config // avoid the no-dynamic-lookups test + return Boolean(scope && cfg.get(scope + ':registry')) +} +function shouldScrubSpec (name, spec) { + const req = npa.resolve(name, spec) + return !req.registry +} + +function scrubArg (arg) { + const req = npa(arg) + let name = req.name + if (shouldScrubName(name) || shouldScrubSpec(name, req.rawSpec)) { + name = scrubName(name) + } + const spec = scrubSpec(req.name, req.rawSpec) + return name + '@' + spec +} + +function scrubName (name) { + return shouldScrubName(name) ? scrub(name) : name +} + +function scrubSpec (name, spec) { + const req = npa.resolve(name, spec) + if (req.registry) return spec + if (req.type === 'git') { + return 'git+ssh://' + scrub(spec) + } else if (req.type === 'remote') { + return 'https://' + scrub(spec) + } else if (req.type === 'directory') { + return 'file:' + scrub(spec) + } else if (req.type === 'file') { + return 'file:' + scrub(spec) + '.tar' + } else { + return scrub(spec) + } +} + +module.exports.scrub = scrub +function scrub (value, rid) { + return ssri.fromData((rid || runId) + ' ' + value, {algorithms: ['sha256']}).hexDigest() +} + +function generateMetadata () { + const meta = {} + meta.npm_version = npm.version + meta.node_version = process.version + meta.platform = process.platform + meta.node_env = process.env.NODE_ENV + + return Promise.resolve(meta) +} +/* + const head = path.resolve(npm.prefix, '.git/HEAD') + return readFile(head, 'utf8').then((head) => { + if (!head.match(/^ref: /)) { + meta.commit_hash = head.trim() + return + } + const headFile = head.replace(/^ref: /, '').trim() + meta.branch = headFile.replace(/^refs[/]heads[/]/, '') + return readFile(path.resolve(npm.prefix, '.git', headFile), 'utf8') + }).then((commitHash) => { + meta.commit_hash = commitHash.trim() + const proc = spawn('git', qw`diff --quiet --exit-code package.json package-lock.json`, {cwd: npm.prefix, stdio: 'ignore'}) + return new Promise((resolve, reject) => { + proc.once('error', reject) + proc.on('exit', (code, signal) => { + if (signal == null) meta.state = code === 0 ? 'clean' : 'dirty' + resolve() + }) + }) + }).then(() => meta, () => meta) +*/ + +function generateFromInstall (tree, diffs, install, remove) { + const requires = {} + tree.requires.forEach((pkg) => { + requires[pkg.package.name] = tree.package.dependencies[pkg.package.name] || tree.package.devDependencies[pkg.package.name] || pkg.package.version + }) + + const auditInstall = (install || []).filter((a) => a.name).map(packageId) + const auditRemove = (remove || []).filter((a) => a.name).map(packageId) + const auditDiffs = {} + diffs.forEach((action) => { + const mutation = action[0] + const child = action[1] + if (mutation !== 'add' && mutation !== 'update' && mutation !== 'remove') return + if (!auditDiffs[mutation]) auditDiffs[mutation] = [] + if (mutation === 'add') { + auditDiffs[mutation].push({location: child.location}) + } else if (mutation === 'update') { + auditDiffs[mutation].push({location: child.location, previous: packageId(child.oldPkg)}) + } else if (mutation === 'remove') { + auditDiffs[mutation].push({previous: packageId(child)}) + } + }) + + return generate(treeToShrinkwrap(tree), requires, auditDiffs, auditInstall, auditRemove) +} |