File: /home/frenchy/www/french-american.org/current/node_modules/snyk/dist/lib/snyk-test/run-test.js
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const _ = require("lodash");
const fs = require("fs");
const pathUtil = require("path");
const moduleToObject = require("snyk-module");
const depGraphLib = require("@snyk/dep-graph");
const analytics = require("../analytics");
const config = require("../config");
const detect = require("../../lib/detect");
const plugins = require("../plugins");
const module_info_1 = require("../module-info");
const is_ci_1 = require("../is-ci");
const request = require("../request");
const snyk = require("../");
const spinner = require("../spinner");
const common = require("./common");
const gemfileLockToDependencies = require("../../lib/plugins/rubygems/gemfile-lock-to-dependencies");
const legacy_1 = require("./legacy");
const errors_1 = require("../errors");
const print_deps_1 = require("../print-deps");
const prune_1 = require("../prune");
const cli_interface_1 = require("@snyk/cli-interface");
const authentication_failed_error_1 = require("../errors/authentication-failed-error");
// tslint:disable-next-line:no-var-requires
const debug = require('debug')('snyk');
function runTest(packageManager, root, options) {
return __awaiter(this, void 0, void 0, function* () {
const results = [];
const spinnerLbl = 'Querying vulnerabilities database...';
try {
const payloads = yield assemblePayloads(root, options);
for (const payload of payloads) {
const payloadPolicy = payload.body && payload.body.policy;
const depGraph = payload.body && payload.body.depGraph;
let dockerfilePackages;
if (payload.body &&
payload.body.docker &&
payload.body.docker.dockerfilePackages) {
dockerfilePackages = payload.body.docker.dockerfilePackages;
}
yield spinner(spinnerLbl);
analytics.add('depGraph', !!depGraph);
analytics.add('isDocker', !!(payload.body && payload.body.docker));
// Type assertion might be a lie, but we are correcting that below
let res = (yield sendTestPayload(payload));
if (depGraph) {
res = legacy_1.convertTestDepGraphResultToLegacy(res, // Double "as" required by Typescript for dodgy assertions
depGraph, packageManager, options.severityThreshold);
// For Node.js: inject additional information (for remediation etc.) into the response.
if (payload.modules) {
res.dependencyCount = payload.modules.numDependencies;
if (res.vulnerabilities) {
res.vulnerabilities.forEach((vuln) => {
if (payload.modules && payload.modules.pluck) {
const plucked = payload.modules.pluck(vuln.from, vuln.name, vuln.version);
vuln.__filename = plucked.__filename;
vuln.shrinkwrap = plucked.shrinkwrap;
vuln.bundled = plucked.bundled;
// this is an edgecase when we're testing the directly vuln pkg
if (vuln.from.length === 1) {
return;
}
const parentPkg = moduleToObject(vuln.from[1]);
const parent = payload.modules.pluck(vuln.from.slice(0, 2), parentPkg.name, parentPkg.version);
vuln.parentDepType = parent.depType;
}
});
}
}
}
// TODO: is this needed? we filter on the other side already based on policy
// this will move to be filtered server side soon & it will support `'ignore-policy'`
analytics.add('vulns-pre-policy', res.vulnerabilities.length);
res.filesystemPolicy = !!payloadPolicy;
if (!options['ignore-policy']) {
res.policy = res.policy || payloadPolicy;
const policy = yield snyk.policy.loadFromText(res.policy);
res = policy.filter(res, root);
}
analytics.add('vulns', res.vulnerabilities.length);
if (res.docker && dockerfilePackages) {
res.vulnerabilities = res.vulnerabilities.map((vuln) => {
const dockerfilePackage = dockerfilePackages[vuln.name.split('/')[0]];
if (dockerfilePackage) {
vuln.dockerfileInstruction =
dockerfilePackage.instruction;
}
vuln.dockerBaseImage = res.docker.baseImage;
return vuln;
});
}
if (options.docker &&
options.file &&
options['exclude-base-image-vulns']) {
res.vulnerabilities = res.vulnerabilities.filter((vuln) => vuln.dockerfileInstruction);
}
res.uniqueCount = countUniqueVulns(res.vulnerabilities);
results.push(res);
}
return results;
}
catch (error) {
debug('Error running test', { error });
// handling denial from registry because of the feature flag
// currently done for go.mod
if (error.code === 403 && error.message.includes('Feature not allowed')) {
throw errors_1.NoSupportedManifestsFoundError([root]);
}
throw new errors_1.FailedToRunTestError(error.userMessage ||
error.message ||
`Failed to test ${packageManager} project`, error.code);
}
finally {
spinner.clear(spinnerLbl)();
}
});
}
function sendTestPayload(payload) {
const filesystemPolicy = payload.body && !!payload.body.policy;
return new Promise((resolve, reject) => {
request(payload, (error, res, body) => {
if (error) {
return reject(error);
}
if (res.statusCode !== 200) {
const err = handleTestHttpErrorResponse(res, body);
return reject(err);
}
body.filesystemPolicy = filesystemPolicy;
resolve(body);
});
});
}
function handleTestHttpErrorResponse(res, body) {
const { statusCode } = res;
let err;
const userMessage = body && body.userMessage;
switch (statusCode) {
case 401:
case 403:
err = authentication_failed_error_1.AuthFailedError(userMessage, statusCode);
err.innerError = body.stack;
break;
case 500:
err = new errors_1.InternalServerError(userMessage);
err.innerError = body.stack;
break;
default:
err = new errors_1.FailedToGetVulnerabilitiesError(userMessage, statusCode);
err.innerError = body.error;
}
return err;
}
function assemblePayloads(root, options) {
let isLocal;
if (options.docker) {
isLocal = true;
}
else {
// TODO: Refactor this check so we don't require files when tests are using mocks
isLocal = fs.existsSync(root);
}
analytics.add('local', isLocal);
if (isLocal) {
return assembleLocalPayloads(root, options);
}
return assembleRemotePayloads(root, options);
}
// Force getDepsFromPlugin to return scannedProjects for processing in assembleLocalPayload
function getDepsFromPlugin(root, options) {
return __awaiter(this, void 0, void 0, function* () {
options.file = options.file || detect.detectPackageFile(root);
if (!options.docker && !(options.file || options.packageManager)) {
throw errors_1.NoSupportedManifestsFoundError([...root]);
}
const plugin = plugins.loadPlugin(options.packageManager, options);
const moduleInfo = module_info_1.ModuleInfo(plugin, options.policy);
const inspectRes = yield moduleInfo.inspect(root, options.file, Object.assign({}, options));
if (!cli_interface_1.legacyPlugin.isMultiResult(inspectRes)) {
if (!inspectRes.package) {
// something went wrong if both are not present...
throw Error(`error getting dependencies from ${options.packageManager} ` +
"plugin: neither 'package' nor 'scannedProjects' were found");
}
if (!inspectRes.package.targetFile && inspectRes.plugin) {
inspectRes.package.targetFile = inspectRes.plugin.targetFile;
}
// We are using "options" to store some information returned from plugin that we need to use later,
// but don't want to send to Registry in the Payload.
// TODO(kyegupov): decouple inspect and payload so that we don't need this hack
if (inspectRes.plugin.meta &&
inspectRes.plugin.meta.allSubProjectNames &&
inspectRes.plugin.meta.allSubProjectNames.length > 1) {
options.advertiseSubprojectsCount =
inspectRes.plugin.meta.allSubProjectNames.length;
}
return {
plugin: inspectRes.plugin,
scannedProjects: [{ depTree: inspectRes.package }],
};
}
else {
// We are using "options" to store some information returned from plugin that we need to use later,
// but don't want to send to Registry in the Payload.
// TODO(kyegupov): decouple inspect and payload so that we don't need this hack
options.subProjectNames = inspectRes.scannedProjects.map((scannedProject) => scannedProject.depTree.name);
return inspectRes;
}
});
}
// Payload to send to the Registry for scanning a package from the local filesystem.
function assembleLocalPayloads(root, options) {
return __awaiter(this, void 0, void 0, function* () {
const analysisType = options.docker ? 'docker' : options.packageManager;
const spinnerLbl = 'Analyzing ' +
analysisType +
' dependencies for ' +
(pathUtil.relative('.', pathUtil.join(root, options.file || '')) ||
pathUtil.relative('..', '.') + ' project dir');
try {
const payloads = [];
yield spinner(spinnerLbl);
const deps = yield getDepsFromPlugin(root, options);
analytics.add('pluginName', deps.plugin.name);
for (const scannedProject of deps.scannedProjects) {
const pkg = scannedProject.depTree;
if (options['print-deps']) {
yield spinner.clear(spinnerLbl)();
print_deps_1.maybePrintDeps(options, pkg);
}
if (deps.plugin && deps.plugin.packageManager) {
options.packageManager = deps.plugin.packageManager;
}
if (pkg.docker) {
const baseImageFromDockerfile = pkg.docker.baseImage;
if (!baseImageFromDockerfile && options['base-image']) {
pkg.docker.baseImage = options['base-image'];
}
if (baseImageFromDockerfile && deps.plugin && deps.plugin.imageLayers) {
analytics.add('BaseImage', baseImageFromDockerfile);
analytics.add('imageLayers', deps.plugin.imageLayers);
}
}
if (_.get(pkg, 'files.gemfileLock.contents')) {
const gemfileLockBase64 = pkg.files.gemfileLock.contents;
const gemfileLockContents = Buffer.from(gemfileLockBase64, 'base64').toString();
pkg.dependencies = gemfileLockToDependencies(gemfileLockContents);
}
let policyLocations = [options['policy-path'] || root];
if (options.docker) {
policyLocations = policyLocations.filter((loc) => {
return loc !== root;
});
}
else if (['npm', 'yarn'].indexOf(options.packageManager) > -1) {
policyLocations = policyLocations.concat(pluckPolicies(pkg));
}
debug('policies found', policyLocations);
analytics.add('policies', policyLocations.length);
analytics.add('packageManager', options.packageManager);
addPackageAnalytics(pkg);
let policy;
if (policyLocations.length > 0) {
try {
policy = yield snyk.policy.load(policyLocations, options);
}
catch (err) {
// note: inline catch, to handle error from .load
// if the .snyk file wasn't found, it is fine
if (err.code !== 'ENOENT') {
throw err;
}
}
}
let body = {
targetFile: pkg.targetFile,
projectNameOverride: options.projectName,
policy: policy && policy.toString(),
docker: pkg.docker,
hasDevDependencies: pkg.hasDevDependencies,
};
if (options.vulnEndpoint) {
// options.vulnEndpoint is only used by `snyk protect` (i.e. local filesystem tests).
body = Object.assign({}, body, pkg);
}
else {
// Graphs are more compact and robust representations.
// Legacy parts of the code are still using trees, but will eventually be fully migrated.
debug('converting dep-tree to dep-graph', {
name: pkg.name,
targetFile: scannedProject.targetFile || options.file,
});
let depGraph = yield depGraphLib.legacy.depTreeToGraph(pkg, options.packageManager);
debug('done converting dep-tree to dep-graph', {
uniquePkgsCount: depGraph.getPkgs().length,
});
if (options['prune-repeated-subdependencies']) {
debug('Trying to prune the graph');
const prePruneDepCount = prune_1.countPathsToGraphRoot(depGraph);
debug('pre prunedPathsCount: ' + prePruneDepCount);
depGraph = yield prune_1.pruneGraph(depGraph, options.packageManager);
analytics.add('prePrunedPathsCount', prePruneDepCount);
const postPruneDepCount = prune_1.countPathsToGraphRoot(depGraph);
debug('post prunedPathsCount: ' + postPruneDepCount);
analytics.add('postPrunedPathsCount', postPruneDepCount);
}
body.depGraph = depGraph;
}
const payload = {
method: 'POST',
url: config.API + (options.vulnEndpoint || '/test-dep-graph'),
json: true,
headers: {
'x-is-ci': is_ci_1.isCI(),
authorization: 'token ' + snyk.api,
},
qs: common.assembleQueryString(options),
body,
};
if (['yarn', 'npm'].indexOf(options.packageManager) !== -1) {
const isLockFileBased = options.file &&
(options.file.endsWith('package-lock.json') ||
options.file.endsWith('yarn.lock'));
if (!isLockFileBased || options.traverseNodeModules) {
payload.modules = pkg; // See the output of resolve-deps
}
}
payloads.push(payload);
}
return payloads;
}
finally {
yield spinner.clear(spinnerLbl)();
}
});
}
// Payload to send to the Registry for scanning a remote package.
function assembleRemotePayloads(root, options) {
return __awaiter(this, void 0, void 0, function* () {
const pkg = moduleToObject(root);
debug('testing remote: %s', pkg.name + '@' + pkg.version);
addPackageAnalytics(pkg);
const encodedName = encodeURIComponent(pkg.name + '@' + pkg.version);
// options.vulnEndpoint is only used by `snyk protect` (i.e. local filesystem tests)
const url = `${config.API}${options.vulnEndpoint ||
`/vuln/${options.packageManager}`}/${encodedName}`;
return [
{
method: 'GET',
url,
qs: common.assembleQueryString(options),
json: true,
headers: {
'x-is-ci': is_ci_1.isCI(),
authorization: 'token ' + snyk.api,
},
},
];
});
}
function addPackageAnalytics(module) {
analytics.add('packageName', module.name);
analytics.add('packageVersion', module.version);
analytics.add('package', module.name + '@' + module.version);
}
function countUniqueVulns(vulns) {
const seen = {};
for (const curr of vulns) {
seen[curr.id] = true;
}
return Object.keys(seen).length;
}
function pluckPolicies(pkg) {
if (!pkg) {
return null;
}
if (pkg.snyk) {
return pkg.snyk;
}
if (!pkg.dependencies) {
return null;
}
return _.flatten(Object.keys(pkg.dependencies)
.map((name) => {
return pluckPolicies(pkg.dependencies[name]);
})
.filter(Boolean));
}
module.exports = runTest;
//# sourceMappingURL=run-test.js.map