UNPKG

auditjs

Version:

Audit dependencies to identify known vulnerabilities and maintenance problems

339 lines 14.4 kB
"use strict"; /* * Copyright (c) 2020-Present Erlend Oftedal, Steve Springett, Sonatype, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CycloneDXSbomCreator = void 0; const uuid_1 = require("uuid"); const xmlbuilder_1 = __importDefault(require("xmlbuilder")); const read_installed_1 = __importDefault(require("read-installed")); const ssri = __importStar(require("ssri")); const fs = __importStar(require("fs")); const spdxLicensesNonDeprecated = require("spdx-license-ids"); const spdxLicensesDeprecated = require("spdx-license-ids/deprecated"); const Helpers_1 = require("./Helpers/Helpers"); const Logger_1 = require("../Application/Logger/Logger"); class CycloneDXSbomCreator { constructor(path, options) { this.path = path; this.options = options; this.licenseFilenames = [ 'LICENSE', 'License', 'license', 'LICENCE', 'Licence', 'licence', 'NOTICE', 'Notice', 'notice', ]; this.licenseContentTypes = [ { licenseContentType: 'text/plain', fileExtension: '' }, { licenseContentType: 'text/txt', fileExtension: '.txt' }, { licenseContentType: 'text/markdown', fileExtension: '.md' }, { licenseContentType: 'text/xml', fileExtension: '.xml' }, ]; this.SBOMSCHEMA = 'http://cyclonedx.org/schema/bom/1.1'; } createBom(pkgInfo) { return __awaiter(this, void 0, void 0, function* () { const bom = xmlbuilder_1.default.create('bom', { encoding: 'utf-8', separateArrayItems: true }).att('xmlns', this.SBOMSCHEMA); if (this.options && this.options.includeBomSerialNumber) { bom.att('serialNumber', 'urn:uuid:' + (0, uuid_1.v4)()); } bom.att('version', 1); const componentsNode = bom.ele('components'); const components = this.listComponents(pkgInfo); if (components.length > 0) { componentsNode.ele(components); } const bomString = bom.end({ width: 0, allowEmpty: false, spaceBeforeSlash: '', }); return bomString; }); } getPackageInfoFromReadInstalled(path = this.path) { return new Promise((resolve, reject) => { (0, read_installed_1.default)(path, { dev: this.options && this.options.devDependencies ? this.options.devDependencies : false, }, (err, data) => __awaiter(this, void 0, void 0, function* () { if (err) { reject(err); } resolve(data); })); }); } listComponents(pkg) { const list = {}; const isRootPkg = true; this.addComponent(pkg, list, isRootPkg); return Object.keys(list).map((k) => ({ component: list[k] })); } addComponent(pkg, list, isRootPkg = false) { var _a; const spartan = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.spartan) ? this.options.spartan : false; //read-installed with default options marks devDependencies as extraneous //if a package is marked as extraneous, do not include it as a component if (pkg.extraneous) { return; } if (!isRootPkg) { const pkgIdentifier = this.parsePackageJsonName(pkg.name); const group = pkgIdentifier.scope == undefined ? '' : `@${pkgIdentifier.scope}`; const name = pkgIdentifier.fullName; const version = pkg.version; const purl = (0, Helpers_1.toPurl)(name, version, group); const component = { '@type': this.determinePackageType(pkg), '@bom-ref': purl, group: group, name: name, version: version, purl: purl, }; if (component.group === '') { delete component.group; } if (!spartan) { const description = { '#cdata': pkg.description }; component.description = description; component.hashes = []; component.licenses = []; component.externalReferences = this.addExternalReferences(pkg); if (this.options && this.options.includeLicenseData) { component.licenses = this.getLicenses(pkg); } else { delete component.licenses; } if (component.externalReferences === undefined || component.externalReferences.length === 0) { delete component.externalReferences; } this.processHashes(pkg, component); } if (list[component.purl]) return; //remove cycles list[component.purl] = component; } if (pkg.dependencies) { Object.keys(pkg.dependencies) .map((x) => pkg.dependencies[x]) .filter((x) => typeof x !== 'string') //remove cycles .map((x) => this.addComponent(x, list)); } } /** * If the author has described the module as a 'framework', the take their * word for it, otherwise, identify the module as a 'library'. */ determinePackageType(pkg) { if (pkg.hasOwnProperty('keywords')) { for (const keyword of pkg.keywords) { if (keyword.toLowerCase() === 'framework') { return 'framework'; } } } return 'library'; } /** * Uses the SHA1 shasum (if Present) otherwise utilizes Subresource Integrity * of the package with support for multiple hashing algorithms. */ processHashes(pkg, component) { component.hashes = new Array(); if (pkg._shasum) { component.hashes.push({ hash: { '@alg': 'SHA-1', '#text': pkg._shasum } }); } else if (pkg._integrity) { const integrity = ssri.parse(pkg._integrity); // Components may have multiple hashes with various lengths. Check each one // that is supported by the CycloneDX specification. if (integrity.hasOwnProperty('sha512')) { component.hashes.push(this.addComponentHash('SHA-512', integrity.sha512[0].digest)); } if (integrity.hasOwnProperty('sha384')) { component.hashes.push(this.addComponentHash('SHA-384', integrity.sha384[0].digest)); } if (integrity.hasOwnProperty('sha256')) { component.hashes.push(this.addComponentHash('SHA-256', integrity.sha256[0].digest)); } if (integrity.hasOwnProperty('sha1')) { component.hashes.push(this.addComponentHash('SHA-1', integrity.sha1[0].digest)); } } if (component.hashes.length === 0) { delete component.hashes; // If no hashes exist, delete the hashes node (it's optional) } } /** * Adds a hash to component. */ addComponentHash(alg, digest) { const hash = Buffer.from(digest, 'base64').toString('hex'); return { hash: { '@alg': alg, '#text': hash } }; } /** * Adds external references supported by the package format. */ addExternalReferences(pkg) { const externalReferences = []; if (pkg.homepage) { this.pushURLToExternalReferences('website', pkg.homepage, externalReferences); } if (pkg.bugs && pkg.bugs.url) { this.pushURLToExternalReferences('issue-tracker', pkg.bugs.url, externalReferences); } if (pkg.repository && pkg.repository.url) { this.pushURLToExternalReferences('vcs', pkg.repository.url, externalReferences); } return externalReferences; } pushURLToExternalReferences(typeOfURL, url, externalReferences) { try { const uri = new URL(url); externalReferences.push({ reference: { '@type': typeOfURL, url: uri.toString() } }); } catch (e) { (0, Logger_1.logMessage)('Encountered an invalid URL', Logger_1.DEBUG, { title: e.message, stack: e.stack }); } } /** * Performs a lookup + validation of the license specified in the * package. If the license is a valid SPDX license ID, set the 'id' * of the license object, otherwise, set the 'name' of the license * object. */ getLicenses(pkg) { const spdxLicenses = [...spdxLicensesNonDeprecated, ...spdxLicensesDeprecated]; let license = pkg.license && (pkg.license.type || pkg.license); if (license) { if (!Array.isArray(license)) { license = [license]; } return license .map((l) => { const licenseContent = {}; if (spdxLicenses.some((v) => { return l === v; })) { licenseContent.id = l; } else { licenseContent.name = l; } if (this.options && this.options.includeLicenseText) { licenseContent.text = this.addLicenseText(pkg, l); } return licenseContent; }) .map((l) => ({ license: l })); } return undefined; } /** * Tries to find a file containing the license text based on commonly * used naming and content types. If a candidate file is found, add * the text to the license text object and stop. */ addLicenseText(pkg, licenseName) { for (const licenseFilename of this.licenseFilenames) { for (const { licenseContentType, fileExtension } of this.licenseContentTypes) { let licenseFilepath = `${pkg.realPath}/${licenseFilename}${licenseName}${fileExtension}`; if (fs.existsSync(licenseFilepath)) { return this.readLicenseText(licenseFilepath, licenseContentType); } licenseFilepath = `${pkg.realPath}/${licenseFilename}${fileExtension}`; if (fs.existsSync(licenseFilepath)) { return this.readLicenseText(licenseFilepath, licenseContentType); } } } } /** * Read the file from the given path to the license text object and includes * content-type attribute, if not default. Returns the license text object. */ readLicenseText(licenseFilepath, licenseContentType) { const licenseText = fs.readFileSync(licenseFilepath, 'utf8'); if (licenseText) { const licenseContentText = { '#cdata': licenseText }; if (licenseContentType !== 'text/plain') { licenseContentText['@content-type'] = licenseContentType; } return licenseContentText; } return undefined; } parsePackageJsonName(name) { const result = { scope: undefined, fullName: '', projectName: '', moduleName: '', }; const regexp = new RegExp(/^(?:@([^/]+)\/)?(([^\.]+)(?:\.(.*))?)$/); const matches = name.match(regexp); if (matches) { result.scope = matches[1] || undefined; result.fullName = matches[2] || matches[0]; result.projectName = matches[3] === matches[2] ? undefined : matches[3]; result.moduleName = matches[4] || matches[2] || undefined; } return result; } } exports.CycloneDXSbomCreator = CycloneDXSbomCreator; //# sourceMappingURL=CycloneDXSbomCreator.js.map