diff --git a/src/lib/monitor/index.ts b/src/lib/monitor/index.ts index 051dc4d4926..cb9a78e4a76 100644 --- a/src/lib/monitor/index.ts +++ b/src/lib/monitor/index.ts @@ -1,4 +1,6 @@ import * as Debug from 'debug'; +import * as path from 'path'; + import * as depGraphLib from '@snyk/dep-graph'; import * as snyk from '..'; import { apiTokenExists } from '../api-token'; @@ -185,6 +187,13 @@ async function monitorDepTree( treeMissingDeps = missingDeps; } + let targetFileDir; + + if (targetFileRelativePath) { + const { dir } = path.parse(targetFileRelativePath); + targetFileDir = dir; + } + const policy = await findAndLoadPolicy( root, meta.isDocker ? 'docker' : packageManager!, @@ -192,6 +201,7 @@ async function monitorDepTree( // TODO: fix this and send only send when we used resolve-deps for node // it should be a ExpandedPkgTree type instead depTree, + targetFileDir, ); const target = await projectMetadata.getInfo(scannedProject, meta, depTree); @@ -328,10 +338,19 @@ export async function monitorDepGraph( ); } + let targetFileDir; + + if (targetFileRelativePath) { + const { dir } = path.parse(targetFileRelativePath); + targetFileDir = dir; + } + const policy = await findAndLoadPolicy( root, meta.isDocker ? 'docker' : packageManager!, options, + undefined, + targetFileDir, ); const target = await projectMetadata.getInfo(scannedProject, meta); @@ -443,6 +462,13 @@ async function experimentalMonitorDepGraphFromDepTree( ); } + let targetFileDir; + + if (targetFileRelativePath) { + const { dir } = path.parse(targetFileRelativePath); + targetFileDir = dir; + } + const policy = await findAndLoadPolicy( root, meta.isDocker ? 'docker' : packageManager!, @@ -450,6 +476,7 @@ async function experimentalMonitorDepGraphFromDepTree( // TODO: fix this and send only send when we used resolve-deps for node // it should be a ExpandedPkgTree type instead depTree, + targetFileDir, ); if (['npm', 'yarn'].includes(meta.packageManager)) { diff --git a/src/lib/policy/find-and-load-policy.ts b/src/lib/policy/find-and-load-policy.ts index 59dfe5f3f87..4e8727769c1 100644 --- a/src/lib/policy/find-and-load-policy.ts +++ b/src/lib/policy/find-and-load-policy.ts @@ -14,11 +14,14 @@ export async function findAndLoadPolicy( scanType: SupportedPackageManagers | 'docker', options: PolicyOptions, pkg?: PackageExpanded, + scannedProjectFolder?: string, ): Promise { const isDocker = scanType === 'docker'; const isNodeProject = ['npm', 'yarn'].includes(scanType); // monitor - let policyLocations: string[] = [options['policy-path'] || root]; + let policyLocations: string[] = [ + options['policy-path'] || scannedProjectFolder || root, + ]; if (isDocker) { policyLocations = policyLocations.filter((loc) => loc !== root); } else if (isNodeProject) { @@ -26,6 +29,7 @@ export async function findAndLoadPolicy( // find and apply policies in node_modules policyLocations = policyLocations.concat(pluckPolicies(pkg as PackageJson)); } + debug('Potential policy locations found:', policyLocations); analytics.add('policies', policyLocations.length); analytics.add('policyLocations', policyLocations); diff --git a/src/lib/snyk-test/run-test.ts b/src/lib/snyk-test/run-test.ts index 04978cd7b77..74b653276e2 100644 --- a/src/lib/snyk-test/run-test.ts +++ b/src/lib/snyk-test/run-test.ts @@ -108,7 +108,6 @@ async function runTest( _.get(payload, 'body.originalProjectName'); const foundProjectCount = _.get(payload, 'body.foundProjectCount'); const displayTargetFile = _.get(payload, 'body.displayTargetFile'); - let dockerfilePackages; if ( payload.body && @@ -402,6 +401,22 @@ async function assembleLocalPayloads( } } + // todo: normalize what target file gets used across plugins and functions + const targetFile = + scannedProject.targetFile || deps.plugin.targetFile || options.file; + + // Forcing options.path to be a string as pathUtil requires is to be stringified + const targetFileRelativePath = targetFile + ? pathUtil.join(pathUtil.resolve(`${options.path || root}`), targetFile) + : ''; + + let targetFileDir; + + if (targetFileRelativePath) { + const { dir } = path.parse(targetFileRelativePath); + targetFileDir = dir; + } + const policy = await findAndLoadPolicy( root, options.docker ? 'docker' : packageManager!, @@ -409,6 +424,7 @@ async function assembleLocalPayloads( // TODO: fix this and send only send when we used resolve-deps for node // it should be a ExpandedPkgTree type instead pkg, + targetFileDir, ); analytics.add('packageManager', packageManager); @@ -421,15 +437,6 @@ async function assembleLocalPayloads( addPackageAnalytics(depTree.name!, depTree.version!); } - // todo: normalize what target file gets used across plugins and functions - const targetFile = - scannedProject.targetFile || deps.plugin.targetFile || options.file; - - // Forcing options.path to be a string as pathUtil requires is to be stringified - const targetFileRelativePath = targetFile - ? pathUtil.join(pathUtil.resolve(`${options.path}`), targetFile) - : ''; - let target: GitTarget | ContainerTarget | null; if (scannedProject.depGraph) { target = await projectMetadata.getInfo(scannedProject, options); diff --git a/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts b/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts index c65b5b570e7..7d13f7ff11a 100644 --- a/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts +++ b/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts @@ -13,7 +13,6 @@ import * as _ from '@snyk/lodash'; // ensure this is required *after* the demo server, since this will // configure our fake configuration too -import * as snykPolicy from 'snyk-policy'; import { AllProjectsTests } from './cli-monitor.all-projects.spec'; const { test, only } = tap; diff --git a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts index dd95ba5931d..4bb744822b1 100644 --- a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts +++ b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts @@ -12,6 +12,37 @@ interface AcceptanceTests { export const AllProjectsTests: AcceptanceTests = { language: 'Mixed', tests: { + '`monitor mono-repo-with-ignores --all-projects` respects .snyk policy': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + await params.cli.monitor('mono-repo-with-ignores', { + allProjects: true, + detectionDepth: 2, + }); + // Pop all calls to server and filter out calls to `featureFlag` endpoint + const requests = params.server + .popRequests(4) + .filter((req) => req.url.includes('/monitor/')); + let policyCount = 0; + requests.forEach((req) => { + const vulnerableFolderPath = + process.platform === 'win32' + ? 'vulnerable\\package-lock.json' + : 'vulnerable/package-lock.json'; + + if (req.body.targetFileRelativePath.endsWith(vulnerableFolderPath)) { + t.match( + req.body.policy, + 'npm:node-uuid:20160328', + 'body contains policy', + ); + policyCount += 1; + } + }); + t.equal(policyCount, 1, 'one policy found'); + }, '`monitor mono-repo-project --all-projects --detection-depth=1`': ( params, utils, diff --git a/test/acceptance/cli-test/cli-test.all-projects.spec.ts b/test/acceptance/cli-test/cli-test.all-projects.spec.ts index e7493c8ff88..cdaa3022ce2 100644 --- a/test/acceptance/cli-test/cli-test.all-projects.spec.ts +++ b/test/acceptance/cli-test/cli-test.all-projects.spec.ts @@ -7,6 +7,67 @@ import { CommandResult } from '../../../src/cli/commands/types'; export const AllProjectsTests: AcceptanceTests = { language: 'Mixed', tests: { + '`test mono-repo-with-ignores --all-projects` respects .snyk policy': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const loadPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + + const result: CommandResult = await params.cli.test( + 'mono-repo-with-ignores', + { + allProjects: true, + detectionDepth: 3, + }, + ); + t.ok(loadPlugin.withArgs('npm').calledTwice, 'calls npm plugin'); + let policyCount = 0; + params.server.popRequests(2).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); + const vulnerableFolderPath = + process.platform === 'win32' + ? 'vulnerable\\package-lock.json' + : 'vulnerable/package-lock.json'; + if (req.body.targetFileRelativePath.endsWith(vulnerableFolderPath)) { + t.match( + req.body.policy, + 'npm:node-uuid:20160328', + 'body contains policy', + ); + policyCount += 1; + } + t.match( + req.body.depGraph.pkgManager.name, + /(npm)/, + 'depGraph has package manager', + ); + }); + + t.match(policyCount, 1, 'one policy should have been found'); + // results should contain test results from both package managers + // and show only 1/2 vulnerable paths for nested one since we ignore + // it in the .snyk file + + t.match( + result.getDisplayResults(), + 'Package manager: npm', + 'contains package manager npm', + ); + t.match( + result.getDisplayResults(), + 'Target file: package-lock.json', + 'contains target file package-lock.json', + ); + }, '`test mono-repo-project with lockfiles --all-projects`': ( params, utils, diff --git a/test/acceptance/cli-test/cli-test.gradle.spec.ts b/test/acceptance/cli-test/cli-test.gradle.spec.ts index 0fda32031df..896486f5c8b 100644 --- a/test/acceptance/cli-test/cli-test.gradle.spec.ts +++ b/test/acceptance/cli-test/cli-test.gradle.spec.ts @@ -112,6 +112,50 @@ export const GradleTests: AcceptanceTests = { }); t.true(((spyPlugin.args[0] as any)[2] as any).allSubProjects); }, + '`test gradle-app --all-sub-projects` with policy': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const plugin = { + async inspect() { + return { plugin: { name: 'gradle' }, package: {} }; + }, + }; + const spyPlugin = sinon.spy(plugin, 'inspect'); + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + loadPlugin.withArgs('gradle').returns(plugin); + + await params.cli.test('gradle-app', { + allSubProjects: true, + }); + t.true(((spyPlugin.args[0] as any)[2] as any).allSubProjects); + const requests = params.server.popRequests(2); + let policyCount = 0; + requests.forEach((req) => { + if ( + req.body.displayTargetFile.endsWith('gradle-multi-project/subproj') + ) { + // TODO: this should return 1 policy when fixed + // uncomment then + // t.match( + // req.body.policy, + // 'SNYK-JAVA-ORGBOUNCYCASTLE-32364', + // 'policy is found & sent', + // ); + t.ok( + req.body.policy, + undefined, + 'policy is not found even though it should be', + ); + policyCount += 1; + } + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + }); + // TODO: this should return 1 policy when fixed + t.equal(policyCount, 0, 'one sub-project policy found & sent'); + }, '`test gradle-app` plugin fails to return package or scannedProjects': ( params, diff --git a/test/acceptance/cli-test/cli-test.yarn.spec.ts b/test/acceptance/cli-test/cli-test.yarn.spec.ts index 624b93a126b..e6f131edaa7 100644 --- a/test/acceptance/cli-test/cli-test.yarn.spec.ts +++ b/test/acceptance/cli-test/cli-test.yarn.spec.ts @@ -187,8 +187,24 @@ export const YarnTests: AcceptanceTests = { 'depGraph looks fine', ); }, - - '`test yarn-package --file=yarn.lock ` sends pkg info': ( + '`test yarn-package --file=yarn-package/yarn.lock ` sends pkg info & policy': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + await params.cli.test({ file: 'yarn-package/yarn.lock' }); + const req = params.server.popRequest(); + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.match(req.body.policy, 'npm:debug:20170905', 'policy is found & sent'); + t.match(req.body.targetFile, undefined, 'target is undefined'); + const depGraph = req.body.depGraph; + t.same( + depGraph.pkgs.map((p) => p.id).sort(), + ['npm-package@1.0.0', 'ms@0.7.1', 'debug@2.2.0'].sort(), + 'depGraph looks fine', + ); + }, + '`test yarn-package --file=yarn.lock ` sends pkg info & policy': ( params, utils, ) => async (t) => { @@ -196,6 +212,23 @@ export const YarnTests: AcceptanceTests = { await params.cli.test('yarn-package', { file: 'yarn.lock' }); const req = params.server.popRequest(); t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.match(req.body.policy, 'npm:debug:20170905', 'policy is found & sent'); + t.match(req.body.targetFile, undefined, 'target is undefined'); + const depGraph = req.body.depGraph; + t.same( + depGraph.pkgs.map((p) => p.id).sort(), + ['npm-package@1.0.0', 'ms@0.7.1', 'debug@2.2.0'].sort(), + 'depGraph looks fine', + ); + }, + '`test yarn-package` sends pkg info & policy': (params, utils) => async ( + t, + ) => { + utils.chdirWorkspaces('yarn-package'); + await params.cli.test(); + const req = params.server.popRequest(); + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.match(req.body.policy, 'npm:debug:20170905', 'policy is found & sent'); t.match(req.body.targetFile, undefined, 'target is undefined'); const depGraph = req.body.depGraph; t.same( diff --git a/test/acceptance/workspaces/gradle-multi-project/build.gradle b/test/acceptance/workspaces/gradle-multi-project/build.gradle new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/acceptance/workspaces/gradle-multi-project/settings.gradle b/test/acceptance/workspaces/gradle-multi-project/settings.gradle new file mode 100644 index 00000000000..90289e8a32b --- /dev/null +++ b/test/acceptance/workspaces/gradle-multi-project/settings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'root-proj' + +include 'subproj' + + diff --git a/test/acceptance/workspaces/gradle-multi-project/subproj/.snyk b/test/acceptance/workspaces/gradle-multi-project/subproj/.snyk new file mode 100644 index 00000000000..d46ea24efac --- /dev/null +++ b/test/acceptance/workspaces/gradle-multi-project/subproj/.snyk @@ -0,0 +1,9 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.14.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JAVA-ORGBOUNCYCASTLE-32364: + - '*': + reason: None Given + expires: 2020-07-19T11:33:19.028Z +patch: {} diff --git a/test/acceptance/workspaces/gradle-multi-project/subproj/build.gradle b/test/acceptance/workspaces/gradle-multi-project/subproj/build.gradle new file mode 100644 index 00000000000..301e7be03bc --- /dev/null +++ b/test/acceptance/workspaces/gradle-multi-project/subproj/build.gradle @@ -0,0 +1,51 @@ +apply plugin: 'java' +apply plugin: 'maven' + +group = 'com.github.jitpack' + +sourceCompatibility = 1.8 // java 8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + // Gradle 3+ will not pick up "compile" dependencies for "compileOnly" + // Gradle 2 will, so for configuration-matching tests we use "runtime" + runtime 'com.google.guava:guava:18.0' + runtime 'batik:batik-dom:1.6' + runtime 'commons-discovery:commons-discovery:0.2' + compileOnly 'axis:axis:1.3' + runtime 'com.android.tools.build:builder:2.3.0' +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +// To specify a license in the pom: +install { + repositories.mavenInstaller { + pom.project { + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + } + } +} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/package-lock.json b/test/acceptance/workspaces/mono-repo-with-ignores/package-lock.json new file mode 100644 index 00000000000..fb619ede5e0 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "mono-repo-with-ignores", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + } + } +} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/package.json b/test/acceptance/workspaces/mono-repo-with-ignores/package.json new file mode 100644 index 00000000000..b87367a529a --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/package.json @@ -0,0 +1,14 @@ +{ + "name": "mono-repo-with-ignores", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "qs": "6.9.4" + } +} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/.snyk b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/.snyk new file mode 100644 index 00000000000..bf755cc8f32 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/.snyk @@ -0,0 +1,13 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.14.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + 'npm:node-uuid:20160328': + - '*': + reason: None Given + expires: 2020-07-17T17:50:18.603Z + 'npm:node-uuid:20111130': + - '*': + reason: None Given + expires: 2020-07-17T17:50:18.603Z +patch: {} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package-lock.json b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package-lock.json new file mode 100644 index 00000000000..fd73bd92c75 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "vulnerable", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "node-uuid": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.3.0.tgz", + "integrity": "sha1-rnqqhsKUGizjr00H3k5jb3A5oFE=" + } + } +} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package.json b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package.json new file mode 100644 index 00000000000..aa31b590077 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package.json @@ -0,0 +1,14 @@ +{ + "name": "vulnerable", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "node-uuid": "1.3.0" + } +} diff --git a/test/acceptance/workspaces/yarn-package/.snyk b/test/acceptance/workspaces/yarn-package/.snyk new file mode 100644 index 00000000000..1cf55a4e446 --- /dev/null +++ b/test/acceptance/workspaces/yarn-package/.snyk @@ -0,0 +1,9 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.14.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + 'npm:debug:20170905': + - '*': + reason: None Given + expires: 2020-07-19T08:17:11.449Z +patch: {} diff --git a/test/package-no-name.test.ts b/test/package-no-name.test.ts index 3102af40024..38752482613 100644 --- a/test/package-no-name.test.ts +++ b/test/package-no-name.test.ts @@ -4,11 +4,9 @@ import * as snyk from '../src/lib'; test('packages with no name read dir', async (t) => { await snyk.test(__dirname + '/fixtures/package-sans-name'); t.pass('succeed'); - t.end(); }); test('packages with no name read dir with a lockfile', async (t) => { await snyk.test(__dirname + '/fixtures/package-sans-name-lockfile'); t.pass('succeed'); - t.end(); });