retire
Version:
Retire is a tool for detecting use of vulnerable libraries
184 lines (183 loc) • 6.75 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.scanJsFile = scanJsFile;
exports.scanBowerFile = scanBowerFile;
exports.on = on;
const events_1 = require("events");
const retire = __importStar(require("./retire"));
const fs = __importStar(require("fs"));
const crypto = __importStar(require("crypto"));
const path = __importStar(require("path"));
const depsdev_1 = require("./depsdev");
const deepscan_1 = require("./deepscan");
const license_1 = require("./license");
const events = new events_1.EventEmitter();
const hash = {
sha1: (data) => {
const shasum = crypto.createHash('sha1');
shasum.update(data);
return shasum.digest('hex');
},
};
function emitResults(finding, options, repo) {
if (options.includeOsv === true) {
Promise.all(finding.results.map((r) => (0, depsdev_1.checkOSV)(r.component, r.version, options).then((v) => (r.vulnerabilities = (r.vulnerabilities ?? []).concat(v))))).then(() => filterAndEmitResults(finding, options, repo));
}
else {
filterAndEmitResults(finding, options, repo);
}
}
function getIdentifiers(v) {
return (v.identifiers?.CVE ?? [])
.concat(v.identifiers?.bug ?? [])
.concat(v.identifiers?.issue ?? [])
.concat(v.identifiers?.githubID ?? []);
}
function uniqueVulnerabilities(vulnerabilities) {
if (!vulnerabilities)
return undefined;
const unique = [];
for (const v of vulnerabilities) {
if (!unique.some((u) => getIdentifiers(u).some((i) => getIdentifiers(v).includes(i)))) {
unique.push(v);
}
}
return unique;
}
function addLicenses(components, repo) {
components.forEach((c) => {
const possibleLicenses = repo[c.component]?.licenses;
if (possibleLicenses)
c.licenses = (0, license_1.evaluateLicense)(possibleLicenses, c.version);
});
}
function filterAndEmitResults(finding, options, repo) {
finding.results.forEach((r) => (r.vulnerabilities = uniqueVulnerabilities(r.vulnerabilities)));
if (options.ignore)
removeIgnored(finding.results, options.ignore);
if (finding.results.length == 0)
return;
addLicenses(finding.results, repo);
if (retire.isVulnerable(finding.results)) {
events.emit('vulnerable-dependency-found', finding);
}
else {
events.emit('dependency-found', finding);
}
}
function shouldIgnorePath(fileSpecs, ignores) {
return (ignores.paths?.some((i) => {
return fileSpecs.some((j) => i.test(j) || i.test(path.resolve(j)));
}) ?? false);
}
function removeIgnored(results, ignores) {
if (!('descriptors' in ignores))
return;
results.forEach((r) => {
if (!('vulnerabilities' in r))
return;
ignores.descriptors
?.filter((d) => 'component' in d)
.forEach((i) => {
if (r.component !== i.component)
return;
if (i.version && r.version !== i.version)
return;
if (i.severity) {
//Remove vulnerabilities with the severity we want to drop
r.vulnerabilities = r.vulnerabilities?.filter((v) => v.severity != i.severity);
return;
}
if (i.identifiers) {
removeIgnoredVulnerabilitiesByIdentifier({ ...i.identifiers }, r);
return;
}
r.vulnerabilities = [];
});
if (r.vulnerabilities?.length === 0)
delete r.vulnerabilities;
});
}
function removeIgnoredVulnerabilitiesByIdentifier(identifiers, result) {
result.vulnerabilities = result.vulnerabilities?.filter((v) => {
if (!('identifiers' in v))
return true;
return !Object.entries(identifiers || {}).every(([key, value]) => hasIdentifier({ ...v.identifiers }, key, value));
});
}
function hasIdentifier(identifiers, key, value) {
if (!(key in identifiers))
return false;
const identifier = identifiers[key];
return Array.isArray(identifier) ? identifier.some((x) => x === value) : identifier === value;
}
function scanJsFile(file, repo, options) {
if (options.ignore && shouldIgnorePath([file], options.ignore)) {
return;
}
let results = retire.scanFileName(file, repo, true);
if (!results || results.length === 0) {
const content = fs.readFileSync(file, 'utf-8');
results = retire.scanFileContent(content, repo, hash);
if (options.deep) {
try {
results = results.concat((0, deepscan_1.deepScan)(content, repo));
}
catch (e) {
options.log.warn(`Failed to scan ${file}: ` + e);
}
}
}
emitResults({ file: file, results: results }, options, repo);
}
function scanBowerFile(file, repo, options) {
if (options.ignore && shouldIgnorePath([file], options.ignore)) {
return;
}
try {
const bower = JSON.parse(fs.readFileSync(file, 'utf-8'));
if (bower.version) {
const results = retire.check(bower.name, bower.version, repo);
emitResults({ file: file, results: results }, options, repo);
}
}
catch (e) {
options.log.warn(`Could not parse file: ${file}`);
}
}
function on(event, handler) {
events.on(event, handler);
}