auditjs
Version:
Audit dependencies to identify known vulnerabilities and maintenance problems
339 lines • 14.4 kB
JavaScript
;
/*
* 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