|
1 | 1 | 'use strict'; |
2 | | -const childProcess = require('child_process'); |
3 | | -const path = require('path'); |
4 | 2 | const { promises: fs } = require('fs'); |
5 | | -const os = require('os'); |
6 | | -const { glob } = require('glob'); |
7 | | -const { promisify } = require('util'); |
8 | | -const execFile = promisify(childProcess.execFile); |
| 3 | +const path = require('path'); |
| 4 | +const fetch = require('make-fetch-happen'); |
| 5 | + |
| 6 | +const MAKE_FETCH_HAPPEN_OPTIONS = { |
| 7 | + timeout: 10000, |
| 8 | + retry: { |
| 9 | + retries: 3, |
| 10 | + factor: 1, |
| 11 | + minTimeout: 1000, |
| 12 | + maxTimeout: 3000, |
| 13 | + randomize: true, |
| 14 | + }, |
| 15 | +}; |
| 16 | + |
| 17 | +async function snykTest(dependency) { |
| 18 | + const { name, version } = dependency; |
| 19 | + |
| 20 | + process.stdout.write(`Testing ${name}@${version} ... `); |
| 21 | + |
| 22 | + const response = await fetch( |
| 23 | + `https://api.snyk.io/v1/test/npm/${encodeURIComponent(name)}/${version}`, |
| 24 | + { |
| 25 | + ...MAKE_FETCH_HAPPEN_OPTIONS, |
| 26 | + headers: { |
| 27 | + Authorization: `token ${process.env.SNYK_TOKEN}`, |
| 28 | + }, |
| 29 | + } |
| 30 | + ); |
9 | 31 |
|
10 | | -async function fileExists(filePath) { |
11 | | - try { |
12 | | - await fs.access(filePath); |
13 | | - return true; |
14 | | - } catch { |
15 | | - return false; |
| 32 | + if (!response.ok) { |
| 33 | + throw new Error(`HTTP error! status: ${response.status}`); |
16 | 34 | } |
17 | | -} |
18 | 35 |
|
19 | | -async function snykTest(cwd) { |
20 | | - const tmpPath = path.join(os.tmpdir(), 'tempfile-' + Date.now()); |
| 36 | + const vulnerabilities = (await response.json()).issues?.vulnerabilities ?? []; |
21 | 37 |
|
22 | | - let execErr; |
23 | | - |
24 | | - try { |
25 | | - if (!(await fileExists(path.join(cwd, `package.json`)))) { |
26 | | - return; |
27 | | - } |
28 | | - |
29 | | - console.info(`testing ${cwd} ...`); |
30 | | - await fs.mkdir(path.join(cwd, `node_modules`), { recursive: true }); |
31 | | - |
32 | | - try { |
33 | | - await execFile( |
34 | | - 'npx', |
35 | | - [ |
36 | | - 'snyk@latest', |
37 | | - 'test', |
38 | | - '--all-projects', |
39 | | - '--severity-threshold=low', |
40 | | - '--dev', |
41 | | - `--json-file-output=${tmpPath}`, |
42 | | - ], |
43 | | - { |
44 | | - cwd, |
45 | | - maxBuffer: 50 /* MB */ * 1024 * 1024, // default is 1 MB |
46 | | - } |
47 | | - ); |
48 | | - } catch (err) { |
49 | | - execErr = err; |
50 | | - } |
| 38 | + process.stdout.write(`Done\n`); |
51 | 39 |
|
52 | | - const res = JSON.parse(await fs.readFile(tmpPath)); |
53 | | - console.info(`testing ${cwd} done.`); |
54 | | - return res; |
55 | | - } catch (err) { |
56 | | - console.error(`Snyk failed to create a json report for ${cwd}:`, execErr); |
57 | | - throw new Error(`Testing ${cwd} failed.`); |
58 | | - } finally { |
59 | | - try { |
60 | | - await fs.rm(tmpPath); |
61 | | - } catch (error) { |
62 | | - // |
63 | | - } |
64 | | - } |
| 40 | + return vulnerabilities.map((v) => { |
| 41 | + // for some reason the api doesn't add these properties unlike `snyk test` |
| 42 | + return { ...v, name: v.package, fixedIn: v.upgradePath ?? [] }; |
| 43 | + }); |
65 | 44 | } |
66 | 45 |
|
67 | 46 | async function main() { |
68 | | - const rootPath = path.resolve(__dirname, '..'); |
| 47 | + if (!process.env.SNYK_TOKEN) { |
| 48 | + throw new Error('process.env.SNYK_TOKEN is missing.'); |
| 49 | + } |
69 | 50 |
|
70 | | - const { workspaces } = JSON.parse( |
71 | | - await fs.readFile(path.join(rootPath, 'package.json')) |
72 | | - ); |
| 51 | + const rootPath = path.resolve(__dirname, '..'); |
73 | 52 |
|
74 | | - const packages = (await Promise.all(workspaces.map((w) => glob(w)))).flat(); |
| 53 | + const dependenciesFile = path.join(rootPath, '.sbom', 'dependencies.json'); |
| 54 | + const dependencies = JSON.parse(await fs.readFile(dependenciesFile, 'utf-8')); |
75 | 55 |
|
76 | 56 | const results = []; |
77 | 57 |
|
78 | | - results.push(await snykTest(rootPath)); |
79 | | - |
80 | | - for (const location of packages) { |
81 | | - const result = await snykTest(location); |
82 | | - if (result) { |
83 | | - results.push(result); |
| 58 | + for (const dependency of dependencies) { |
| 59 | + const vulnerabilities = await snykTest(dependency); |
| 60 | + if (vulnerabilities && vulnerabilities.length) { |
| 61 | + results.push({ vulnerabilities }); |
84 | 62 | } |
85 | 63 | } |
86 | 64 |
|
87 | | - await fs.mkdir(path.join(rootPath, `.sbom`), { recursive: true }); |
88 | | - |
89 | 65 | await fs.writeFile( |
90 | 66 | path.join(rootPath, `.sbom/snyk-test-result.json`), |
91 | | - JSON.stringify(results.flat(), null, 2) |
92 | | - ); |
93 | | - |
94 | | - await execFile( |
95 | | - 'npx', |
96 | | - [ |
97 | | - 'snyk-to-html', |
98 | | - '-i', |
99 | | - path.join(rootPath, '.sbom/snyk-test-result.json'), |
100 | | - '-o', |
101 | | - path.join(rootPath, `.sbom/snyk-test-result.html`), |
102 | | - ], |
103 | | - { cwd: rootPath } |
| 67 | + JSON.stringify(results, null, 2) |
104 | 68 | ); |
105 | 69 | } |
106 | 70 |
|
|
0 commit comments