Comparing version 0.4.3 to 0.5.0
@@ -1,3 +0,16 @@ | ||
# [Unreleased](https://github.com/G-Rath/audit-app/compare/v0.4.3...HEAD) (YYYY-MM-DD) | ||
# [Unreleased](https://github.com/G-Rath/audit-app/compare/v0.5.0...HEAD) (YYYY-MM-DD) | ||
# [0.5.0](https://github.com/G-Rath/audit-app/compare/v0.4.3...v0.5.0) (2021-02-15) | ||
This version dramatically changes the audit report created by `audit-app` in | ||
order to support NPM 7. | ||
In addition to changing the JSON structure outputted by `--output json`, the | ||
format of ignore paths has also changed meaning any existing ignores will need | ||
to be updated. | ||
### Features | ||
- initial support for NPM 7 ([2e10def0][]) | ||
# [0.4.3](https://github.com/G-Rath/audit-app/compare/v0.4.2...v0.4.3) (2021-01-06) | ||
@@ -62,2 +75,3 @@ | ||
[2e10def0]: https://github.com/G-Rath/audit-app/commit/2e10def0 | ||
[d5c5fd0e]: https://github.com/G-Rath/audit-app/commit/d5c5fd0e | ||
@@ -64,0 +78,0 @@ [86083810]: https://github.com/G-Rath/audit-app/commit/86083810 |
@@ -1,2 +0,2 @@ | ||
import { Advisories, Statistics } from './types'; | ||
import { Finding, Statistics } from './types'; | ||
declare type DependencyStatistics = Statistics['dependencies']; | ||
@@ -6,3 +6,3 @@ export declare const SupportedPackageManagers: readonly ["npm", "pnpm", "yarn"]; | ||
export interface AuditResults { | ||
advisories: Advisories; | ||
findings: Record<string, Finding>; | ||
dependencyStatistics: DependencyStatistics; | ||
@@ -9,0 +9,0 @@ } |
@@ -15,2 +15,8 @@ "use strict"; | ||
}; | ||
const extractDependencyStatisticsFromNpm7 = (metadata) => ({ | ||
dependencies: metadata.dependencies.prod, | ||
devDependencies: metadata.dependencies.dev, | ||
optionalDependencies: metadata.dependencies.optional, | ||
totalDependencies: metadata.dependencies.total | ||
}); | ||
const tryOrCall = (fn, er) => (...args) => { | ||
@@ -24,4 +30,22 @@ try { | ||
}; | ||
const npm7AdvisoryToFinding = (advisory) => ({ | ||
id: advisory.source, | ||
name: advisory.name, | ||
paths: [advisory.dependency], | ||
range: advisory.range, | ||
severity: advisory.severity, | ||
title: advisory.title, | ||
url: advisory.url | ||
}); | ||
const npm6AdvisoryToFinding = (advisory) => ({ | ||
id: advisory.id, | ||
name: advisory.module_name, | ||
paths: advisory.findings.reduce((acc, finding) => acc.concat(finding.paths), []), | ||
range: advisory.vulnerable_versions, | ||
severity: advisory.severity, | ||
title: advisory.title, | ||
url: advisory.url | ||
}); | ||
const collectYarnAuditResults = async (stdout) => { | ||
const results = { advisories: {}, dependencyStatistics: {} }; | ||
const results = { findings: {}, dependencyStatistics: {} }; | ||
return new Promise((resolve, reject) => { | ||
@@ -35,4 +59,3 @@ stdout.on('error', reject); | ||
if (parsedLine.type === 'auditAdvisory') { | ||
results.advisories[parsedLine.data.advisory.id] = | ||
parsedLine.data.advisory; | ||
results.findings[parsedLine.data.advisory.id.toString()] = npm6AdvisoryToFinding(parsedLine.data.advisory); | ||
} | ||
@@ -43,2 +66,7 @@ }, reject)); | ||
}; | ||
const toMapOfFindings = (findings) => { | ||
const theFindings = {}; | ||
findings.forEach(finding => (theFindings[finding.id.toString()] = finding)); | ||
return theFindings; | ||
}; | ||
const collectNpmAuditResults = async (stdout) => { | ||
@@ -60,4 +88,13 @@ let json = ''; | ||
} | ||
if ('auditReportVersion' in auditOutput) { | ||
resolve({ | ||
findings: toMapOfFindings(Object.values(auditOutput.vulnerabilities) | ||
.filter((vul) => vul.via.length === 1 && typeof vul.via[0] === 'object') | ||
.map(vul => npm7AdvisoryToFinding(vul.via[0]))), | ||
dependencyStatistics: extractDependencyStatisticsFromNpm7(auditOutput.metadata) | ||
}); | ||
return; | ||
} | ||
resolve({ | ||
advisories: auditOutput.advisories, | ||
findings: toMapOfFindings(Object.values(auditOutput.advisories).map(npm6AdvisoryToFinding)), | ||
dependencyStatistics: extractDependencyStatistics(auditOutput.metadata) | ||
@@ -64,0 +101,0 @@ }); |
@@ -90,20 +90,15 @@ "use strict"; | ||
const Severities = Object.keys(severityColors); | ||
const buildAdvisoryTable = (advisory) => buildTable([ | ||
const buildFindingsTable = (finding) => buildTable([ | ||
[ | ||
severityColors[advisory.severity](advisory.severity), | ||
chalk_1.default.whiteBright(`${advisory.title} (#${advisory.id})`) | ||
severityColors[finding.severity](finding.severity), | ||
chalk_1.default.whiteBright(`${finding.title} (#${finding.id})`) | ||
], | ||
[ | ||
'Package', | ||
`${advisory.module_name} ${Array.from(new Set(advisory.findings.map(finding => `v${finding.version}`))).join(', ')}` | ||
], | ||
['Patched in', advisory.patched_versions], | ||
['More info', advisory.url] | ||
['Package', finding.name], | ||
['Vulnerable range', finding.range], | ||
['More info', finding.url] | ||
]).join('\n'); | ||
const getHighestSeverity = (severities) => { var _a; return (_a = Object.keys(severityColors).reverse().find(severity => severities[severity] > 0)) !== null && _a !== void 0 ? _a : 'info'; }; | ||
const compareAdvisories = (a, b) => a.module_name.localeCompare(b.module_name) || | ||
const compareFindings = (a, b) => a.name.localeCompare(b.name) || | ||
Severities.indexOf(b.severity) - Severities.indexOf(a.severity); | ||
const buildReportTables = (report) => Object.values(report.advisories) | ||
.sort(compareAdvisories) | ||
.map(buildAdvisoryTable); | ||
const buildReportTables = (report) => Object.values(report.findings).sort(compareFindings).map(buildFindingsTable); | ||
const buildReportSummary = (report) => { | ||
@@ -110,0 +105,0 @@ const { statistics: { dependencies: { totalDependencies = '"some"' }, severities, vulnerable, ignored }, missing: { length: missing } } = report; |
import { AuditResults } from './audit'; | ||
import { Advisories, Statistics } from './types'; | ||
import { Finding, Statistics } from './types'; | ||
export interface AuditReport { | ||
statistics: Statistics; | ||
advisories: Advisories; | ||
findings: Record<string, Finding>; | ||
vulnerable: readonly string[]; | ||
@@ -7,0 +7,0 @@ ignored: readonly string[]; |
@@ -19,9 +19,6 @@ "use strict"; | ||
}; | ||
Object.values(results.advisories).forEach(({ id, findings, severity }) => { | ||
const { vulnerable, ignored } = findings.reduce((sums, { paths }) => { | ||
const { length: count } = paths.filter(path => ignores.includes(`${id}|${path}`)); | ||
sums.vulnerable += paths.length - count; | ||
sums.ignored += count; | ||
return sums; | ||
}, { vulnerable: 0, ignored: 0 }); | ||
Object.values(results.findings).forEach(({ id, paths, severity }) => { | ||
const { length: count } = paths.filter(path => ignores.includes(`${id}|${path}`)); | ||
const vulnerable = paths.length - count; | ||
const ignored = count; | ||
statistics.severities[severity] += ignored + vulnerable; | ||
@@ -38,4 +35,4 @@ statistics.severities.total += ignored + vulnerable; | ||
const [vulnerable, // | ||
ignored, missing] = Object.entries(results.advisories) | ||
.reduce((paths, [, advisory]) => paths.concat(advisory.findings.reduce((acc, finding) => acc.concat(finding.paths.map(path => `${advisory.id}|${path}`)), [])), []) | ||
ignored, missing] = Object.values(results.findings) | ||
.reduce((allPaths, { id, paths }) => allPaths.concat(paths.map(path => `${id}|${path}`)), []) | ||
.reduce((sorts, path) => { | ||
@@ -52,3 +49,3 @@ const ignoreIndex = sorts[2].indexOf(path); | ||
return { | ||
advisories: results.advisories, | ||
findings: results.findings, | ||
statistics: generateStatistics(ignores, results), | ||
@@ -55,0 +52,0 @@ vulnerable, |
export declare type Severity = 'info' | 'low' | 'moderate' | 'high' | 'critical'; | ||
export declare type SeverityCounts = Record<Severity, number>; | ||
export declare type SeverityCountsWithTotal = Record<Severity | 'total', number>; | ||
export declare type Advisories = Record<string, Advisory>; | ||
export declare type Advisories = Record<string, Npm6Advisory>; | ||
interface DependencyStatistics { | ||
@@ -11,2 +11,10 @@ dependencies?: number; | ||
} | ||
interface DependencyCounts { | ||
prod: number; | ||
dev: number; | ||
optional: number; | ||
peer: number; | ||
peerOptional: number; | ||
total: number; | ||
} | ||
export interface Statistics { | ||
@@ -18,3 +26,7 @@ dependencies: DependencyStatistics; | ||
} | ||
export interface AuditOutput { | ||
export declare type AuditOutput = NpmAuditOutput | PnpmAuditOutput | YarnAuditOutput; | ||
export declare type NpmAuditOutput = Npm6AuditOutput | Npm7AuditOutput; | ||
export declare type PnpmAuditOutput = Npm6AuditOutput; | ||
export declare type YarnAuditOutput = Npm6AuditOutput; | ||
export interface Npm6AuditOutput { | ||
actions: Action[]; | ||
@@ -26,2 +38,39 @@ advisories: Advisories; | ||
} | ||
export interface Finding { | ||
id: number; | ||
name: string; | ||
paths: string[]; | ||
range: string; | ||
severity: Severity; | ||
title: string; | ||
url: string; | ||
} | ||
export interface Npm7Vulnerability { | ||
name: string; | ||
via: Array<Npm7Advisory | string>; | ||
effects: string[]; | ||
range: string; | ||
nodes: string[]; | ||
fixAvailable: Fix | boolean; | ||
severity: Severity; | ||
} | ||
export interface Npm7AuditOutput { | ||
auditReportVersion: 2; | ||
vulnerabilities: Record<string, Npm7Vulnerability>; | ||
metadata: Npm7AuditMetadata; | ||
} | ||
export interface Npm7Advisory { | ||
source: number; | ||
name: string; | ||
dependency: string; | ||
title: string; | ||
url: string; | ||
severity: Severity; | ||
range: string; | ||
} | ||
interface Fix { | ||
name: string; | ||
version: string; | ||
isSemVerMajor: boolean; | ||
} | ||
interface Action { | ||
@@ -42,3 +91,3 @@ action: 'update' | 'install' | 'review'; | ||
} | ||
export interface Advisory { | ||
export interface Npm6Advisory { | ||
findings: AdvisoryFinding[]; | ||
@@ -82,2 +131,6 @@ id: number; | ||
} | ||
export interface Npm7AuditMetadata { | ||
vulnerabilities: SeverityCountsWithTotal; | ||
dependencies: DependencyCounts; | ||
} | ||
export {}; |
{ | ||
"name": "audit-app", | ||
"version": "0.4.3", | ||
"version": "0.5.0", | ||
"description": "A cli tool for auditing apps & packages using their respective package managers.", | ||
@@ -56,9 +56,9 @@ "keywords": [ | ||
"@types/eslint": "^7.2.6", | ||
"@types/jest": "^26.0.19", | ||
"@types/node": "^14.14.20", | ||
"@types/jest": "^26.0.20", | ||
"@types/node": "^14.14.25", | ||
"@types/readline-transform": "^1.0.0", | ||
"@types/yargs": "^15.0.12", | ||
"@typescript-eslint/eslint-plugin": "^4.12.0", | ||
"@typescript-eslint/parser": "^4.12.0", | ||
"eslint": "^7.17.0", | ||
"@types/yargs": "^16.0.0", | ||
"@typescript-eslint/eslint-plugin": "^4.15.0", | ||
"@typescript-eslint/parser": "^4.15.0", | ||
"eslint": "^7.19.0", | ||
"eslint-config-ackama": "^2.0.1", | ||
@@ -68,3 +68,3 @@ "eslint-plugin-eslint-comments": "^3.2.0", | ||
"eslint-plugin-jest": "^24.1.3", | ||
"eslint-plugin-jest-formatting": "^2.0.0", | ||
"eslint-plugin-jest-formatting": "^2.0.1", | ||
"eslint-plugin-node": "^11.1.0", | ||
@@ -76,8 +76,8 @@ "eslint-plugin-prettier": "^3.3.1", | ||
"prettier-config-ackama": "^0.1.2", | ||
"ts-jest": "^26.4.4", | ||
"ts-jest": "^26.5.1", | ||
"ts-node": "^9.1.1", | ||
"ttypescript": "^1.5.12", | ||
"typescript": "^4.1.3", | ||
"typescript": "^4.1.5", | ||
"unionfs": "^4.4.0" | ||
} | ||
} |
@@ -8,2 +8,15 @@ # audit-app | ||
# NPM 7 Support | ||
There is initial support for `npm@7`, but it has meant the audit report output | ||
has been changed significantly due to the difference in information provided in | ||
the new version. | ||
In particular, it's now no longer possible to calculate the full dependency path | ||
to a vulnerability without making additional calls to `npm`. As such, currently | ||
the paths used for ignoring vulnerabilities with `npm@7` are made up solely of | ||
the advisory number followed by the name of the package the advisory is for. | ||
This may be improved in the future. | ||
# Getting Started | ||
@@ -157,3 +170,3 @@ | ||
audit-app --output paths | pbcopy # on OSX | ||
audit-app --output paths | clip # on Windows (including WSL) | ||
audit-app --output paths | clip # on Windows (including WSL) | ||
``` | ||
@@ -160,0 +173,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
42907
628
351