diff --git a/.README/all_good.png b/.README/all_good.png
new file mode 100644
index 0000000..d6d1d8e
Binary files /dev/null and b/.README/all_good.png differ
diff --git a/.README/exceptions_table.png b/.README/exceptions_table.png
new file mode 100644
index 0000000..73818c9
Binary files /dev/null and b/.README/exceptions_table.png differ
diff --git a/.README/highlighted_exceptions.png b/.README/highlighted_exceptions.png
new file mode 100644
index 0000000..886a40b
Binary files /dev/null and b/.README/highlighted_exceptions.png differ
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..b9e995b
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: jeemok
+custom: https://www.buymeacoffee.com/jeemok
\ No newline at end of file
diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
index e62dec3..e36440f 100644
--- a/.github/workflows/node.js.yml
+++ b/.github/workflows/node.js.yml
@@ -27,4 +27,5 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run lint
- - run: npm run test
\ No newline at end of file
+ - run: npm run test
+ - run: npm run audit
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0d458e..0707497 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,27 +1,54 @@
+## Next: 2.0.0-rc
+
+### Notable changes
+
+* Simplified the workflow and improved overall performance by running less.
+ * Reduce code size and package size in half (!
+* Added own table display for security report
+* Added table overview of exceptions from `.nsprc` file
+
+### Breaking changes
+
+* Renamed `ignore` field to `active` in `.nsprc` file for better clarity.
+* Renamed `reason` field to `notes` in `.nsprc` file for better clarity.
+* Removed `--display-full` flag that was used to ignore the maximum display limit. Now with the summary table it would be unlikely to display large size of information.
+* Removed `--display-notes` flag that was used for displaying exception notes. Now it is included in the exceptions table.
+
+### Others
+
+* Removed logging of flags used
+* Added npm audit into CI pipeline
+* Added FUNDING.md
+* Updated README.md
+
+## Closed issues
+
+* # []()
+
+---
+
## 1.12.0 (June 18, 2021)
-* [Display warning when exceptionIds are unused](https://github.com/jeemok/better-npm-audit/pull/38)
+* [#38](https://github.com/jeemok/better-npm-audit/pull/38) Display warning when `exceptionIds` are unused
## 1.11.2 (June 11, 2021)
-* [Fixed security CVE-2020-28469: Bump glob-parent from 5.1.1 to 5.1.2](https://github.com/jeemok/better-npm-audit/pull/37)
+* [#37](https://github.com/jeemok/better-npm-audit/pull/37) Fixed security CVE-2020-28469: Bump glob-parent from 5.1.1 to 5.1.2
## 1.11.1 (June 11, 2021)
-* Updated README
+* Updated `README.md`
## 1.11.0 (June 11, 2021)
-* [Added environment variable support `process.env.NPM_CONFIG_AUDIT_LEVEL` to set the audit level](https://github.com/jeemok/better-npm-audit/pull/36)
+* [#36](https://github.com/jeemok/better-npm-audit/pull/36) Added environment variable support `process.env.NPM_CONFIG_AUDIT_LEVEL` to set the audit level
## 1.10.1 (June 7, 2021)
* Updated `--full` flag logging from `[full log mode enabled]` to `[report display limit disabled]`
-* [Added new flag `--display-notes` to display reasons for the exceptions](https://github.com/jeemok/better-npm-audit/issues/32)
+* [#32](https://github.com/jeemok/better-npm-audit/issues/32) Added new flag `--display-notes` to display reasons for the exceptions
## 1.9.3 (June 6, 2021)
-### Features
-
-* [Added CHANGELOG.md](https://github.com/jeemok/better-npm-audit/issues/31)
+* [#31](https://github.com/jeemok/better-npm-audit/issues/31) Added `CHANGELOG.md`
* Updated `README.md`
\ No newline at end of file
diff --git a/README.md b/README.md
index d5df53c..cdf7e62 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,21 @@ or
## Usage
-### `package.json`
+### Run global
+
+```bash
+better-npm-audit audit
+```
+
+### Run with exceptions
+
+
+
+Unhandled or new exceptions will be highlighted:
+
+
+
+### Add into package scripts
```JSON
{
@@ -43,10 +57,10 @@ or
}
```
-### Run global
+Now you can run locally or in your CI pipeline:
```bash
-better-npm-audit audit
+npm run audit
```
@@ -58,8 +72,6 @@ better-npm-audit audit
| `--level` | `-l` | Same as the original `--audit-level` flag |
| `--production` | `-p` | Skip checking `devDependencies` |
| `--ignore` | `-i` | For skipping certain advisories |
-| `--full` | `-f` | Display full audit report. There is a character limit set to the audit report to prevent overwhelming details to the console. |
-| `--display-notes` | `-d` | Display the reasons of matched exceptions from `.nsprc` file. |
@@ -67,7 +79,7 @@ better-npm-audit audit
| Variable | Description |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
-| `process.env.NPM_CONFIG_AUDIT_LEVEL` | Used in setting the audit level.
*Note: this will be disregard if the audit level flag is passed onto the command.* |
+| `NPM_CONFIG_AUDIT_LEVEL` | Used in setting the audit level.
*Note: this will be disregard if the audit level flag is passed onto the command.* |
@@ -78,153 +90,23 @@ You may add a file `.nsprc` to your project root directory to manage the excepti
```json
{
"1337": {
- "ignore": true,
- "reason": "Ignored since we don't use xxx method",
+ "active": true,
+ "notes": "Ignored since we don't use xxx method",
"expiry": 1615462134681
},
"4501": {
- "ignore": false,
- "reason": "Ignored since we don't use xxx method"
+ "active": false,
+ "notes": "Ignored since we don't use xxx method"
},
"980": "Ignored since we don't use xxx method",
- "Note": "Any non number key will be ignored"
+ "Note": "Any non number key will not be excepted"
}
```
-
-
-## Examples
-
-**NPM v6**
-
-Running `node node_modules/better-npm-audit audit` with vulnerabilities, will receive the error:
-
-```bash
-2 vulnerabilities found. Node security advisories: 118,577
-```
-
-Added the ignore flags `node node_modules/better-npm-audit audit -i 118,577` and rerun:
-
-```bash
-Executing script: audit
-
-to be executed: "node node_modules/better-npm-audit audit -i 118,577"
-Exception Vulnerabilities IDs: [ '118', '577' ]
-=== npm audit security report ===
-
-
- Manual Review
- Some vulnerabilities require your attention to resolve
-
- Visit https://go.npm.me/audit-guide for additional guidance
+When using `.nsprc` file, you will see this report display when it starts running:
+
- High Regular Expression Denial of Service
-
- Package minimatch
-
- Patched in >=3.0.2
-
- Dependency of semantic-ui
-
- Path semantic-ui > gulp > vinyl-fs > glob-stream > glob >
- minimatch
-
- More info https://nodesecurity.io/advisories/118
-
-
- High Regular Expression Denial of Service
-
- Package minimatch
-
- Patched in >=3.0.2
-
- Dependency of semantic-ui
-
- Path semantic-ui > gulp > vinyl-fs > glob-watcher > gaze >
- globule > minimatch
-
- More info https://nodesecurity.io/advisories/118
-
-
- Low Prototype Pollution
-
- Package lodash
-
- Patched in >=4.17.5
-
- Dependency of semantic-ui
-
- Path semantic-ui > gulp > vinyl-fs > glob-watcher > gaze >
- globule > lodash
-
- More info https://nodesecurity.io/advisories/577
-
-found 5 vulnerabilities (1 low, 4 high) in 30441 scanned packages
- 5 vulnerabilities require manual review. See the full report for details.
-
-🤝 All good
-```
-
-**NPM v7**
-
-```bash
-# npm audit report
-
-bl <=1.2.2 || 2.0.1 - 2.2.0 || 3.0.0 || 4.0.0 - 4.0.2
-Severity: high
-Remote Memory Exposure - https://npmjs.com/advisories/1555
-fix available via `npm audit fix`
-node_modules/bl
-
-dot-prop <4.2.1 || >=5.0.0 <5.1.1
-Severity: high
-Prototype Pollution - https://npmjs.com/advisories/1213
-fix available via `npm audit fix`
-node_modules/dot-prop
-
-mem <4.0.0
-Denial of Service - https://npmjs.com/advisories/1084
-fix available via `npm audit fix`
-node_modules/loopback-connector-rest/node_modules/mem
- os-locale 2.0.0 - 3.0.0
- Depends on vulnerable versions of mem
- node_modules/loopback-connector-rest/node_modules/os-locale
- strong-globalize 2.8.4 || 2.10.0 - 4.1.1
- Depends on vulnerable versions of os-locale
- node_modules/loopback-connector-rest/node_modules/strong-globalize
-
-swagger-ui <=3.20.8
-Severity: moderate
-Reverse Tabnapping - https://npmjs.com/advisories/975
-Cross-Site Scripting - https://npmjs.com/advisories/976
-Cross-Site Scripting - https://npmjs.com/advisories/985
-fix available via `npm audit fix --force`
-Will install loopback-component-explorer@2.7.0, which is a breaking change
-node_modules/swagger-ui
- loopback-component-explorer >=3.0.0
- Depends on vulnerable versions of swagger-ui
- node_modules/loopback-component-explorer
-
-yargs-parser <=13.1.1 || 14.0.0 - 15.0.0 || 16.0.0 - 18.1.1
-Prototype Pollution - https://npmjs.com/advisories/1500
-fix available via `npm audit fix`
-node_modules/mocha/node_modules/yargs-parser
-node_modules/yargs-unparser/node_modules/yargs-parser
- mocha 1.21.5 - 6.2.2 || 7.0.0-esm1 - 7.1.0
- Depends on vulnerable versions of mkdirp
- Depends on vulnerable versions of yargs-parser
- Depends on vulnerable versions of yargs-unparser
- node_modules/mocha
- yargs 4.0.0-alpha1 - 12.0.5 || 14.1.0 || 15.0.0 - 15.2.0
- Depends on vulnerable versions of yargs-parser
- node_modules/yargs-unparser/node_modules/yargs
- yargs-unparser 1.1.0 - 1.5.0
- Depends on vulnerable versions of yargs
- node_modules/yargs-unparser
-
-18 vulnerabilities (14 low, 2 moderate, 2 high)
-```
diff --git a/index.js b/index.js
index 4598fbc..78cf46a 100755
--- a/index.js
+++ b/index.js
@@ -8,125 +8,75 @@ const get = require('lodash.get');
const program = require('commander');
const { exec } = require('child_process');
const packageJson = require('./package');
-const { isWholeNumber, mapLevelToNumber, getRawVulnerabilities, filterValidException, filterExceptions } = require('./utils/common');
+
+const { getExceptionsIds, processAuditJson } = require('./utils/vulnerability');
+const { printSecurityReport } = require('./utils/print');
+const { isWholeNumber } = require('./utils/common');
const { readFile } = require('./utils/file');
-const consoleUtil = require('./utils/console');
-const EXCEPTION_FILE_PATH = '.nsprc';
-const BASE_COMMAND = 'npm audit';
-const SEPARATOR = ',';
-const DEFAULT_MESSSAGE_LIMIT = 100000; // characters
const MAX_BUFFER_SIZE = 1024 * 1000 * 50; // 50 MB
-const RESPONSE_MESSAGE = {
- SUCCESS: '🤝 All good!',
- LOGS_EXCEEDED: '[MAXIMUM EXCEEDED] Logs exceeded the maximum length limit. Add the flag `-f` to see the full audit logs.',
-};
/**
- * Handle the analyzed result and log display
- * @param {Array} vulnerabilities List of found vulnerabilities
- * @param {String} logData Logs
- * @param {Object} configs Configurations
- * @param {Array} unusedExceptionIds List of unused exceptionsIds.
+ * Process and analyze the NPM audit JSON
+ * @param {String} jsonBuffer NPM audit stringified JSON payload
+ * @param {Number} auditLevel The level of vulnerabilities we care about
+ * @param {Array} exceptionIds List of vulnerability IDs to ignore
+ * @return {undefined}
*/
-function handleFinish(vulnerabilities, logData = '', configs = {}, unusedExceptionIds = []) {
- const {
- displayFullLog = false,
- maxLength = DEFAULT_MESSSAGE_LIMIT,
- } = configs;
-
- let toDisplay = logData.substring(0, maxLength);
-
- // Display an additional information if we not displaying the full logs
- if (toDisplay.length < logData.length) {
- toDisplay += '\n\n';
- toDisplay += '...';
- toDisplay += '\n\n';
- toDisplay += RESPONSE_MESSAGE.LOGS_EXCEEDED;
- toDisplay += '\n\n';
+function handleFinish(jsonBuffer, auditLevel, exceptionIds) {
+ const { unhandledIds, vulnerabilityIds, report } = processAuditJson(jsonBuffer, auditLevel, exceptionIds);
+
+ // If unable to process the audit JSON
+ if (!Array.isArray(unhandledIds) || !Array.isArray(vulnerabilityIds) || !Array.isArray(report)) {
+ console.error('Unable to process the JSON buffer string.');
+ // Exit failed
+ process.exit(1);
+ return;
}
- if (displayFullLog) {
- console.info(logData);
- } else {
- console.info(toDisplay);
+ // Print the security report
+ if (report.length) {
+ printSecurityReport(report);
}
- if (unusedExceptionIds.length > 0) {
+ // Grab any un-filtered vulnerabilities at the appropriate level
+ const unusedExceptionIds = exceptionIds.filter(id => !vulnerabilityIds.includes(id));
+
+ // Display the unused exceptionId's
+ if (unusedExceptionIds.length) {
// eslint-disable-next-line max-len
- const message = `${unusedExceptionIds.length} vulnerabilities where ignored but did not result in a vulnerabilities: ${unusedExceptionIds}. They can be removed from the .nsprc file or -ignore -i flags.`;
- consoleUtil.info(message);
+ const message = `${unusedExceptionIds.length} vulnerabilities where ignored but did not result in a vulnerabilities: ${unusedExceptionIds.join(', ')}. They can be removed from the .nsprc file or -ignore -i flags.`;
+ console.warn(message);
}
- // Display the error if found vulnerabilities
- if (vulnerabilities.length > 0) {
- consoleUtil.error(`${vulnerabilities.length} vulnerabilities found. Node security advisories: ${vulnerabilities}`);
-
+ // Display the found unhandled vulnerabilities
+ if (unhandledIds.length) {
+ console.error(`${unhandledIds.length} vulnerabilities found. Node security advisories: ${unhandledIds.join(', ')}`);
// Exit failed
process.exit(1);
} else {
// Happy happy, joy joy
- consoleUtil.info(RESPONSE_MESSAGE.SUCCESS);
+ console.info('🤝 All good!');
}
}
/**
- * Re-runs the audit in human readable form
- * @param {String} auditCommand The NPM audit command to use (with flags)
- * @param {Boolean} displayFullLog True if full log should be displayed in the case of no vulnerabilities
- * @param {Array} vulnerabilities List of vulnerabilities
- * @param {Array} unusedExceptionIds List of unused exceptionsIds.
- */
-function auditLog(auditCommand, displayFullLog, vulnerabilities, unusedExceptionIds) {
- // Execute `npm audit` command again, but this time we don't use the JSON flag
- const audit = exec(auditCommand);
-
- // Set a temporary string
- // Note: collect all buffers' data before displaying it later to avoid unintentional line breaking in the report display
- let bufferData = '';
-
- audit.stdout.on('data', data => bufferData += data);
-
- // Once the stdout has completed
- audit.stderr.on('close', () => handleFinish(vulnerabilities, bufferData, { displayFullLog }, unusedExceptionIds));
-
- // stderr
- audit.stderr.on('data', console.error);
-}
-
-/**
- * Run the main Audit
+ * Run audit
* @param {String} auditCommand The NPM audit command to use (with flags)
* @param {Number} auditLevel The level of vulnerabilities we care about
- * @param {Boolean} fullLog True if the full log should be displayed in the case of no vulnerabilities
* @param {Array} exceptionIds List of vulnerability IDs to ignore
*/
-function audit(auditCommand, auditLevel, fullLog, exceptionIds) {
- // Execute `npm audit` command to get the security report, taking into account
- // any additional flags that have been passed through. Using the JSON flag
- // to make this easier to process
- // NOTE: Increase max buffer size from default 1MB
+function audit(auditCommand, auditLevel, exceptionIds) {
+ // Increase the default max buffer size (1 MB)
const audit = exec(`${auditCommand} --json`, { maxBuffer: MAX_BUFFER_SIZE });
// Grab the data in chunks and buffer it as we're unable to parse JSON straight from stdout
let jsonBuffer = '';
- audit.stdout.on('data', data => (jsonBuffer += data));
- // Once the stdout has completed process the output
- audit.stderr.on('close', () => {
- // Grab any un-filtered vulnerabilities at the appropriate level
- const rawVulnerabilities = getRawVulnerabilities(jsonBuffer, auditLevel);
-
- // filter out exceptions
- const vulnerabilities = filterExceptions(rawVulnerabilities, exceptionIds);
-
- // Display the unused exceptionId's
- const exceptionsIdsAsArray = Array.isArray(exceptionIds) ? exceptionIds : [exceptionIds];
- const unusedExceptionIds = exceptionsIdsAsArray.filter(id => !rawVulnerabilities.includes(id));
+ audit.stdout.on('data', data => (jsonBuffer += data));
- // Display the original audit logs
- auditLog(auditCommand, fullLog, vulnerabilities, unusedExceptionIds);
- });
+ // Once the stdout has completed, process the output
+ audit.stderr.on('close', () => handleFinish(jsonBuffer, auditLevel, exceptionIds));
// stderr
audit.stderr.on('data', console.error);
@@ -137,49 +87,23 @@ function audit(auditCommand, auditLevel, fullLog, exceptionIds) {
* @param {Object} options User's options or flags
* @param {Function} fn The function to handle the inputs
*/
-function handleUserInput(options, fn) {
- let auditCommand = BASE_COMMAND;
- let auditLevel = 0;
- let exceptionIds = [];
- let displayFullLog = false;
-
- // Check `.nsprc` file for exceptions
- const fileException = readFile(EXCEPTION_FILE_PATH);
- const filteredExceptions = filterValidException(fileException);
- if (fileException) {
- exceptionIds = filteredExceptions.map(details => details.id);
- }
- // Check also if any exception IDs passed via command flags
- if (options && options.ignore) {
- const cmdExceptions = options.ignore.split(SEPARATOR).filter(isWholeNumber).map(Number);
- exceptionIds = exceptionIds.concat(cmdExceptions);
- }
- if (Array.isArray(exceptionIds) && exceptionIds.length) {
- consoleUtil.info(`Exception vulnerabilities ID(s): ${exceptionIds}`);
- }
- if (options && options.displayNotes && filteredExceptions.length) {
- console.info(''); // Add some spacings
- console.info('Exceptions notes:');
- console.info('');
- filteredExceptions.forEach(({ id, reason }) => console.info(`${id}: ${reason || 'n/a'}`));
- console.info('');
- }
+function handleAction(options, fn) {
+ // Generate NPM Audit command
+ const auditCommand = [
+ 'npm audit',
+ // flags
+ get(options, 'production') ? '--production' : '',
+ ].join(' ');
+
// Taking the audit level from the command or environment variable
- const level = get(options, 'level', process.env.NPM_CONFIG_AUDIT_LEVEL);
- if (level) {
- console.info(`[level: ${level}]`);
- auditLevel = mapLevelToNumber(level);
- }
- if (options && options.production) {
- console.info('[production mode enabled]');
- auditCommand += ' --production';
- }
- if (options && options.full) {
- console.info('[report display limit disabled]');
- displayFullLog = true;
- }
+ const auditLevel = get(options, 'level', process.env.NPM_CONFIG_AUDIT_LEVEL) || 'info';
+
+ // Get the exceptions
+ const nsprc = readFile('.nsprc');
+ const cmdExceptions = get(options, 'ignore', '').split(',').filter(isWholeNumber).map(Number);
+ const exceptionIds = getExceptionsIds(nsprc, cmdExceptions);
- fn(auditCommand, auditLevel, displayFullLog, exceptionIds);
+ fn(auditCommand, auditLevel, exceptionIds);
}
program.version(packageJson.version);
@@ -188,18 +112,13 @@ program
.command('audit')
.description('execute npm audit')
.option('-i, --ignore ', 'Vulnerabilities ID(s) to ignore.')
- .option('-f, --full', `Display complete audit report. Limit to ${DEFAULT_MESSSAGE_LIMIT} characters by default.`)
.option('-l, --level ', 'The minimum audit level to validate.')
.option('-p, --production', 'Skip checking devDependencies.')
- .option('-d, --display-notes', 'Display exception notes.')
- .action(userOptions => handleUserInput(userOptions, audit));
+ .action(options => handleAction(options, audit));
program.parse(process.argv);
module.exports = {
handleFinish,
- handleUserInput,
- BASE_COMMAND,
- SUCCESS_MESSAGE: RESPONSE_MESSAGE.SUCCESS,
- LOGS_EXCEEDED_MESSAGE: RESPONSE_MESSAGE.LOGS_EXCEEDED,
+ handleAction,
};
diff --git a/package-lock.json b/package-lock.json
index 45efc10..b46cd40 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -216,7 +216,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"requires": {
"color-convert": "^2.0.1"
}
@@ -246,8 +245,7 @@
"astral-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
- "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
- "dev": true
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="
},
"balanced-match": {
"version": "1.0.0",
@@ -404,7 +402,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -412,8 +409,7 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"commander": {
"version": "2.19.0",
@@ -493,8 +489,7 @@
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"enquirer": {
"version": "2.3.6",
@@ -711,8 +706,7 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
@@ -1018,14 +1012,7 @@
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
- "dev": true
- },
- "lodash.flatten": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
- "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
- "dev": true
+ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
"lodash.get": {
"version": "4.4.2",
@@ -1035,8 +1022,7 @@
"lodash.truncate": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
- "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
- "dev": true
+ "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM="
},
"log-symbols": {
"version": "4.0.0",
@@ -1239,8 +1225,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"randombytes": {
"version": "2.1.0",
@@ -1275,8 +1260,7 @@
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
"resolve-from": {
"version": "4.0.0",
@@ -1367,7 +1351,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
"integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
- "dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"astral-regex": "^2.0.0",
@@ -1377,8 +1360,7 @@
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
}
}
},
@@ -1423,14 +1405,12 @@
}
},
"table": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/table/-/table-6.6.0.tgz",
- "integrity": "sha512-iZMtp5tUvcnAdtHpZTWLPF0M7AgiQsURR2DwmxnJwSy8I3+cY+ozzVvYha3BOLG2TB+L0CqjIz+91htuj6yCXg==",
- "dev": true,
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz",
+ "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==",
"requires": {
"ajv": "^8.0.1",
"lodash.clonedeep": "^4.5.0",
- "lodash.flatten": "^4.4.0",
"lodash.truncate": "^4.4.2",
"slice-ansi": "^4.0.0",
"string-width": "^4.2.0",
@@ -1438,10 +1418,9 @@
},
"dependencies": {
"ajv": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz",
- "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==",
- "dev": true,
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz",
+ "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -1452,26 +1431,22 @@
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
- "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
- "dev": true
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
- "dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -1482,7 +1457,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
- "dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
@@ -1529,7 +1503,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
"requires": {
"punycode": "^2.1.0"
}
diff --git a/package.json b/package.json
index bcee1f7..19d95e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "better-npm-audit",
- "version": "1.12.0",
+ "version": "2.0.0-rc",
"author": "Jee Mok ",
"description": "Made to allow skipping certain vulnerabilities, and any extra handling that are not supported by the default npm audit in the future.",
"license": "MIT",
@@ -31,13 +31,15 @@
},
"dependencies": {
"commander": "^2.19.0",
- "lodash.get": "^4.4.2"
+ "lodash.get": "^4.4.2",
+ "table": "^6.7.1"
},
"engines": {
"node": ">= 8.12"
},
"scripts": {
- "test": "mocha test/index.js",
+ "audit": "node . audit",
+ "test": "mocha test --recursive",
"lint": "eslint ."
},
"devDependencies": {
diff --git a/test/__mocks__/nsprc.json b/test/__mocks__/nsprc.json
new file mode 100644
index 0000000..8153510
--- /dev/null
+++ b/test/__mocks__/nsprc.json
@@ -0,0 +1,33 @@
+{
+ "1213": "Ignored since we don't use xxx method",
+ "1084": {
+ "expiry": 1615462134681,
+ "active": false,
+ "notes": "Inactive package; consider replacing it."
+ },
+ "1179": {
+ "expiry": 1615462134681,
+ "active": true
+ },
+ "1556": {
+ "expiry": 1615462134681,
+ "active": true,
+ "notes": "Issue: https://github.com/jeemok/better-npm-audit/issues/28"
+ },
+ "975": {
+ "expiry": 1615462134681
+ },
+ "976": {
+ "active": false
+ },
+ "985": "",
+ "1651": {
+ "expiry": 1615462134681,
+ "notes": "This will be fixed by the maintainers by June 14"
+ },
+ "1654": {
+ "expiry": 1640966400000
+ },
+ "2100": "Unused",
+ "Note": "personal note"
+}
\ No newline at end of file
diff --git a/test/__mocks__/v7-json-buffer.json b/test/__mocks__/v7-json-buffer.json
index 48d07f7..842513e 100644
--- a/test/__mocks__/v7-json-buffer.json
+++ b/test/__mocks__/v7-json-buffer.json
@@ -328,7 +328,7 @@
"dependency": "yargs-parser",
"title": "Prototype Pollution",
"url": "https://npmjs.com/advisories/1500",
- "severity": "info",
+ "severity": "low",
"range": "<13.1.2 || >=14.0.0 <15.0.1 || >=16.0.0 <18.1.2"
}
],
diff --git a/test/flags.js b/test/flags.js
new file mode 100644
index 0000000..8838cc8
--- /dev/null
+++ b/test/flags.js
@@ -0,0 +1,186 @@
+const sinon = require('sinon');
+const chai = require('chai');
+const { expect } = chai;
+
+const { handleAction } = require('../index');
+
+describe('Flags', () => {
+ describe('default', () => {
+ it('should be able to handle default correctly', () => {
+ const callbackStub = sinon.stub();
+ const options = {};
+
+ expect(callbackStub.called).to.equal(false);
+ handleAction(options, callbackStub);
+ expect(callbackStub.called).to.equal(true);
+
+ const auditCommand = 'npm audit ';
+ const auditLevel = 'info';
+ const exceptionIds = [];
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds)).to.equal(true);
+ });
+ });
+
+ describe('--ignore', () => {
+ it('should be able to pass exception IDs using the command flag smoothly', () => {
+ const callbackStub = sinon.stub();
+ const consoleStub = sinon.stub(console, 'info');
+ const options = { ignore: '1567,919' };
+ const auditCommand = 'npm audit ';
+ const auditLevel = 'info';
+ const exceptionIds = [1567, 919];
+
+ expect(callbackStub.called).to.equal(false);
+ handleAction(options, callbackStub);
+ expect(callbackStub.called).to.equal(true);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds)).to.equal(true);
+ expect(consoleStub.calledWith('Exception IDs: 1567, 919')).to.equal(true);
+
+ // with space
+ options.ignore = '1567, 1902';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, [1567, 1902])).to.equal(true);
+ expect(consoleStub.calledWith('Exception IDs: 1567, 1902')).to.equal(true);
+
+ // invalid exceptions
+ options.ignore = '1134,undefined,888';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, [1134, 888])).to.equal(true);
+ expect(consoleStub.calledWith('Exception IDs: 1134, 888')).to.equal(true);
+
+ // invalid NaN
+ options.ignore = '1134,NaN,3e,828';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, [1134, 828])).to.equal(true);
+ expect(consoleStub.calledWith('Exception IDs: 1134, 828')).to.equal(true);
+
+ // invalid decimals
+ options.ignore = '1199,29.41,628';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, [1199, 628])).to.equal(true);
+ expect(consoleStub.calledWith('Exception IDs: 1199, 628')).to.equal(true);
+
+ consoleStub.restore();
+ });
+
+ it('should info log the vulnerabilities if it is only passed in command line', () => {
+ const callbackStub = sinon.stub();
+ const consoleStub = sinon.stub(console, 'info');
+ const options = { ignore: '1567,919' };
+ const auditCommand = 'npm audit ';
+ const auditLevel = 'info';
+ const exceptionIds = [1567, 919];
+
+ expect(callbackStub.called).to.equal(false);
+ handleAction(options, callbackStub);
+
+ expect(callbackStub.called).to.equal(true);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds)).to.equal(true);
+ expect(consoleStub.called).to.equal(true);
+ expect(consoleStub.calledWith('Exception IDs: 1567, 919')).to.equal(true);
+
+ consoleStub.restore();
+ });
+
+ it('should not info log the vulnerabilities if there are no exceptions given', () => {
+ const callbackStub = sinon.stub();
+ const consoleStub = sinon.stub(console, 'info');
+ const options = {};
+ const auditCommand = 'npm audit ';
+ const auditLevel = 'info';
+ const exceptionIds = [];
+
+ expect(callbackStub.called).to.equal(false);
+ handleAction(options, callbackStub);
+
+ expect(callbackStub.called).to.equal(true);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds)).to.equal(true);
+ expect(consoleStub.called).to.equal(false);
+
+ consoleStub.restore();
+ });
+ });
+
+ describe('--production', () => {
+ it('should be able to set production mode from the command flag correctly', () => {
+ const callbackStub = sinon.stub();
+ const options = { production: true };
+ const auditCommand = 'npm audit --production';
+ const auditLevel = 'info';
+ const exceptionIds = [];
+
+ expect(callbackStub.called).to.equal(false);
+ handleAction(options, callbackStub);
+ expect(callbackStub.called).to.equal(true);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds)).to.equal(true);
+ });
+
+ // TODO check this, or maybe not? use picha eat to check json output
+ it('should not exit on dev dependencies vulnerabilites when using production flag', () => {});
+ });
+
+ describe('--level', () => {
+ it('should be able to pass audit level from the command flag correctly', () => {
+ const callbackStub = sinon.stub();
+ const options = { level: 'info' };
+
+ const auditCommand = 'npm audit ';
+ const exceptionIds = [];
+
+ expect(callbackStub.called).to.equal(false);
+ handleAction(options, callbackStub);
+ expect(callbackStub.called).to.equal(true);
+ expect(callbackStub.calledWith(auditCommand, 'info', exceptionIds)).to.equal(true);
+
+ options.level = 'low';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'low', exceptionIds)).to.equal(true);
+
+ options.level = 'moderate';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'moderate', exceptionIds)).to.equal(true);
+
+ options.level = 'high';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'high', exceptionIds)).to.equal(true);
+
+ options.level = 'critical';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'critical', exceptionIds)).to.equal(true);
+ });
+
+ it('should be able to pass audit level from the environment variables correctly', () => {
+ const callbackStub = sinon.stub();
+ const options = {};
+ const auditCommand = 'npm audit ';
+
+ // info
+ process.env.NPM_CONFIG_AUDIT_LEVEL = 'info';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'info')).to.equal(true);
+
+ // low
+ process.env.NPM_CONFIG_AUDIT_LEVEL = 'low';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'low')).to.equal(true);
+
+ // moderate
+ process.env.NPM_CONFIG_AUDIT_LEVEL = 'moderate';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'moderate')).to.equal(true);
+
+ // high
+ process.env.NPM_CONFIG_AUDIT_LEVEL = 'high';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'high')).to.equal(true);
+
+ // critical
+ process.env.NPM_CONFIG_AUDIT_LEVEL = 'critical';
+ handleAction(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, 'critical')).to.equal(true);
+
+ // Clean up
+ process.env.NPM_CONFIG_AUDIT_LEVEL = undefined;
+ });
+ });
+});
diff --git a/test/index.js b/test/index.js
index 06e3b01..24e30c8 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,558 +1,119 @@
const sinon = require('sinon');
const chai = require('chai');
const { expect } = chai;
-const V6_LOG_REPORT = require('./__mocks__/v6-log-data');
+
const V6_JSON_BUFFER = require('./__mocks__/v6-json-buffer.json');
const V6_JSON_BUFFER_EMPTY = require('./__mocks__/v6-json-buffer-empty.json');
-const V7_JSON_BUFFER = require('./__mocks__/v7-json-buffer.json');
-const V7_JSON_BUFFER_EMPTY = require('./__mocks__/v7-json-buffer-empty.json');
-const consoleUtil = require('../utils/console');
-const { isWholeNumber, mapLevelToNumber, getRawVulnerabilities, isJsonString, filterValidException, filterExceptions } = require('../utils/common');
-const { handleFinish, handleUserInput, BASE_COMMAND, SUCCESS_MESSAGE, LOGS_EXCEEDED_MESSAGE } = require('../index');
-
-const { FG_WHITE, RESET_COLOR } = consoleUtil;
-
-describe('console utils', () => {
- it('should wrap error console message with styling format correctly', () => {
- const stub = sinon.stub(console, 'error');
- const message = 'console message';
-
- expect(stub.called).to.equal(false);
- consoleUtil.error(message);
-
- expect(stub.called).to.equal(true);
- expect(stub.calledWith(`${FG_WHITE}${message}${RESET_COLOR}`)).to.equal(true);
- stub.restore();
- });
-
- it('should wrap error info message with styling format correctly', () => {
- const stub = sinon.stub(console, 'info');
- const message = 'console message';
-
- expect(stub.called).to.equal(false);
- consoleUtil.info(message);
-
- expect(stub.called).to.equal(true);
- expect(stub.calledWith(`${FG_WHITE}${message}${RESET_COLOR}`)).to.equal(true);
- stub.restore();
- });
-});
-
-describe('common utils', () => {
- it('should return true for valid JSON object', () => {
- expect(isJsonString(JSON.stringify({ a: 1, b: { c: 2 } }))).to.equal(true);
- });
-
- it('should return false if it is not a valid JSON object', () => {
- expect(isJsonString('abc')).to.equal(false);
- });
- it('should be able to determine a whole number', () => {
- expect(isWholeNumber()).to.equal(false);
- expect(isWholeNumber(0.14)).to.equal(false);
- expect(isWholeNumber(20.45)).to.equal(false);
- expect(isWholeNumber('')).to.equal(false);
- expect(isWholeNumber('2.50')).to.equal(false);
- expect(isWholeNumber(null)).to.equal(false);
- expect(isWholeNumber('true')).to.equal(false);
+const { handleFinish } = require('../index');
- expect(isWholeNumber(1)).to.equal(true);
- expect(isWholeNumber(2920)).to.equal(true);
- expect(isWholeNumber(934)).to.equal(true);
- expect(isWholeNumber('0920')).to.equal(true);
-
- expect(isWholeNumber(true)).to.equal(true); // Should handle this?
- });
-
- it('should be able to map audit level to correct numbers', () => {
- expect(mapLevelToNumber('info')).to.equal(0);
- expect(mapLevelToNumber('low')).to.equal(1);
- expect(mapLevelToNumber('moderate')).to.equal(2);
- expect(mapLevelToNumber('high')).to.equal(3);
- expect(mapLevelToNumber('critical')).to.equal(4);
- // default and exceptions
- expect(mapLevelToNumber('unknown')).to.equal(0);
- expect(mapLevelToNumber()).to.equal(0);
- expect(mapLevelToNumber(true)).to.equal(0);
- expect(mapLevelToNumber(false)).to.equal(0);
- expect(mapLevelToNumber({})).to.equal(0);
- });
-
- it('should be able to filter valid file exceptions correctly', () => {
- const exceptions = {
- '137': {
- ignore: true,
- reason: 'Ignored since we dont use xxx method',
- },
- '581': {
- reason: 'Ignored since we dont use xxx method',
- },
- '980': 'Ignored since we dont use xxx method',
- '5': '',
- '3': null,
- '2': undefined,
- '1': false,
- 'invalid': 'Ignored since we dont use xxx method',
- };
- const expected = [
- { id: 1, reason: undefined },
- { id: 2, reason: undefined },
- { id: 3, reason: undefined },
- { id: 5, reason: undefined },
- { id: 137, ignore: true, reason: 'Ignored since we dont use xxx method' },
- { id: 980, reason: 'Ignored since we dont use xxx method' },
- ];
-
- expect(filterValidException(exceptions)).to.deep.equal(expected);
- });
-
- it('should be able to filter valid file exceptions with expiry dates correctly', () => {
- const exceptions = {
- '137': {
- ignore: true,
- expiry: 1615462130000,
- },
- '581': {
- ignore: true,
- expiry: 1615462140000,
- },
- '980': {
- ignore: true,
- expiry: 1615462150000,
- },
- };
-
- expect(filterValidException(exceptions)).to.deep.equal([]);
- let clock = sinon.stub(Date, 'now').returns(1615462140000);
-
- expect(filterValidException(exceptions)).to.deep.equal([
- { id: 980, ignore: true, expiry: 1615462150000 },
- ]);
-
- clock.restore();
- clock = sinon.stub(Date, 'now').returns(1615462130000);
-
- expect(filterValidException(exceptions)).to.deep.equal([
- { id: 581, ignore: true, expiry: 1615462140000 },
- { id: 980, ignore: true, expiry: 1615462150000 },
- ]);
-
- clock.restore();
- });
-
- it('should know how to filter raw vulnerabilities based on the exceptionIds', () => {
- const exceptions = [1213, 1500, 1555, 9999];
- const result = filterExceptions([975, 1213, 976, 985, 1500, 1084, 1179, 1523, 1555, 1556, 1589, 9999], exceptions);
-
- expect(result).to.have.length(8).and.to.deep.equal([975, 976, 985, 1084, 1179, 1523, 1556, 1589]);
- });
-});
-
-describe('event handlers', () => {
- it('should be able to pass exceptions from the command correctly', () => {
- const stub = sinon.stub();
- const options = {
- ignore: '1567,919',
- };
-
- expect(stub.called).to.equal(false);
- handleUserInput(options, stub);
- expect(stub.called).to.equal(true);
-
- const auditCommand = BASE_COMMAND;
- const auditLevel = 0;
- const fullLog = false;
- const exceptionIds = [1567, 919];
- expect(stub.calledWith(auditCommand, auditLevel, fullLog, exceptionIds)).to.equal(true);
-
- // with space
- options.ignore = '1567, 1902';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, auditLevel, fullLog, [1567, 1902])).to.equal(true);
-
- // invalid exceptions
- options.ignore = '1134,undefined,888';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, auditLevel, fullLog, [1134, 888])).to.equal(true);
-
- // invalid NaN
- options.ignore = '1134,NaN,3e,828';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, auditLevel, fullLog, [1134, 828])).to.equal(true);
-
- // invalid decimals
- options.ignore = '1199,29.41,628';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, auditLevel, fullLog, [1199, 628])).to.equal(true);
- });
-
- it('should be able to handle audit level from the command correctly', () => {
- const stub = sinon.stub();
- const consoleStub = sinon.stub(console, 'info');
- const options = {
- level: 'info',
- };
-
- expect(stub.called).to.equal(false);
- handleUserInput(options, stub);
- expect(stub.called).to.equal(true);
-
- const auditCommand = BASE_COMMAND;
- const fullLog = false;
+describe('Events handling', () => {
+ it('should exit if unable to process the JSON buffer', () => {
+ const processStub = sinon.stub(process, 'exit');
+ const consoleStub = sinon.stub(console, 'error');
+ const jsonBuffer = '';
+ const auditLevel = 'info';
const exceptionIds = [];
- expect(stub.calledWith(auditCommand, 0, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: info]')).to.equal(true);
- // low
- options.level = 'low';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 1, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: low]')).to.equal(true);
+ expect(processStub.called).to.equal(false);
+ expect(consoleStub.called).to.equal(false);
- // moderate
- options.level = 'moderate';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 2, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: moderate]')).to.equal(true);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds);
- // high
- options.level = 'high';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 3, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: high]')).to.equal(true);
+ expect(processStub.called).to.equal(true);
+ expect(processStub.calledWith(1)).to.equal(true);
- // critical
- options.level = 'critical';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 4, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: critical]')).to.equal(true);
+ expect(consoleStub.called).to.equal(true);
+ expect(consoleStub.calledWith('Unable to process the JSON buffer string.')).to.equal(true);
+ processStub.restore();
consoleStub.restore();
});
- it('should be able to use audit level from the environment variables correctly', () => {
- const stub = sinon.stub();
+ it('should be able to handle success case properly', () => {
const consoleStub = sinon.stub(console, 'info');
- const options = {};
- const auditCommand = BASE_COMMAND;
- const fullLog = false;
+ const jsonBuffer = JSON.stringify(V6_JSON_BUFFER_EMPTY);
+ const auditLevel = 'info';
const exceptionIds = [];
- // info
- process.env.NPM_CONFIG_AUDIT_LEVEL = 'info';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 0, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: info]')).to.equal(true);
-
- // low
- process.env.NPM_CONFIG_AUDIT_LEVEL = 'low';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 1, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: low]')).to.equal(true);
-
- // moderate
- process.env.NPM_CONFIG_AUDIT_LEVEL = 'moderate';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 2, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: moderate]')).to.equal(true);
+ expect(consoleStub.called).to.equal(false);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds);
+ expect(consoleStub.called).to.equal(true);
+ expect(consoleStub.calledWith('🤝 All good!')).to.equal(true);
- // high
- process.env.NPM_CONFIG_AUDIT_LEVEL = 'high';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 3, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: high]')).to.equal(true);
-
- // critical
- process.env.NPM_CONFIG_AUDIT_LEVEL = 'critical';
- handleUserInput(options, stub);
- expect(stub.calledWith(auditCommand, 4, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[level: critical]')).to.equal(true);
-
- // Clean up
consoleStub.restore();
- process.env.NPM_CONFIG_AUDIT_LEVEL = undefined;
});
- it('should be able to handle production flag from the command correctly', () => {
- const stub = sinon.stub();
+ it('should be able to except vulnerabilities properly', () => {
const consoleStub = sinon.stub(console, 'info');
- const options = {
- production: true,
- };
-
- expect(stub.called).to.equal(false);
- handleUserInput(options, stub);
- expect(stub.called).to.equal(true);
+ const jsonBuffer = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'info';
+ const exceptionIds = [975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555, 1556, 1589];
- const auditCommand = `${BASE_COMMAND} --production`;
- const auditLevel = 0;
- const fullLog = false;
- const exceptionIds = [];
- expect(stub.calledWith(auditCommand, auditLevel, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[production mode enabled]')).to.equal(true);
+ expect(consoleStub.called).to.equal(false);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds);
+ expect(consoleStub.called).to.equal(true);
+ expect(consoleStub.calledWith('🤝 All good!')).to.equal(true);
consoleStub.restore();
});
- it('should be able to handle full logs flag from the command correctly', () => {
- const stub = sinon.stub();
- const consoleStub = sinon.stub(console, 'info');
- const options = {
- full: true,
- };
+ it('should be able to handle found vulnerabilities properly', () => {
+ const processStub = sinon.stub(process, 'exit');
+ const consoleErrorStub = sinon.stub(console, 'error');
+ const consoleInfoStub = sinon.stub(console, 'info');
+ const jsonBuffer = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'info';
+ const exceptionIds = [975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555];
- expect(stub.called).to.equal(false);
- handleUserInput(options, stub);
- expect(stub.called).to.equal(true);
+ expect(processStub.called).to.equal(false);
+ expect(consoleErrorStub.called).to.equal(false);
+ expect(consoleInfoStub.called).to.equal(false);
- const auditCommand = BASE_COMMAND;
- const auditLevel = 0;
- const fullLog = true;
- const exceptionIds = [];
- expect(stub.calledWith(auditCommand, auditLevel, fullLog, exceptionIds)).to.equal(true);
- expect(consoleStub.calledWith('[report display limit disabled]')).to.equal(true);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds);
- consoleStub.restore();
- });
-
- it('should be able to handle default command correctly', () => {
- const stub = sinon.stub();
- const options = {};
+ expect(processStub.called).to.equal(true);
+ expect(consoleErrorStub.called).to.equal(true);
+ expect(consoleInfoStub.called).to.equal(true); // Print security report
- expect(stub.called).to.equal(false);
- handleUserInput(options, stub);
- expect(stub.called).to.equal(true);
+ expect(processStub.calledWith(1)).to.equal(true);
+ expect(consoleErrorStub.calledWith('2 vulnerabilities found. Node security advisories: 1556, 1589')).to.equal(true);
- const auditCommand = BASE_COMMAND;
- const auditLevel = 0;
- const fullLog = false;
- const exceptionIds = [];
- expect(stub.calledWith(auditCommand, auditLevel, fullLog, exceptionIds)).to.equal(true);
- });
-
- it('should be able to handle the success result properly', () => {
- const stub = sinon.stub(consoleUtil, 'info');
- const vulnerabilities = [];
-
- expect(stub.called).to.equal(false);
- handleFinish(vulnerabilities);
- expect(stub.called).to.equal(true);
- expect(stub.calledWith(SUCCESS_MESSAGE)).to.equal(true);
- stub.restore();
- });
-
- it('should be able to handle the found vulnerabilities properly', () => {
- const stubProcess = sinon.stub(process, 'exit');
- const stubConsole = sinon.stub(consoleUtil, 'error');
- const vulnerabilities = [1165, 1890];
-
- expect(stubProcess.called).to.equal(false);
- expect(stubConsole.called).to.equal(false);
-
- handleFinish(vulnerabilities);
-
- expect(stubProcess.called).to.equal(true);
- expect(stubConsole.called).to.equal(true);
-
- expect(stubProcess.calledWith(1)).to.equal(true);
- expect(stubConsole.calledWith('2 vulnerabilities found. Node security advisories: 1165,1890')).to.equal(true);
-
- stubProcess.restore();
- stubConsole.restore();
+ processStub.restore();
+ consoleErrorStub.restore();
+ consoleInfoStub.restore();
});
it('should inform the developer when exceptionsIds are unused', () => {
- const stubProcess = sinon.stub(process, 'exit');
- const stubErrorConsole = sinon.stub(consoleUtil, 'error');
- const stubInfoConsole = sinon.stub(consoleUtil, 'info');
- const vulnerabilities = [1165, 1890, 1337];
- const exceptionIds = [2000, 4242];
-
- expect(stubProcess.called).to.equal(false);
- expect(stubErrorConsole.called).to.equal(false);
- expect(stubInfoConsole.called).to.equal(false);
-
- handleFinish(vulnerabilities, '', {}, exceptionIds);
-
- expect(stubProcess.called).to.equal(true);
- expect(stubProcess.calledWith(1)).to.equal(true);
-
- expect(stubErrorConsole.called).to.equal(true);
- expect(stubErrorConsole.calledWith('3 vulnerabilities found. Node security advisories: 1165,1890,1337')).to.equal(true);
-
- expect(stubInfoConsole.called).to.equal(true);
+ const processStub = sinon.stub(process, 'exit');
+ const consoleErrorStub = sinon.stub(console, 'error');
+ const consoleWarnStub = sinon.stub(console, 'warn');
+ const consoleInfoStub = sinon.stub(console, 'info');
+ const jsonBuffer = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'info';
+ const exceptionIds = [975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555, 2001, 2002];
+
+ expect(processStub.called).to.equal(false);
+ expect(consoleErrorStub.called).to.equal(false);
+ expect(consoleWarnStub.called).to.equal(false);
+ expect(consoleInfoStub.called).to.equal(false);
+
+ handleFinish(jsonBuffer, auditLevel, exceptionIds);
+
+ expect(processStub.called).to.equal(true);
+ expect(processStub.calledWith(1)).to.equal(true);
+ expect(consoleErrorStub.called).to.equal(true);
+ expect(consoleErrorStub.calledWith('2 vulnerabilities found. Node security advisories: 1556, 1589')).to.equal(true);
+
+ expect(consoleInfoStub.called).to.equal(true); // Print security report
+ expect(consoleWarnStub.called).to.equal(true);
// eslint-disable-next-line max-len
- const message = `2 vulnerabilities where ignored but did not result in a vulnerabilities: 2000,4242. They can be removed from the .nsprc file or -ignore -i flags.`;
- expect(stubInfoConsole.calledWith(message)).to.equal(true);
-
- stubProcess.restore();
- stubErrorConsole.restore();
- stubInfoConsole.restore();
- });
-
- it('should be able to handle normal log display correctly', () => {
- const stub = sinon.stub(console, 'info');
- const smallLog = '123456789';
- const displayFullLog = true;
- const maxLength = 50;
- const vulnerabilities = [];
-
- expect(stub.called).to.equal(false);
- handleFinish(vulnerabilities, smallLog, { displayFullLog, maxLength });
- expect(stub.called).to.equal(true);
- expect(stub.calledWith(smallLog)).to.equal(true);
- stub.restore();
- });
-
- it('should display overlength log properly', () => {
- const stub = sinon.stub(console, 'info');
- const displayFullLog = true;
- const maxLength = 500;
- const vulnerabilities = [];
-
- expect(stub.called).to.equal(false);
- handleFinish(vulnerabilities, V6_LOG_REPORT, { displayFullLog, maxLength });
- expect(stub.called).to.equal(true);
- // Full log
- expect(stub.calledWith(V6_LOG_REPORT)).to.equal(true);
- stub.restore();
- });
-
- it('should display an additional message on overlength log', () => {
- const stub = sinon.stub(console, 'info');
- const displayFullLog = false;
- const maxLength = 500;
- const vulnerabilities = [];
-
- let expectedDisplay = V6_LOG_REPORT.substring(0, maxLength);
- expectedDisplay += '\n\n';
- expectedDisplay += '...';
- expectedDisplay += '\n\n';
- expectedDisplay += LOGS_EXCEEDED_MESSAGE;
- expectedDisplay += '\n\n';
-
- expect(stub.called).to.equal(false);
- handleFinish(vulnerabilities, V6_LOG_REPORT, { displayFullLog, maxLength });
- expect(stub.called).to.equal(true);
- expect(stub.calledWith(expectedDisplay)).to.equal(true);
- stub.restore();
- });
-
- it('should be able to handle log display within maximum length properly', () => {
- const stub = sinon.stub(console, 'info');
- const data = '123456789';
- const fullLog = false;
- const maxLength = 9;
- const vulnerabilities = [];
-
- expect(stub.called).to.equal(false);
- handleFinish(vulnerabilities, data, fullLog, maxLength);
- expect(stub.called).to.equal(true);
- expect(stub.calledWith('123456789')).to.equal(true);
- stub.restore();
- });
-});
-
-describe('npm v6', () => {
- describe('retrieve vulnerabilities', () => {
- it('should be able to handle correctly for empty vulnerability scan', () => {
- const jsonString = JSON.stringify(V6_JSON_BUFFER_EMPTY);
- const auditLevel = 0; // info
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(0).and.to.deep.equal([]);
- });
-
- it('should be able to get info level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V6_JSON_BUFFER);
- const auditLevel = 0; // info
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(11).and.to.deep.equal([975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555, 1556, 1589]);
- });
-
- it('should be able to get low level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V6_JSON_BUFFER);
- const auditLevel = 1; // low
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(11).and.to.deep.equal([975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555, 1556, 1589]);
- });
-
- it('should be able to get moderate level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V6_JSON_BUFFER);
- const auditLevel = 2; // moderate
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(5).and.to.deep.equal([975, 976, 985, 1213, 1555]);
- });
-
- it('should be able to get high level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V6_JSON_BUFFER);
- const auditLevel = 3; // high
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(2).and.to.deep.equal([1213, 1555]);
- });
-
- it('should be able to get critical level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V6_JSON_BUFFER);
- const auditLevel = 4; // critical
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(1).and.to.deep.equal([1555]);
- });
- });
-});
-
-describe('npm v7', () => {
- describe('retrieve vulnerabilities', () => {
- it('should be able to handle correctly for empty vulnerability scan', () => {
- const jsonString = JSON.stringify(V7_JSON_BUFFER_EMPTY);
- const auditLevel = 0; // info
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(0).and.to.deep.equal([]);
- });
-
- it('should be able to get info level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V7_JSON_BUFFER);
- const auditLevel = 0; // info
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(11).and.to.deep.equal([1555, 1213, 1589, 1523, 1084, 1179, 1556, 975, 976, 985, 1500]);
- });
-
- it('should be able to get low level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V7_JSON_BUFFER);
- const auditLevel = 1; // low
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(10).and.to.deep.equal([1555, 1213, 1589, 1523, 1084, 1179, 1556, 975, 976, 985]);
- });
-
- it('should be able to get moderate level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V7_JSON_BUFFER);
- const auditLevel = 2; // moderate
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(5).and.to.deep.equal([1555, 1213, 975, 976, 985]);
- });
-
- it('should be able to get high level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V7_JSON_BUFFER);
- const auditLevel = 3; // high
- const result = getRawVulnerabilities(jsonString, auditLevel);
-
- expect(result).to.have.length(2).and.to.deep.equal([1555, 1213]);
- });
-
- it('should be able to get critical level vulnerabilities from JSON buffer', () => {
- const jsonString = JSON.stringify(V7_JSON_BUFFER);
- const auditLevel = 4; // critical
- const result = getRawVulnerabilities(jsonString, auditLevel);
+ const message = `2 vulnerabilities where ignored but did not result in a vulnerabilities: 2001, 2002. They can be removed from the .nsprc file or -ignore -i flags.`;
+ expect(consoleWarnStub.calledWith(message)).to.equal(true);
- expect(result).to.have.length(1).and.to.deep.equal([1555]);
- });
+ processStub.restore();
+ consoleErrorStub.restore();
+ consoleWarnStub.restore();
+ consoleInfoStub.restore();
});
});
diff --git a/test/utils/color.js b/test/utils/color.js
new file mode 100644
index 0000000..43f6e7a
--- /dev/null
+++ b/test/utils/color.js
@@ -0,0 +1,49 @@
+const chai = require('chai');
+const { expect } = chai;
+const { color, getSeverityBgColor } = require('../../utils/color');
+
+describe('Color utils', () => {
+ describe('#color', () => {
+ it('should handle correctly without given colors specificed', () => {
+ expect(color('message')).to.equal('message\x1b[0m');
+ });
+
+ it('should be able to color message foreground correctly', () => {
+ expect(color('message', 'black')).to.equal('\033[30mmessage\x1b[0m');
+ expect(color('message', 'red')).to.equal('\033[31mmessage\x1b[0m');
+ expect(color('message', 'green')).to.equal('\033[32mmessage\x1b[0m');
+ expect(color('message', 'yellow')).to.equal('\033[33mmessage\x1b[0m');
+ expect(color('message', 'blue')).to.equal('\033[34mmessage\x1b[0m');
+ expect(color('message', 'magenta')).to.equal('\033[35mmessage\x1b[0m');
+ expect(color('message', 'cyan')).to.equal('\033[36mmessage\x1b[0m');
+ expect(color('message', 'white')).to.equal('\033[37mmessage\x1b[0m');
+ });
+
+ it('should be able to color message background correctly', () => {
+ expect(color('message', null, 'black')).to.equal('\033[40mmessage\x1b[0m');
+ expect(color('message', null, 'red')).to.equal('\033[41mmessage\x1b[0m');
+ expect(color('message', null, 'green')).to.equal('\033[42mmessage\x1b[0m');
+ expect(color('message', null, 'yellow')).to.equal('\033[43mmessage\x1b[0m');
+ expect(color('message', null, 'blue')).to.equal('\033[44mmessage\x1b[0m');
+ expect(color('message', null, 'magenta')).to.equal('\033[45mmessage\x1b[0m');
+ expect(color('message', null, 'cyan')).to.equal('\033[46mmessage\x1b[0m');
+ expect(color('message', null, 'white')).to.equal('\033[47mmessage\x1b[0m');
+ });
+
+ it('should be able to color message foreground and background correctly', () => {
+ expect(color('message', 'black', 'green')).to.equal('\033[30m\033[42mmessage\x1b[0m');
+ expect(color('message', 'white', 'cyan')).to.equal('\033[37m\033[46mmessage\x1b[0m');
+ });
+ });
+
+ describe('#getSeverityBgColor', () => {
+ it('should return correctly', () => {
+ expect(getSeverityBgColor()).to.equal(undefined);
+ expect(getSeverityBgColor('info')).to.equal(undefined);
+ expect(getSeverityBgColor('low')).to.equal(undefined);
+ expect(getSeverityBgColor('moderate')).to.equal(undefined);
+ expect(getSeverityBgColor('high')).to.equal('red');
+ expect(getSeverityBgColor('critical')).to.equal('red');
+ });
+ });
+});
diff --git a/test/utils/common.js b/test/utils/common.js
new file mode 100644
index 0000000..ad01806
--- /dev/null
+++ b/test/utils/common.js
@@ -0,0 +1,35 @@
+const chai = require('chai');
+const { expect } = chai;
+
+const { isWholeNumber, isJsonString } = require('../../utils/common');
+
+describe('Common utils', () => {
+ describe('#isJsonString', () => {
+ it('should return true for valid JSON object', () => {
+ expect(isJsonString(JSON.stringify({ a: 1, b: { c: 2 } }))).to.equal(true);
+ });
+
+ it('should return false if it is not a valid JSON object', () => {
+ expect(isJsonString('abc')).to.equal(false);
+ });
+ });
+
+ describe('#isWholeNumber', () => {
+ it('should be able to determine a whole number', () => {
+ expect(isWholeNumber()).to.equal(false);
+ expect(isWholeNumber(0.14)).to.equal(false);
+ expect(isWholeNumber(20.45)).to.equal(false);
+ expect(isWholeNumber('')).to.equal(false);
+ expect(isWholeNumber('2.50')).to.equal(false);
+ expect(isWholeNumber(null)).to.equal(false);
+ expect(isWholeNumber('true')).to.equal(false);
+
+ expect(isWholeNumber(1)).to.equal(true);
+ expect(isWholeNumber(2920)).to.equal(true);
+ expect(isWholeNumber(934)).to.equal(true);
+ expect(isWholeNumber('0920')).to.equal(true);
+
+ expect(isWholeNumber(true)).to.equal(true); // Should handle this?
+ });
+ });
+});
diff --git a/test/utils/vulnerability.js b/test/utils/vulnerability.js
new file mode 100644
index 0000000..6b598e2
--- /dev/null
+++ b/test/utils/vulnerability.js
@@ -0,0 +1,594 @@
+const sinon = require('sinon');
+const chai = require('chai');
+const { expect } = chai;
+
+const NSPRC = require('../__mocks__/nsprc');
+const V6_JSON_BUFFER = require('../__mocks__/v6-json-buffer.json');
+const V6_JSON_BUFFER_EMPTY = require('../__mocks__/v6-json-buffer-empty.json');
+const V7_JSON_BUFFER = require('../__mocks__/v7-json-buffer.json');
+const V7_JSON_BUFFER_EMPTY = require('../__mocks__/v7-json-buffer-empty.json');
+
+const { mapLevelToNumber, processAuditJson, processExceptions, getExceptionsIds } = require('../../utils/vulnerability');
+
+describe('Vulnerability utils', () => {
+ describe('#mapLevelToNumber', () => {
+ it('should be able to map audit level to correct numbers', () => {
+ expect(mapLevelToNumber('info')).to.equal(0);
+ expect(mapLevelToNumber('low')).to.equal(1);
+ expect(mapLevelToNumber('moderate')).to.equal(2);
+ expect(mapLevelToNumber('high')).to.equal(3);
+ expect(mapLevelToNumber('critical')).to.equal(4);
+ });
+
+ it('should be able to handle exceptions properly', () => {
+ expect(mapLevelToNumber('unknown')).to.equal(0);
+ expect(mapLevelToNumber()).to.equal(0);
+ expect(mapLevelToNumber(true)).to.equal(0);
+ expect(mapLevelToNumber(false)).to.equal(0);
+ expect(mapLevelToNumber({})).to.equal(0);
+ });
+ });
+
+ describe('#getExceptionsIds', () => {
+ it('should display the vulnerabilities from command line if .nsprc file not given', () => {
+ const consoleStub = sinon.stub(console, 'info');
+ const cmdExceptions = [1165, 1890];
+ expect(consoleStub.called).to.equal(false);
+ const result = getExceptionsIds(null, cmdExceptions);
+ expect(result).to.have.length(2).and.deep.equal([1165, 1890]);
+ expect(consoleStub.called).to.equal(true);
+ expect(consoleStub.calledWith('Exception IDs: 1165, 1890')).to.equal(true);
+ consoleStub.restore();
+ });
+
+ it('should combine the exceptions from command line and .nsprc file', () => {
+ const consoleStub = sinon.stub(console, 'info');
+ const cmdExceptions = [1165, 1890];
+ expect(consoleStub.called).to.equal(false);
+ const result = getExceptionsIds(NSPRC, cmdExceptions);
+ expect(result).to.have.length(6).and.deep.equal([1165, 1890, 985, 1213, 1654, 2100]);
+ expect(consoleStub.called).to.equal(true); // Print security report
+ consoleStub.restore();
+ });
+ });
+
+ describe('#processExceptions', () => {
+ it('should be able to process exceptions correctly', () => {
+ const cmdExceptions = [1165, 1890];
+ const result = processExceptions(NSPRC, cmdExceptions);
+
+ expect(result).to.have.property('exceptionIds');
+ expect(result.exceptionIds).to.have.length(6).and.to.deep.equal([1165, 1890, 985, 1213, 1654, 2100]);
+ expect(result).to.have.property('report');
+ /* eslint-disable indent */
+ expect(result.report)
+ .to.have.length(13)
+ .and.to.deep.equal([
+ [
+ '1165',
+ '\u001b[32mactive\u001b[0m',
+ '',
+ '',
+ ],
+ [
+ '1890',
+ '\u001b[32mactive\u001b[0m',
+ '',
+ '',
+ ],
+ [
+ '975',
+ '\u001b[31mexpired\u001b[0m',
+ 'Thu, 11 Mar 2021 11:28:54 GMT',
+ '',
+ ],
+ [
+ '976',
+ '\u001b[33minactive\u001b[0m',
+ '',
+ '',
+ ],
+ [
+ '985',
+ '\u001b[32mactive\u001b[0m',
+ '',
+ '',
+ ],
+ [
+ '1084',
+ '\u001b[31mexpired\u001b[0m',
+ 'Thu, 11 Mar 2021 11:28:54 GMT',
+ 'Inactive package; consider replacing it.',
+ ],
+ [
+ '1179',
+ '\u001b[31mexpired\u001b[0m',
+ 'Thu, 11 Mar 2021 11:28:54 GMT',
+ '',
+ ],
+ [
+ '1213',
+ '\u001b[32mactive\u001b[0m',
+ '',
+ 'Ignored since we don\'t use xxx method',
+ ],
+ [
+ '1556',
+ '\u001b[31mexpired\u001b[0m',
+ 'Thu, 11 Mar 2021 11:28:54 GMT',
+ 'Issue: https://github.com/jeemok/better-npm-audit/issues/28',
+ ],
+ [
+ '1651',
+ '\u001b[31mexpired\u001b[0m',
+ 'Thu, 11 Mar 2021 11:28:54 GMT',
+ 'This will be fixed by the maintainers by June 14',
+ ],
+ [
+ '1654',
+ '\u001b[32mactive\u001b[0m',
+ 'Fri, 31 Dec 2021 16:00:00 GMT',
+ '',
+ ],
+ [
+ '2100',
+ '\u001b[32mactive\u001b[0m',
+ '',
+ 'Unused',
+ ],
+ [
+ 'Note',
+ '\u001b[31minvalid\u001b[0m',
+ '',
+ 'personal note',
+ ],
+ ]);
+ /* eslint-enable indent */
+ });
+
+ it('should be able to filter active exceptions and label correctly', () => {
+ const result = processExceptions(NSPRC);
+ expect(result).to.have.property('exceptionIds').and.to.have.length(4);
+ expect(result).to.have.property('report');
+
+ const activeExceptionIds = result.report.filter(exception => exception[1] === '\u001b[32mactive\u001b[0m').map(each => Number(each[0]));
+ expect(activeExceptionIds).to.have.length(4).to.deep.equal([985, 1213, 1654, 2100]);
+ });
+
+ it('should be able to filter inactive exceptions and label correctly', () => {
+ const result = processExceptions(NSPRC);
+ expect(result).to.have.property('exceptionIds').and.to.have.length(4);
+ expect(result).to.have.property('report');
+
+ const activeExceptionIds = result.report.filter(exception => exception[1] === '\u001b[33minactive\u001b[0m').map(each => Number(each[0]));
+ expect(activeExceptionIds).to.have.length(1).to.deep.equal([976]);
+ });
+
+ it('should be able to filter expired exceptions and label correctly', () => {
+ const dateStub = sinon.stub(Date, 'now').returns(new Date(Date.UTC(2021, 6, 1)).valueOf());
+ const result = processExceptions(NSPRC);
+ expect(result).to.have.property('exceptionIds').and.to.have.length(4);
+ expect(result).to.have.property('report');
+
+ const activeExceptionIds = result.report.filter(exception => exception[1] === '\u001b[31mexpired\u001b[0m').map(each => Number(each[0]));
+ expect(activeExceptionIds).to.have.length(5).to.deep.equal([975, 1084, 1179, 1556, 1651]);
+
+ // Clean up
+ dateStub.restore();
+ });
+
+ it('should be able to filter invalid exceptions and label correctly', () => {
+ const result = processExceptions(NSPRC);
+ expect(result).to.have.property('exceptionIds').and.to.have.length(4);
+ expect(result).to.have.property('report');
+
+ const activeExceptionIds = result.report.filter(exception => exception[1] === '\u001b[31minvalid\u001b[0m').map(each => each[0]);
+ expect(activeExceptionIds).to.have.length(1).to.deep.equal(['Note']);
+ });
+ });
+
+ describe('#processAuditJson', () => {
+ describe('npm v6', () => {
+ it('should be able to handle correctly for empty vulnerability scan', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER_EMPTY);
+ const auditLevel = 'info';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds');
+ expect(result.vulnerabilityIds).to.have.length(0).and.to.deep.equal([]);
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(0).and.to.deep.equal([]);
+ expect(result).to.have.property('report');
+ expect(result.report).to.have.length(0).and.to.deep.equal([]);
+ });
+
+ it('should be able to except some of the reported vulnerabilities', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'info';
+
+ expect(processAuditJson(jsonString, auditLevel))
+ .to.have.property('unhandledIds')
+ .and.to.have.length(11)
+ .and.to.deep.equal([975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555, 1556, 1589]);
+
+ expect(processAuditJson(jsonString, auditLevel, [975, 1179, 1589]))
+ .to.have.property('unhandledIds')
+ .and.to.have.length(8)
+ .and.to.deep.equal([976, 985, 1084, 1213, 1500, 1523, 1555, 1556]);
+ });
+
+ it('should be able to list all the reported vulnerabilities', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'info';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds');
+ expect(result.vulnerabilityIds).to.have.length(11).and.to.deep.equal([975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555, 1556, 1589]);
+ });
+
+ it('should be able to generate a report of all the reported vulnerabilities', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'info';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('report');
+ /* eslint-disable indent */
+ expect(result.report)
+ .to.have.length(11)
+ .and.to.deep.equal([
+ [
+ '\u001b[33m975\u001b[0m',
+ '\u001b[33mswagger-ui\u001b[0m',
+ '\u001b[33mReverse Tabnapping\u001b[0m',
+ '\u001b[33mmoderate\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/975\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m976\u001b[0m',
+ '\u001b[33mswagger-ui\u001b[0m',
+ '\u001b[33mCross-Site Scripting\u001b[0m',
+ '\u001b[33mmoderate\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/976\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m985\u001b[0m',
+ '\u001b[33mswagger-ui\u001b[0m',
+ '\u001b[33mCross-Site Scripting\u001b[0m',
+ '\u001b[33mmoderate\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/985\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1084\u001b[0m',
+ '\u001b[33mmem\u001b[0m',
+ '\u001b[33mDenial of Service\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1084\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1179\u001b[0m',
+ '\u001b[33mminimist\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1179\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1213\u001b[0m',
+ '\u001b[33mdot-prop\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33m\u001b[41mhigh\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1213\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1500\u001b[0m',
+ '\u001b[33myargs-parser\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1500\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1523\u001b[0m',
+ '\u001b[33mlodash\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1523\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1555\u001b[0m',
+ '\u001b[33mbl\u001b[0m',
+ '\u001b[33mRemote Memory Exposure\u001b[0m',
+ '\u001b[33m\u001b[41mcritical\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1555\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1556\u001b[0m',
+ '\u001b[33mnode-fetch\u001b[0m',
+ '\u001b[33mDenial of Service\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1556\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1589\u001b[0m',
+ '\u001b[33mini\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1589\u001b[0m',
+ '\u001b[33mn\u001b[0m',
+ ],
+ ]);
+ /* eslint-enable indent */
+ });
+
+ it('should be able to get info level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'info';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(11).and.to.deep.equal([975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555, 1556, 1589]);
+ });
+
+ it('should be able to get low level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'low';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(11).and.to.deep.equal([975, 976, 985, 1084, 1179, 1213, 1500, 1523, 1555, 1556, 1589]);
+ });
+
+ it('should be able to get moderate level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'moderate';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(5).and.to.deep.equal([975, 976, 985, 1213, 1555]);
+ });
+
+ it('should be able to get high level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'high';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(2).and.to.deep.equal([1213, 1555]);
+ });
+
+ it('should be able to get critical level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V6_JSON_BUFFER);
+ const auditLevel = 'critical';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(1).and.to.deep.equal([1555]);
+ });
+ });
+
+ describe('npm v7', () => {
+ it('should be able to handle correctly for empty vulnerability scan', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER_EMPTY);
+ const auditLevel = 'info';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds');
+ expect(result.vulnerabilityIds).to.have.length(0).and.to.deep.equal([]);
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(0).and.to.deep.equal([]);
+ expect(result).to.have.property('report');
+ expect(result.report).to.have.length(0).and.to.deep.equal([]);
+ });
+
+ it('should be able to except some of the reported vulnerabilities', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER);
+ const auditLevel = 'info';
+
+ expect(processAuditJson(jsonString, auditLevel))
+ .to.have.property('unhandledIds')
+ .and.to.have.length(11)
+ .and.to.deep.equal([1555, 1213, 1589, 1523, 1084, 1179, 1556, 975, 976, 985, 1500]);
+
+ expect(processAuditJson(jsonString, auditLevel, [975, 1179, 1589]))
+ .to.have.property('unhandledIds')
+ .and.to.have.length(8)
+ .and.to.deep.equal([1555, 1213, 1523, 1084, 1556, 976, 985, 1500]);
+ });
+
+ it('should be able to list all the reported vulnerabilities', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER);
+ const auditLevel = 'info';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds');
+ expect(result.vulnerabilityIds).to.have.length(11).and.to.deep.equal([1555, 1213, 1589, 1523, 1084, 1179, 1556, 975, 976, 985, 1500]);
+ });
+
+ it('should be able to generate a report of all the reported vulnerabilities', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER);
+ const auditLevel = 'info';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('report');
+
+ /* eslint-disable indent */
+ expect(result.report)
+ .to.have.length(11)
+ .and.to.deep.equal([
+ [
+ '\u001b[33m1555\u001b[0m',
+ '\u001b[33mbl\u001b[0m',
+ '\u001b[33mRemote Memory Exposure\u001b[0m',
+ '\u001b[33m\u001b[41mcritical\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1555\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1213\u001b[0m',
+ '\u001b[33mdot-prop\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33m\u001b[41mhigh\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1213\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1589\u001b[0m',
+ '\u001b[33mini\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1589\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1523\u001b[0m',
+ '\u001b[33mlodash\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1523\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1084\u001b[0m',
+ '\u001b[33mmem\u001b[0m',
+ '\u001b[33mDenial of Service\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1084\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1179\u001b[0m',
+ '\u001b[33mminimist\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1179\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1556\u001b[0m',
+ '\u001b[33mnode-fetch\u001b[0m',
+ '\u001b[33mDenial of Service\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1556\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m975\u001b[0m',
+ '\u001b[33mswagger-ui\u001b[0m',
+ '\u001b[33mReverse Tabnapping\u001b[0m',
+ '\u001b[33mmoderate\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/975\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m976\u001b[0m',
+ '\u001b[33mswagger-ui\u001b[0m',
+ '\u001b[33mCross-Site Scripting\u001b[0m',
+ '\u001b[33mmoderate\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/976\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m985\u001b[0m',
+ '\u001b[33mswagger-ui\u001b[0m',
+ '\u001b[33mCross-Site Scripting\u001b[0m',
+ '\u001b[33mmoderate\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/985\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ [
+ '\u001b[33m1500\u001b[0m',
+ '\u001b[33myargs-parser\u001b[0m',
+ '\u001b[33mPrototype Pollution\u001b[0m',
+ '\u001b[33mlow\u001b[0m',
+ '\u001b[33mhttps://npmjs.com/advisories/1500\u001b[0m',
+ '\u001b[31mn\u001b[0m',
+ ],
+ ]);
+ /* eslint-enable indent */
+ });
+
+ it('should be able to get info level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER);
+ const auditLevel = 'info';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(11).and.to.deep.equal([1555, 1213, 1589, 1523, 1084, 1179, 1556, 975, 976, 985, 1500]);
+ });
+
+ it('should be able to get low level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER);
+ const auditLevel = 'low';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(11).and.to.deep.equal([1555, 1213, 1589, 1523, 1084, 1179, 1556, 975, 976, 985, 1500]);
+ });
+
+ it('should be able to get moderate level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER);
+ const auditLevel = 'moderate';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(5).and.to.deep.equal([1555, 1213, 975, 976, 985]);
+ });
+
+ it('should be able to get high level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER);
+ const auditLevel = 'high';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(2).and.to.deep.equal([1555, 1213]);
+ });
+
+ it('should be able to get critical level vulnerabilities from JSON buffer', () => {
+ const jsonString = JSON.stringify(V7_JSON_BUFFER);
+ const auditLevel = 'critical';
+ const result = processAuditJson(jsonString, auditLevel);
+
+ expect(result).to.have.property('vulnerabilityIds').and.to.have.length(11);
+ expect(result).to.have.property('report').and.to.have.length(11);
+
+ expect(result).to.have.property('unhandledIds');
+ expect(result.unhandledIds).to.have.length(1).and.to.deep.equal([1555]);
+ });
+ });
+ });
+});
diff --git a/utils/color.js b/utils/color.js
new file mode 100644
index 0000000..a1bf9ea
--- /dev/null
+++ b/utils/color.js
@@ -0,0 +1,80 @@
+const get = require('lodash.get');
+
+const COLORS = {
+ RESET: '\x1b[0m',
+ black: {
+ fg: '\033[30m',
+ bg: '\033[40m',
+ },
+ red: {
+ fg: '\033[31m',
+ bg: '\033[41m',
+ },
+ green: {
+ fg: '\033[32m',
+ bg: '\033[42m',
+ },
+ yellow: {
+ fg: '\033[33m',
+ bg: '\033[43m',
+ },
+ blue: {
+ fg: '\033[34m',
+ bg: '\033[44m',
+ },
+ magenta: {
+ fg: '\033[35m',
+ bg: '\033[45m',
+ },
+ cyan: {
+ fg: '\033[36m',
+ bg: '\033[46m',
+ },
+ white: {
+ fg: '\033[37m',
+ bg: '\033[47m',
+ },
+};
+
+/**
+ * Color a console message's foreground and background
+ * @param {String} message Message
+ * @param {String} fgColor Foreground color
+ * @param {String} bgColor Background color
+ * @return {String} Message
+ */
+function color(message, fgColor, bgColor) {
+ return [
+ get(COLORS, `${fgColor}.fg`, ''),
+ get(COLORS, `${bgColor}.bg`, ''),
+ message,
+ COLORS.RESET, // Reset the color at the end
+ ].join('');
+}
+
+/**
+ * Get background color based on severity
+ * @param {String} severity Vulnerability's severity
+ * @return {(String | undefined)} Background color or undefined
+ */
+function getSeverityBgColor(severity) {
+ switch (severity) {
+ case 'info':
+ return undefined;
+ case 'low':
+ return undefined;
+ case 'moderate':
+ return undefined;
+ case 'high':
+ return 'red';
+ case 'critical':
+ return 'red';
+ default:
+ return undefined;
+ }
+}
+
+module.exports = {
+ color,
+ getSeverityBgColor,
+};
diff --git a/utils/common.js b/utils/common.js
index 05bd374..de28847 100644
--- a/utils/common.js
+++ b/utils/common.js
@@ -1,110 +1,3 @@
-const get = require('lodash.get');
-
-/**
- * Converts an audit level to a numeric value for filtering purposes
- * @param {String} auditLevel The npm audit level
- * @return {Number} Returns the numeric value, higher is more severe
- */
-function mapLevelToNumber(auditLevel) {
- switch (auditLevel) {
- case 'info':
- return 0;
- case 'low':
- return 1;
- case 'moderate':
- return 2;
- case 'high':
- return 3;
- case 'critical':
- return 4;
- default:
- return 0;
- }
-}
-
-/**
- * Analyze the JSON string buffer for vulnerabilities
- * @param {String} jsonBuffer NPM audit's JSON string buffer
- * @param {Integer} auditLevel Audit level in integer
- * @param {Array} exceptionIds List of exception vulnerabilities
- * @return {Array} Returns the list of found vulnerabilities
- */
-function getRawVulnerabilities(jsonBuffer = '', auditLevel = 0) {
- // NPM v6 uses `advisories`
- // NPM v7 uses `vulnerabilities`
- // Refer to the test folder for some sample mockups
- const { advisories, vulnerabilities } = JSON.parse(jsonBuffer);
-
- // NPM v6 handling
- if (advisories) {
- return Object.values(advisories)
- .filter(advisory => mapLevelToNumber(advisory.severity) >= auditLevel) // Filter out if there is requested audit level
- .map(advisory => advisory.id); // Map out the vulnerabilities IDs
- }
-
- // NPM v7 handling
- if (vulnerabilities) {
- return Object.values(vulnerabilities)
- .filter(vulnerability => mapLevelToNumber(vulnerability.severity) >= auditLevel) // Filter out if there is requested audit level
- // Map out the vulnerabilities IDs
- .reduce((acc, vulnerability) => {
- // Its stored inside `via` array, but sometimes it might be a String
- const cleanedArray = get(vulnerability, 'via', []).map(each => get(each, 'source')).filter(Boolean);
- // Compile into a single array
- return acc.concat(cleanedArray);
- }, []);
- }
-
- return [];
-}
-
-/**
- * Takes the rawVulnerabilities and filters out the exceptionIds
- * @param {Array} rawVulnerabilities List of raw vulnerabilities to filter
- * @param {Array} exceptionIds List of exception vulnerabilities
- * @return {Array} Returns the list of found vulnerabilities
- */
-function filterExceptions(rawVulnerabilities, exceptionIds = []) {
- return rawVulnerabilities.filter(id => !exceptionIds.includes(id));
-}
-
-/**
- * Filter the given list in the `.nsprc` file for valid exceptions
- * @param {Object} fileException The exception object
- * @return {Array} Returns the list of found vulnerabilities
- */
-function filterValidException(fileException) {
- if (typeof fileException !== 'object') {
- return [];
- }
- return Object.entries(fileException).reduce((acc, [id, details]) => {
- const numberId = Number(id);
- // has to be valid number
- if (isNaN(numberId)) {
- return acc;
- }
- // if the details is not an config object, we will accept this ID
- if (!details || typeof details !== 'object') {
- return acc.concat(Object.assign({}, { id: numberId, reason: details || undefined }));
- }
- // `ignore` flag has to be true
- if (!details.ignore) {
- return acc;
- }
- // if it given an expiry date, validate the date
- if (details.expiry) {
- // if the expiry time is in the future, accept it
- if (details.expiry > new Date(Date.now()).getTime()) {
- return acc.concat(Object.assign({}, { id: numberId }, details));
- }
- // else it is expired, so don't accept it
- return acc;
- }
- // Accept the ID
- return acc.concat(Object.assign({}, { id: numberId }, details));
- }, []);
-}
-
/**
* @param {Any} value The input number
* @return {Boolean} Returns true if the input is a whole number
@@ -130,10 +23,6 @@ function isJsonString(string) {
}
module.exports = {
- filterValidException,
isWholeNumber,
isJsonString,
- mapLevelToNumber,
- getRawVulnerabilities,
- filterExceptions,
};
diff --git a/utils/console.js b/utils/console.js
deleted file mode 100644
index b8e921c..0000000
--- a/utils/console.js
+++ /dev/null
@@ -1,27 +0,0 @@
-const RESET_COLOR = '\x1b[0m';
-const FG_WHITE = '\x1b[37m';
-
-/**
- * @param {String} string The error message
- * @return {Boolean} Returns `true`
- */
-function error(string) {
- console.error(`${FG_WHITE}${string}${RESET_COLOR}`);
- return true;
-}
-
-/**
- * @param {String} string The info message
- * @return {Boolean} Returns `true`
- */
-function info(string) {
- console.info(`${FG_WHITE}${string}${RESET_COLOR}`);
- return true;
-}
-
-module.exports = {
- error,
- info,
- RESET_COLOR,
- FG_WHITE,
-};
diff --git a/utils/file.js b/utils/file.js
index 7953429..4e7a55b 100644
--- a/utils/file.js
+++ b/utils/file.js
@@ -2,8 +2,9 @@ const fs = require('fs');
const { isJsonString } = require('./common');
/**
- * @param {String} path The file path
- * @return {(Object|Boolean)} Returns the parsed data if found, or else returns `false`
+ * Read file from path
+ * @param {String} path File path
+ * @return {(Object | Boolean)} Returns the parsed data if found, or else returns `false`
*/
function readFile(path) {
try {
diff --git a/utils/print.js b/utils/print.js
new file mode 100644
index 0000000..4625142
--- /dev/null
+++ b/utils/print.js
@@ -0,0 +1,43 @@
+const table = require('table').table;
+
+const SECURITY_REPORT_HEADER = ['ID', 'Module', 'Title', 'Sev.', 'URL', 'Ex.'];
+const EXCEPTION_REPORT_HEADER = ['ID', 'Status', 'Expiry', 'Notes'];
+
+/**
+ * Print the security report in a table format
+ * @param {Array} data Array of arrays
+ * @return {undefined} Returns void
+ */
+function printSecurityReport(data) {
+ const configs = {
+ singleLine: true,
+ header: {
+ alignment: 'center',
+ content: '=== npm audit security report ===\n',
+ },
+ };
+
+ console.info(table([SECURITY_REPORT_HEADER, ...data], configs));
+}
+
+/**
+ * Print the exception report in a table format
+ * @param {Array} data Array of arrays
+ * @return {undefined} Returns void
+ */
+function printExceptionReport(data) {
+ const configs = {
+ singleLine: true,
+ header: {
+ alignment: 'center',
+ content: '=== list of exceptions ===\n',
+ },
+ };
+
+ console.info(table([EXCEPTION_REPORT_HEADER, ...data], configs));
+}
+
+module.exports = {
+ printSecurityReport,
+ printExceptionReport,
+};
diff --git a/utils/vulnerability.js b/utils/vulnerability.js
new file mode 100644
index 0000000..acc5c79
--- /dev/null
+++ b/utils/vulnerability.js
@@ -0,0 +1,192 @@
+const get = require('lodash.get');
+
+const { isJsonString } = require('./common');
+const { color, getSeverityBgColor } = require('./color');
+const { printExceptionReport } = require('./print');
+
+/**
+ * Converts an audit level to a numeric value
+ * @param {String} auditLevel Audit level
+ * @return {Number} Numberic level: the higher the number, the more severe it is
+ */
+function mapLevelToNumber(auditLevel) {
+ switch (auditLevel) {
+ case 'info':
+ return 0;
+ case 'low':
+ return 1;
+ case 'moderate':
+ return 2;
+ case 'high':
+ return 3;
+ case 'critical':
+ return 4;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * Analyze the JSON string buffer
+ * @param {String} jsonBuffer NPM Audit JSON string buffer
+ * @param {String} auditLevel User's target audit level
+ * @param {Array} exceptionIds User's exception IDs
+ * @return {Object} Processed vulnerabilities details
+ */
+function processAuditJson(jsonBuffer = '', auditLevel = 'info', exceptionIds = []) {
+ if (!isJsonString(jsonBuffer)) {
+ return {};
+ }
+ // NPM v6 uses `advisories`
+ // NPM v7 uses `vulnerabilities`
+ // Refer to the `test/__mocks__` folder for some sample mockups
+ const { advisories, vulnerabilities } = JSON.parse(jsonBuffer);
+
+ // NPM v6 handling
+ if (advisories) {
+ return Object.values(advisories).reduce((acc, cur) => {
+ const shouldAudit = mapLevelToNumber(cur.severity) >= mapLevelToNumber(auditLevel);
+ const isExcepted = exceptionIds.includes(cur.id);
+
+ // Record this vulnerability into the report, and highlight it using yellow color if it's new
+ acc.report.push([
+ color(cur.id, isExcepted ? '' : 'yellow'),
+ color(cur.module_name, isExcepted ? '' : 'yellow'),
+ color(cur.title, isExcepted ? '' : 'yellow'),
+ color(cur.severity, isExcepted ? '' : 'yellow', getSeverityBgColor(cur.severity)),
+ color(cur.url, isExcepted ? '' : 'yellow'),
+ isExcepted ? 'y' : color('n', 'yellow'),
+ ]);
+
+ acc.vulnerabilityIds.push(cur.id);
+
+ // Found unhandled vulnerabilites
+ if (shouldAudit && !isExcepted) {
+ acc.unhandledIds.push(cur.id);
+ }
+
+ return acc;
+ },
+ {
+ unhandledIds: [],
+ vulnerabilityIds: [],
+ report: [],
+ });
+ }
+
+ // NPM v7 handling
+ if (vulnerabilities) {
+ return Object.values(vulnerabilities).reduce((acc, cur) => {
+ // Inside `via` array, its either the related module name or the vulnerability source object.
+ get(cur, 'via', []).forEach(vul => {
+ // The vulnerability ID is labeled as `source`
+ const id = get(vul, 'source');
+
+ // Let's skip if ID is a string (module name), and only focus on the root vulnerabilities
+ if (!id || typeof id === 'string') {
+ return;
+ }
+
+ const shouldAudit = mapLevelToNumber(vul.severity) >= mapLevelToNumber(auditLevel);
+ const isExcepted = exceptionIds.includes(id);
+
+ // Record this vulnerability into the report, and highlight it using yellow color if it's new
+ acc.report.push([
+ color(id, isExcepted ? '' : 'yellow'),
+ color(vul.name, isExcepted ? '' : 'yellow'),
+ color(vul.title, isExcepted ? '' : 'yellow'),
+ color(vul.severity, isExcepted ? '' : 'yellow', getSeverityBgColor(vul.severity)),
+ color(vul.url, isExcepted ? '' : 'yellow'),
+ isExcepted ? 'y' : color('n', 'red'),
+ ]);
+
+ acc.vulnerabilityIds.push(id);
+
+ // Found unhandled vulnerabilites
+ if (shouldAudit && !isExcepted) {
+ acc.unhandledIds.push(id);
+ }
+ });
+
+ return acc;
+ },
+ {
+ unhandledIds: [],
+ vulnerabilityIds: [],
+ report: [],
+ });
+ }
+ return {};
+}
+
+/**
+ * Process all exceptions and return a list of exception IDs
+ * @param {Object} nsprc File content from `.nsprc`
+ * @param {Array} cmdExceptions Exceptions passed in via command line
+ * @return {Array} List of found vulnerabilities
+ */
+function getExceptionsIds(nsprc, cmdExceptions = []) {
+ // If file does not exists
+ if (!nsprc || typeof nsprc !== 'object') {
+ // If there are exceptions passed in from command line
+ if (cmdExceptions.length) {
+ // Display simple info
+ console.info(`Exception IDs: ${cmdExceptions.join(', ')}`);
+ return cmdExceptions;
+ }
+
+ return [];
+ }
+
+ // Process the content of the file along with the command line exceptions
+ const { exceptionIds, report } = processExceptions(nsprc, cmdExceptions);
+
+ printExceptionReport(report);
+
+ return exceptionIds;
+}
+
+/**
+ * Filter the given list in the `.nsprc` file for valid exceptions
+ * @param {Object} nsprc The nsprc file content, contains exception info
+ * @param {Array} cmdExceptions Exceptions passed in via command line
+ * @return {Object} Processed vulnerabilities details
+ */
+function processExceptions(nsprc, cmdExceptions = []) {
+ return Object.entries(nsprc).reduce((acc, [id, details]) => {
+ const numberId = Number(id);
+ const isValidId = !isNaN(numberId);
+ const isActive = Boolean(get(details, 'active', true)); // default to true
+ const expiryDate = get(details, 'expiry') ? new Date(details.expiry).toUTCString() : '';
+ const hasExpired = get(details, 'expiry') ? details.expiry < new Date(Date.now()).getTime() : false;
+ const notes = typeof details === 'string' ? details : get(details, 'notes', '');
+
+ let status = color('active', 'green');
+ if (hasExpired) {
+ status = color('expired', 'red');
+ } else if (!isValidId) {
+ status = color('invalid', 'red');
+ } else if (!isActive) {
+ status = color('inactive', 'yellow');
+ }
+
+ acc.report.push([id, status, expiryDate, notes]);
+
+ if (isValidId && isActive && !hasExpired) {
+ acc.exceptionIds.push(numberId);
+ }
+
+ return acc;
+ },
+ {
+ exceptionIds: cmdExceptions,
+ report: cmdExceptions.map(id => [String(id), color('active', 'green'), '', '']),
+ });
+}
+
+module.exports = {
+ mapLevelToNumber,
+ getExceptionsIds,
+ processAuditJson,
+ processExceptions,
+};