diff --git a/README.md b/README.md index 1299bda..c8ed558 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ If vulnerabilities are found by `npm audit`, Action triggered by push, schedule |:--:|:--:|:--:|:--| |audit_level|false|low|The value of `--audit-level` flag| |production_flag|false|false|Runnning `npm audit` with `--production`| +|json_flag|false|false|Runnning `npm audit` with `--json`| |issue_assignees|false|N/A|Issue assignees (separated by commma)| |issue_labels|false|N/A|Issue labels (separated by commma)| |issue_title|false|npm audit found vulnerabilities|Issue title| @@ -33,7 +34,9 @@ If vulnerabilities are found by `npm audit`, Action triggered by push, schedule ### Outputs -N/A +|Parameter name|Description| +|:--:|:--| +|npm_audit|The output of the npm audit report in a text format| ## Example Workflow diff --git a/__tests__/audit.test.ts b/__tests__/audit.test.ts index ebca734..c73b2ba 100644 --- a/__tests__/audit.test.ts +++ b/__tests__/audit.test.ts @@ -30,7 +30,7 @@ describe('run', () => { } }) - audit.run('low', 'false') + audit.run('low', 'false', 'false') expect(audit.foundVulnerability()).toBeTruthy() }) @@ -51,7 +51,28 @@ describe('run', () => { } }) - audit.run('low', 'true') + audit.run('low', 'true', 'false') + expect(audit.foundVulnerability()).toBeTruthy() + }) + + test('finds vulnerabilities with json flag enabled', () => { + mocked(child_process).spawnSync.mockImplementation((): any => { + const stdout = fs.readFileSync( + path.join(__dirname, 'testdata/audit/error.json') + ) + + return { + pid: 100, + output: [stdout], + stdout, + stderr: '', + status: 1, + signal: null, + error: null + } + }) + + audit.run('low', 'false', 'true') expect(audit.foundVulnerability()).toBeTruthy() }) @@ -72,7 +93,7 @@ describe('run', () => { } }) - audit.run('low', 'false') + audit.run('low', 'false', 'false') expect(audit.foundVulnerability()).toBeFalsy() }) @@ -91,7 +112,7 @@ describe('run', () => { expect.assertions(1) const e = new Error('Something is wrong') - expect(() => audit.run('low', 'false')).toThrowError(e) + expect(() => audit.run('low', 'false', 'false')).toThrowError(e) }) test('throws an error if status is null', () => { @@ -109,7 +130,7 @@ describe('run', () => { expect.assertions(1) const e = new Error('the subprocess terminated due to a signal.') - expect(() => audit.run('low', 'false')).toThrowError(e) + expect(() => audit.run('low', 'false', 'false')).toThrowError(e) }) test('throws an error if stderr is null', () => { @@ -127,6 +148,6 @@ describe('run', () => { expect.assertions(1) const e = new Error('Something is wrong') - expect(() => audit.run('low', 'false')).toThrowError(e) + expect(() => audit.run('low', 'false', 'false')).toThrowError(e) }) }) diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index e8b6916..4a799f0 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -16,6 +16,7 @@ describe('run', () => { process.env.INPUT_AUDIT_LEVEL = 'low' process.env.INPUT_PRODUCTION_FLAG = 'false' + process.env.INPUT_JSON_FLAG = 'false' process.env.INPUT_GITHUB_CONTEXT = '{ "event_name": "pull_request", "event": { "number": 100} }' process.env.INPUT_GITHUB_TOKEN = '***' diff --git a/__tests__/testdata/audit/error.json b/__tests__/testdata/audit/error.json new file mode 100644 index 0000000..7a17af4 --- /dev/null +++ b/__tests__/testdata/audit/error.json @@ -0,0 +1,73 @@ +{ + "actions": [ + { + "isMajor": false, + "action": "install", + "resolves": [ + { + "id": 532, + "path": "moment", + "dev": false, + "optional": false, + "bundled": false + } + ], + "module": "moment", + "target": "2.29.1" + } + ], + "advisories": { + "532": { + "findings": [ + { + "version": "2.19.2", + "paths": [ + "moment" + ] + } + ], + "id": 532, + "created": "2017-09-21T20:40:00.889Z", + "updated": "2019-06-24T15:10:05.868Z", + "deleted": null, + "title": "Regular Expression Denial of Service", + "found_by": { + "name": "Cristian-Alexandru Staicu" + }, + "reported_by": { + "name": "Cristian-Alexandru Staicu" + }, + "module_name": "moment", + "cves": [], + "vulnerable_versions": "<2.19.3", + "patched_versions": ">=2.19.3", + "overview": "Affected versions of `moment` are vulnerable to a low severity regular expression denial of service when parsing dates as strings.", + "recommendation": "Update to version 2.19.3 or later.", + "references": "- [Issue #4163](https://github.com/moment/moment/issues/4163)\n- [PR #4326](https://github.com/moment/moment/pull/4326)", + "access": "public", + "severity": "low", + "cwe": "CWE-400", + "metadata": { + "module_type": "", + "exploitability": 5, + "affected_components": "" + }, + "url": "https://npmjs.com/advisories/532" + } + }, + "muted": [], + "metadata": { + "vulnerabilities": { + "info": 0, + "low": 7, + "moderate": 1, + "high": 5, + "critical": 0 + }, + "dependencies": 659, + "devDependencies": 0, + "optionalDependencies": 0, + "totalDependencies": 659 + }, + "runId": "88c86b12-b4a4-4827-9d3c-d58ae74384c5" +} diff --git a/action.yml b/action.yml index 6d6f0b5..7942606 100644 --- a/action.yml +++ b/action.yml @@ -10,6 +10,10 @@ inputs: description: 'Run npm audit with --production' default: 'false' required: false + json_flag: + description: 'Run npm audit with --json' + default: 'false' + required: false github_context: description: 'The `github` context' default: ${{ toJson(github) }} @@ -34,6 +38,9 @@ inputs: description: 'Flag to de-dupe against open issues' default: 'false' required: false +outputs: + npm_audit: + description: 'The output of the npm audit report in a text format' runs: using: 'node12' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index cd469cd..28739ae 100644 --- a/dist/index.js +++ b/dist/index.js @@ -556,12 +556,15 @@ class Audit { this.stdout = ''; this.status = null; } - run(auditLevel, productionFlag) { + run(auditLevel, productionFlag, jsonFlag) { try { const auditOptions = ['audit', '--audit-level', auditLevel]; if (productionFlag === 'true') { auditOptions.push('--production'); } + if (jsonFlag === 'true') { + auditOptions.push('--json'); + } const result = child_process_1.spawnSync('npm', auditOptions, { encoding: 'utf-8', maxBuffer: SPAWN_PROCESS_BUFFER_SIZE @@ -1435,6 +1438,10 @@ function run() { if (!['true', 'false'].includes(productionFlag)) { throw new Error('Invalid input: production_flag'); } + const jsonFlag = core.getInput('json_flag', { required: false }); + if (!['true', 'false'].includes(jsonFlag)) { + throw new Error('Invalid input: json_flag'); + } // run `npm audit` const audit = new audit_1.Audit(); audit.run(auditLevel, productionFlag); diff --git a/src/audit.ts b/src/audit.ts index b43d574..1f05f03 100644 --- a/src/audit.ts +++ b/src/audit.ts @@ -7,7 +7,7 @@ export class Audit { stdout = '' private status: number | null = null - public run(auditLevel: string, productionFlag: string): void { + public run(auditLevel: string, productionFlag: string, jsonFlag: string): void { try { const auditOptions: Array = ['audit', '--audit-level', auditLevel] @@ -15,6 +15,10 @@ export class Audit { auditOptions.push('--production') } + if (jsonFlag === 'true') { + auditOptions.push('--json') + } + const result: SpawnSyncReturns = spawnSync('npm', auditOptions, { encoding: 'utf-8', maxBuffer: SPAWN_PROCESS_BUFFER_SIZE diff --git a/src/main.ts b/src/main.ts index ca4a9b1..c416cbd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,10 +30,17 @@ export async function run(): Promise { throw new Error('Invalid input: production_flag') } + const jsonFlag = core.getInput('json_flag', {required: false}) + if (!['true', 'false'].includes(jsonFlag)) { + throw new Error('Invalid input: json_flag') + } + + // run `npm audit` const audit = new Audit() - audit.run(auditLevel, productionFlag) + audit.run(auditLevel, productionFlag, jsonFlag) core.info(audit.stdout) + core.setOutput('npm_audit', audit.stdout); if (audit.foundVulnerability()) { // vulnerabilities are found