diff --git a/README.md b/README.md
index f1b0b56..7d1988c 100644
--- a/README.md
+++ b/README.md
@@ -86,13 +86,14 @@ npm run audit
## Options
-| Flag | Short | Description |
-| ----------------- | ----- | ----------------------------------------------------------------------------------------------------- |
-| `--exclude` | `-x` | Exceptions or the vulnerabilities ID(s) to exclude; the ID can be the numeric ID, CVE, CWE or GHSA ID |
-| `--module-ignore` | `-m` | Names of modules to exclude |
-| `--level` | `-l` | The minimum audit level to validate; Same as the original `--audit-level` flag |
-| `--production` | `-p` | Skip the `devDependencies` |
-| `--registry` | `-r` | The npm registry url to use |
+| Flag | Short | Description |
+| --------------------| ----- | ----------------------------------------------------------------------------------------------------- |
+| `--exclude` | `-x` | Exceptions or the vulnerabilities ID(s) to exclude; the ID can be the numeric ID, CVE, CWE or GHSA ID |
+| `--module-ignore` | `-m` | Names of modules to exclude |
+| `--level` | `-l` | The minimum audit level to validate; Same as the original `--audit-level` flag |
+| `--production` | `-p` | Skip the `devDependencies` |
+| `--registry` | `-r` | The npm registry url to use |
+| `--include-columns` | `-i` | Columns to include in report |
diff --git a/index.ts b/index.ts
index f3a19a4..5ab2838 100755
--- a/index.ts
+++ b/index.ts
@@ -18,9 +18,16 @@ const program = new Command();
* @param {String} auditCommand The NPM audit command to use (with flags)
* @param {String} auditLevel The level of vulnerabilities we care about
* @param {Array} exceptionIds List of vulnerability IDs to exclude
- * @param {Array} modulesToIgnore List of vulnerable modules to ignore in audit results
+ * @param {Array} modulesToIgnore List of vulnerable modules to ignore in audit results
+ * @param {Array} columnsToInclude List of columns to include in audit results
*/
-export function callback(auditCommand: string, auditLevel: AuditLevel, exceptionIds: string[], modulesToIgnore: string[]): void {
+export function callback(
+ auditCommand: string,
+ auditLevel: AuditLevel,
+ exceptionIds: string[],
+ modulesToIgnore: string[],
+ columnsToInclude: string[],
+): void {
// Increase the default max buffer size (1 MB)
const audit = exec(`${auditCommand} --json`, { maxBuffer: MAX_BUFFER_SIZE });
@@ -33,7 +40,7 @@ export function callback(auditCommand: string, auditLevel: AuditLevel, exception
// Once the stdout has completed, process the output
if (audit.stderr) {
- audit.stderr.on('close', () => handleFinish(jsonBuffer, auditLevel, exceptionIds, modulesToIgnore));
+ audit.stderr.on('close', () => handleFinish(jsonBuffer, auditLevel, exceptionIds, modulesToIgnore, columnsToInclude));
// stderr
audit.stderr.on('data', console.error);
}
@@ -49,6 +56,7 @@ program
.option('-l, --level ', 'The minimum audit level to validate.')
.option('-p, --production', 'Skip checking the devDependencies.')
.option('-r, --registry ', 'The npm registry url to use.')
+ .option('-i, --include-columns ,,..,', 'Columns to include in report.')
.action((options: CommandOptions) => handleInput(options, callback));
program.parse(process.argv);
diff --git a/package-lock.json b/package-lock.json
index 372fb98..05b9174 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1663,7 +1663,6 @@
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
diff --git a/src/handlers/handleFinish.ts b/src/handlers/handleFinish.ts
index 8275109..3f4c6ce 100644
--- a/src/handlers/handleFinish.ts
+++ b/src/handlers/handleFinish.ts
@@ -8,13 +8,21 @@ import { processAuditJson, handleUnusedExceptions } from '../utils/vulnerability
* @param {Number} auditLevel The level of vulnerabilities we care about
* @param {Array} exceptionIds List of vulnerability IDs to exclude
* @param {Array} exceptionModules List of vulnerable modules to ignore in audit results
+ * @param {Array} columnsToInclude List of columns to include in audit results
*/
-export default function handleFinish(jsonBuffer: string, auditLevel: AuditLevel, exceptionIds: string[], exceptionModules: string[]): void {
+export default function handleFinish(
+ jsonBuffer: string,
+ auditLevel: AuditLevel,
+ exceptionIds: string[],
+ exceptionModules: string[],
+ columnsToInclude: string[],
+): void {
const { unhandledIds, report, failed, unusedExceptionIds, unusedExceptionModules } = processAuditJson(
jsonBuffer,
auditLevel,
exceptionIds,
exceptionModules,
+ columnsToInclude,
);
// If unable to process the audit JSON
@@ -27,7 +35,7 @@ export default function handleFinish(jsonBuffer: string, auditLevel: AuditLevel,
// Print the security report
if (report.length) {
- printSecurityReport(report);
+ printSecurityReport(report, columnsToInclude);
}
// Handle unused exceptions
diff --git a/src/handlers/handleInput.ts b/src/handlers/handleInput.ts
index e24032f..9219324 100644
--- a/src/handlers/handleInput.ts
+++ b/src/handlers/handleInput.ts
@@ -22,7 +22,10 @@ function getProductionOnlyOption() {
* @param {Object} options User's command options or flags
* @param {Function} fn The function to handle the inputs
*/
-export default function handleInput(options: CommandOptions, fn: (T1: string, T2: AuditLevel, T3: string[], T4: string[]) => void): void {
+export default function handleInput(
+ options: CommandOptions,
+ fn: (T1: string, T2: AuditLevel, T3: string[], T4: string[], T5: string[]) => void,
+): void {
// Generate NPM Audit command
const auditCommand: string = [
'npm audit',
@@ -45,6 +48,10 @@ export default function handleInput(options: CommandOptions, fn: (T1: string, T2
.filter((each) => each !== '');
const exceptionIds: string[] = getExceptionsIds(nsprc, cmdExceptions);
const cmdModuleIgnore: string[] = get(options, 'moduleIgnore', '').split(',');
+ const cmdIncludeColumns: string[] = get(options, 'includeColumns', '')
+ .split(',')
+ .map((each: string) => each.trim())
+ .filter((each: string) => !!each);
- fn(auditCommand, auditLevel, exceptionIds, cmdModuleIgnore);
+ fn(auditCommand, auditLevel, exceptionIds, cmdModuleIgnore, cmdIncludeColumns);
}
diff --git a/src/types/general.d.ts b/src/types/general.d.ts
index 5f478a3..e4e2552 100644
--- a/src/types/general.d.ts
+++ b/src/types/general.d.ts
@@ -6,6 +6,7 @@ export interface CommandOptions {
readonly production?: boolean;
readonly level?: AuditLevel;
readonly registry?: string;
+ readonly includeColumns?: string;
}
export interface NpmAuditJson {
diff --git a/src/types/table.d.ts b/src/types/table.d.ts
index 0040e0a..f974ea3 100644
--- a/src/types/table.d.ts
+++ b/src/types/table.d.ts
@@ -1,2 +1,2 @@
-export type SecurityReportHeader = 'ID' | 'Module' | 'Title' | 'Paths' | 'Sev.' | 'URL' | 'Ex.';
+export type SecurityReportHeader = 'ID' | 'Module' | 'Title' | 'Paths' | 'Severity' | 'URL' | 'Ex.';
export type ExceptionReportHeader = 'ID' | 'Status' | 'Expiry' | 'Notes';
diff --git a/src/utils/print.ts b/src/utils/print.ts
index 1c68de9..9f433ac 100644
--- a/src/utils/print.ts
+++ b/src/utils/print.ts
@@ -2,7 +2,7 @@ import get from 'lodash.get';
import { table, TableUserConfig } from 'table';
import { SecurityReportHeader, ExceptionReportHeader } from 'src/types';
-const SECURITY_REPORT_HEADER: SecurityReportHeader[] = ['ID', 'Module', 'Title', 'Paths', 'Sev.', 'URL', 'Ex.'];
+const SECURITY_REPORT_HEADER: SecurityReportHeader[] = ['ID', 'Module', 'Title', 'Paths', 'Severity', 'URL', 'Ex.'];
const EXCEPTION_REPORT_HEADER: ExceptionReportHeader[] = ['ID', 'Status', 'Expiry', 'Notes'];
// TODO: Add unit tests
@@ -35,10 +35,11 @@ export function getColumnWidth(tableData: string[][], columnIndex: number, maxWi
/**
* Print the security report in a table format
- * @param {Array} data Array of arrays
- * @return {undefined} Returns void
+ * @param {Array} data Array of arrays
+ * @return {undefined} Returns void
+ * @param {Array} columnsToInclude List of columns to include in audit results
*/
-export function printSecurityReport(data: string[][]): void {
+export function printSecurityReport(data: string[][], columnsToInclude: string[]): void {
const configs: TableUserConfig = {
singleLine: true,
header: {
@@ -58,8 +59,9 @@ export function printSecurityReport(data: string[][]): void {
},
},
};
+ const headers = columnsToInclude.length ? SECURITY_REPORT_HEADER.filter((h) => columnsToInclude.includes(h)) : SECURITY_REPORT_HEADER;
- console.info(table([SECURITY_REPORT_HEADER, ...data], configs));
+ console.info(table([headers, ...data], configs));
}
/**
diff --git a/src/utils/vulnerability.ts b/src/utils/vulnerability.ts
index 4d82be6..1feddf7 100644
--- a/src/utils/vulnerability.ts
+++ b/src/utils/vulnerability.ts
@@ -114,6 +114,7 @@ export function validateV7Vulnerability(
* @param {String} auditLevel User's target audit level
* @param {Array} exceptionIds Exception IDs (ID to be ignored)
* @param {Array} exceptionModules Exception modules (modules to be ignored)
+ * @param {Array} columnsToInclude List of columns to include in audit results
* @return {Object} Processed vulnerabilities details
*/
export function processAuditJson(
@@ -121,6 +122,7 @@ export function processAuditJson(
auditLevel: AuditLevel = 'info',
exceptionIds: string[] = [],
exceptionModules: string[] = [],
+ columnsToInclude: string[] = [],
): ProcessedResult {
if (!isJsonString(jsonBuffer)) {
return {
@@ -156,22 +158,28 @@ export function processAuditJson(
acc.unusedExceptionModules = acc.unusedExceptionModules.filter((module) => module !== cur.module_name);
}
- // Record this vulnerability into the report, and highlight it using yellow color if it's new
- acc.report.push([
- color(cur.id.toString(), isExcepted ? '' : 'yellow'),
- color(cur.module_name, isExcepted ? '' : 'yellow'),
- color(cur.title, isExcepted ? '' : 'yellow'),
- color(
- trimArray(
+ const rowData = [
+ { key: 'ID', value: cur.id.toString() },
+ { key: 'Module', value: cur.module_name },
+ { key: 'Title', value: cur.title },
+ {
+ key: 'Paths',
+ value: trimArray(
cur.findings.reduce((a, c) => [...a, ...c.paths] as [], []),
MAX_PATHS_SIZE,
).join('\n'),
- isExcepted ? '' : 'yellow',
- ),
- color(cur.severity, isExcepted ? '' : 'yellow', getSeverityBgColor(cur.severity)),
- color(cur.url, isExcepted ? '' : 'yellow'),
- isExcepted ? 'y' : color('n', 'yellow'),
- ]);
+ },
+ { key: 'Severity', value: cur.severity },
+ { key: 'URL', value: cur.url },
+ { key: 'Ex.', value: isExcepted ? 'y' : 'n' },
+ ]
+ .filter(({ key }) => (columnsToInclude.length ? columnsToInclude.includes(key) : true))
+ .map(({ key, value }) =>
+ color(value, isExcepted ? '' : 'yellow', key === 'Severity' ? getSeverityBgColor(cur.severity) : undefined),
+ );
+
+ // Record this vulnerability into the report, and highlight it using yellow color if it's new
+ acc.report.push(rowData);
acc.vulnerabilityIds.push(cur.id.toString());
if (!acc.vulnerabilityModules.includes(cur.module_name)) {
@@ -224,16 +232,20 @@ export function processAuditJson(
acc.unusedExceptionModules = acc.unusedExceptionModules.filter((module) => module !== moduleName);
}
+ const rowData = [
+ { key: 'ID', value: String(id) },
+ { key: 'Module', value: vul.name },
+ { key: 'Title', value: vul.title },
+ { key: 'Paths', value: trimArray(get(cur, 'nodes', []).map(shortenNodePath), MAX_PATHS_SIZE).join('\n') },
+ { key: 'Severity', value: vul.severity, bgColor: getSeverityBgColor(vul.severity) },
+ { key: 'URL', value: vul.url },
+ { key: 'Ex.', value: isExcepted ? 'y' : 'n' },
+ ]
+ .filter(({ key }) => (columnsToInclude.length ? columnsToInclude.includes(key) : true))
+ .map(({ key, value, bgColor }) => color(value, isExcepted ? '' : 'yellow', key === 'Severity' ? bgColor : undefined));
+
// Record this vulnerability into the report, and highlight it using yellow color if it's new
- acc.report.push([
- color(String(id), isExcepted ? '' : 'yellow'),
- color(vul.name, isExcepted ? '' : 'yellow'),
- color(vul.title, isExcepted ? '' : 'yellow'),
- color(trimArray(get(cur, 'nodes', []).map(shortenNodePath), MAX_PATHS_SIZE).join('\n'), isExcepted ? '' : 'yellow'),
- color(vul.severity, isExcepted ? '' : 'yellow', getSeverityBgColor(vul.severity)),
- color(vul.url, isExcepted ? '' : 'yellow'),
- isExcepted ? 'y' : color('n', 'yellow'),
- ]);
+ acc.report.push(rowData);
acc.vulnerabilityIds.push(String(id));
if (!acc.vulnerabilityModules.includes(moduleName)) {
diff --git a/test/handlers/flags.test.ts b/test/handlers/flags.test.ts
index 2f12d19..48fe503 100644
--- a/test/handlers/flags.test.ts
+++ b/test/handlers/flags.test.ts
@@ -203,6 +203,7 @@ describe('Flags', () => {
// with space
options.moduleIgnore = 'lodash, moment';
+
handleInput(options, callbackStub);
expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds, modulesToIgnore)).to.equal(true);
@@ -217,4 +218,36 @@ describe('Flags', () => {
expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds, modulesToIgnore)).to.equal(true);
});
});
+
+ describe('--include-columns', () => {
+ it('should be able to pass column names using the command flag smoothly', () => {
+ const callbackStub = sinon.stub();
+ const options = { includeColumns: 'ID,Module' };
+ const auditCommand = 'npm audit';
+ const auditLevel = 'info';
+ const exceptionIds: string[] = [];
+ const modulesToIgnore: string[] = [''];
+ const columnsToInclude = ['ID', 'Module'];
+
+ expect(callbackStub.called).to.equal(false);
+ handleInput(options, callbackStub);
+ expect(callbackStub.called).to.equal(true);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds, modulesToIgnore, columnsToInclude)).to.equal(true);
+
+ // with space
+ options.includeColumns = 'ID, Module';
+ handleInput(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds, modulesToIgnore, columnsToInclude)).to.equal(true);
+
+ // invalid exceptions
+ options.includeColumns = 'ID,undefined,Module';
+ handleInput(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds, modulesToIgnore, columnsToInclude)).to.equal(true);
+
+ // invalid null
+ options.includeColumns = 'ID,null,Module';
+ handleInput(options, callbackStub);
+ expect(callbackStub.calledWith(auditCommand, auditLevel, exceptionIds, modulesToIgnore, columnsToInclude)).to.equal(true);
+ });
+ });
});
diff --git a/test/handlers/handleFinish.test.ts b/test/handlers/handleFinish.test.ts
index 85a56fc..2f306be 100644
--- a/test/handlers/handleFinish.test.ts
+++ b/test/handlers/handleFinish.test.ts
@@ -14,11 +14,12 @@ describe('Events handling', () => {
const auditLevel = 'info';
const exceptionIds: string[] = [];
const exceptionModules: string[] = [];
+ const columnsToInclude: string[] = [];
expect(processStub.called).to.equal(false);
expect(consoleStub.called).to.equal(false);
- handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude);
expect(processStub.called).to.equal(true);
expect(processStub.calledWith(1)).to.equal(true);
@@ -37,9 +38,10 @@ describe('Events handling', () => {
const auditLevel = 'info';
const exceptionIds: string[] = [];
const exceptionModules: string[] = [];
+ const columnsToInclude: string[] = [];
expect(consoleStub.called).to.equal(false);
- handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude);
expect(processStub.called).to.equal(true);
expect(processStub.calledWith(0)).to.equal(true);
@@ -58,9 +60,10 @@ describe('Events handling', () => {
const auditLevel = 'info';
const exceptionIds = ['975', '985', '1179', '1213', '1500', '1523', '1555', '1556', '1589'];
const exceptionModules = ['swagger-ui', 'mem'];
+ const columnsToInclude: string[] = [];
expect(consoleStub.called).to.equal(false);
- handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude);
expect(processStub.called).to.equal(true);
expect(processStub.calledWith(0)).to.equal(true);
@@ -80,12 +83,13 @@ describe('Events handling', () => {
const auditLevel = 'info';
const exceptionIds = ['975', '976', '985', '1084', '1179', '1213', '1500', '1523', '1555'];
const exceptionModules: string[] = [];
+ const columnsToInclude: string[] = [];
expect(processStub.called).to.equal(false);
expect(consoleErrorStub.called).to.equal(false);
expect(consoleInfoStub.called).to.equal(false);
- handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude);
expect(processStub.called).to.equal(true);
expect(consoleErrorStub.called).to.equal(true);
@@ -108,13 +112,14 @@ describe('Events handling', () => {
const auditLevel = 'info';
let exceptionModules = ['fakeModule1', 'fakeModule2'];
let exceptionIds = ['975', '976', '985', '1084', '1179', '1213', '1500', '1523', '1555', '2001'];
+ const columnsToInclude: string[] = [];
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, exceptionModules);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude);
expect(processStub.called).to.equal(true);
expect(processStub.calledWith(1)).to.equal(true);
@@ -136,7 +141,7 @@ describe('Events handling', () => {
// Message for multiple unused exceptions
exceptionIds = ['975', '976', '985', '1084', '1179', '1213', '1500', '1523', '1555', '2001', '2002'];
exceptionModules = ['fakeModule1'];
- handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules);
+ handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude);
message = [
'2 of the excluded vulnerabilities did not match any of the found vulnerabilities: 2001, 2002.',
'They can be removed from the .nsprc file or --exclude -x flags.',
diff --git a/test/utils/print.test.ts b/test/utils/print.test.ts
index 3439dc7..18d5f5e 100644
--- a/test/utils/print.test.ts
+++ b/test/utils/print.test.ts
@@ -6,11 +6,11 @@ import V7_SECURITY_REPORT_TABLE_DATA from '../__mocks__/v7-security-report-table
describe('Print utils', () => {
it('v6 security report table visual', () => {
- printSecurityReport(V6_SECURITY_REPORT_TABLE_DATA);
+ printSecurityReport(V6_SECURITY_REPORT_TABLE_DATA, []);
});
it('v7 security report table visual', () => {
- printSecurityReport(V7_SECURITY_REPORT_TABLE_DATA);
+ printSecurityReport(V7_SECURITY_REPORT_TABLE_DATA, []);
});
it('exception table visual', () => {