UNPKG

retire

Version:

Retire is a tool for detecting use of vulnerable libraries

197 lines (178 loc) 5.48 kB
/* * This file is used by the browser plugins and the Cli scanner and thus * cannot have any external dependencies (no require) */ var exports = exports || {}; exports.version = '5.4.2'; function isDefined(o) { return typeof o !== 'undefined'; } function uniq(results) { var keys = {}; return results.filter(function (r) { var k = r.component + ' ' + r.version; keys[k] = keys[k] || 0; return keys[k]++ === 0; }); } function scan(data, extractor, repo, matcher = simpleMatch) { var detected = []; for (var component in repo) { var extractors = repo[component].extractors[extractor]; if (!isDefined(extractors)) continue; for (var i in extractors) { var matches = matcher(extractors[i], data); matches.forEach((match) => { match = match.replace(/(\.|-)min$/, ''); detected.push({ version: match, component: component, npmname: repo[component].npmname, basePurl: repo[component].basePurl, detection: extractor, }); }); } } return uniq(detected); } function simpleMatch(regex, data) { var re = new RegExp(regex, 'g'); const result = []; let match; while ((match = re.exec(data))) { result.push(match[1]); } return result; } function replacementMatch(regex, data) { var ar = /^\/(.*[^\\])\/([^\/]+)\/$/.exec(regex); var re = new RegExp(ar[1], 'g'); const result = []; let match; while ((match = re.exec(data))) { var ver = null; if (match) { ver = match[0].replace(new RegExp(ar[1]), ar[2]); result.push(ver); } } return result; } function splitAndMatchAll(tokenizer) { return function (regex, data) { var elm = data.split(tokenizer).pop(); return simpleMatch('^' + regex + '$', elm); }; } function scanhash(hash, repo) { for (var component in repo) { var hashes = repo[component].extractors.hashes; if (!isDefined(hashes)) continue; if (hashes.hasOwnProperty(hash)) { return [ { version: hashes[hash], component: component, npmname: repo[component].npmname, basePurl: repo[component].basePurl, detection: 'hash', }, ]; } } return []; } function check(results, repo) { for (var r in results) { var result = results[r]; if (!isDefined(repo[result.component])) continue; var vulns = repo[result.component].vulnerabilities; result.basePurl = repo[result.component].basePurl; result.npmname = repo[result.component].npmname; for (var i in vulns) { if (!isDefined(vulns[i].below) || !isAtOrAbove(result.version, vulns[i].below)) { if (isDefined(vulns[i].atOrAbove) && !isAtOrAbove(result.version, vulns[i].atOrAbove)) { continue; } if (isDefined(vulns[i].excludes) && vulns[i].excludes.includes(result.version)) { continue; } var vulnerability = { info: vulns[i].info, below: vulns[i].below, atOrAbove: vulns[i].atOrAbove }; if (vulns[i].severity) { vulnerability.severity = vulns[i].severity; } if (vulns[i].identifiers) { vulnerability.identifiers = vulns[i].identifiers; } if (vulns[i].cwe) { vulnerability.cwe = vulns[i].cwe; } result.vulnerabilities = result.vulnerabilities || []; result.vulnerabilities.push(vulnerability); } } } return results; } function isAtOrAbove(version1, version2) { var v1 = version1.split(/[\.\-]/g); var v2 = version2.split(/[\.\-]/g); var l = v1.length > v2.length ? v1.length : v2.length; for (var i = 0; i < l; i++) { var v1_c = toComparable(v1[i]); var v2_c = toComparable(v2[i]); if (typeof v1_c !== typeof v2_c) return typeof v1_c === 'number'; if (v1_c > v2_c) return true; if (v1_c < v2_c) return false; } return true; } function toComparable(n) { if (!isDefined(n)) return 0; if (n.match(/^[0-9]+$/)) { return parseInt(n, 10); } return n; } //------- External API ------- exports.check = function (component, version, repo) { return check([{ component: component, version: version }], repo); }; exports.replaceVersion = function (jsRepoJsonAsText) { return jsRepoJsonAsText.replace(/§§version§§/g, '[0-9][0-9.a-z_\\\\-]+'); }; exports.isVulnerable = function (results) { for (var r in results) { if ( results[r].hasOwnProperty('vulnerabilities') && results[r].vulnerabilities != undefined && results[r].vulnerabilities.length > 0 ) return true; } return false; }; exports.scanUri = function (uri, repo) { var result = scan(uri, 'uri', repo); return check(result, repo); }; exports.scanFileName = function (fileName, repo, includeUri = false) { var result = scan(fileName, 'filename', repo, splitAndMatchAll(/[\/\\]/)); if (includeUri) { result = result.concat(scan(fileName.replace(/\\/g, '/'), 'uri', repo)); } return check(result, repo); }; exports.scanFileContent = function (content, repo, hasher) { var normalizedContent = content.toString().replace(/(\r\n|\r)/g, '\n'); var result = scan(normalizedContent, 'filecontent', repo); if (result.length === 0) { result = scan(normalizedContent, 'filecontentreplace', repo, replacementMatch); } if (result.length === 0) { result = scanhash(hasher.sha1(normalizedContent), repo); } return check(result, repo); }; exports.isAtOrAbove = isAtOrAbove;