@maverick006/scanner-npm-audit 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -6
- package/jest.config.js +0 -5
- package/src/index.ts +0 -135
- package/src/parser.ts +0 -55
- package/tests/fixtures/npm-audit-output.json +0 -87
- package/tests/parser.test.ts +0 -41
- package/tsconfig.json +0 -15
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maverick006/scanner-npm-audit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "tsc",
|
|
7
|
+
"build": "tsc -b --force",
|
|
8
8
|
"clean": "rm -rf dist",
|
|
9
9
|
"test": "jest",
|
|
10
|
-
"typecheck": "tsc --noEmit"
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"prepack": "npm run build"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
|
13
|
-
"@maverick006/types": "
|
|
14
|
-
"@maverick006/security-engine": "
|
|
14
|
+
"@maverick006/types": "^1.0.5",
|
|
15
|
+
"@maverick006/security-engine": "^1.0.5"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"typescript": "^5.0.0",
|
|
@@ -19,5 +20,8 @@
|
|
|
19
20
|
"@types/jest": "^29.5.0",
|
|
20
21
|
"ts-jest": "^29.1.0",
|
|
21
22
|
"@types/node": "^20.0.0"
|
|
22
|
-
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
]
|
|
23
27
|
}
|
package/jest.config.js
DELETED
package/src/index.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { SecurityScanner, ScannerCapability } from '@maverick006/security-engine';
|
|
2
|
-
import { ScanInput, ScannerResult, ScannerState } from '@maverick006/types';
|
|
3
|
-
import { execFile } from 'child_process';
|
|
4
|
-
import { promisify } from 'util';
|
|
5
|
-
import { parseNpmAuditOutput } from './parser';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
|
|
9
|
-
const execFileAsync = promisify(execFile);
|
|
10
|
-
|
|
11
|
-
export class NpmAuditScanner implements SecurityScanner {
|
|
12
|
-
public name = 'npm-audit';
|
|
13
|
-
public version = 'unknown';
|
|
14
|
-
|
|
15
|
-
public capabilities: ScannerCapability = {
|
|
16
|
-
category: 'dependencies',
|
|
17
|
-
detectApplicability: (repoPath: string) => {
|
|
18
|
-
const safePath = path.resolve(repoPath);
|
|
19
|
-
return fs.existsSync(path.join(safePath, 'package.json'));
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
async scan(input: ScanInput): Promise<ScannerResult> {
|
|
24
|
-
const startTime = new Date();
|
|
25
|
-
let rawOutput = '';
|
|
26
|
-
|
|
27
|
-
const safePath = path.resolve(input.repositoryPath);
|
|
28
|
-
const hasPackageJson = fs.existsSync(path.join(safePath, 'package.json'));
|
|
29
|
-
|
|
30
|
-
if (!hasPackageJson) {
|
|
31
|
-
const endTime = new Date();
|
|
32
|
-
return {
|
|
33
|
-
scanner: this.name,
|
|
34
|
-
success: true,
|
|
35
|
-
state: ScannerState.SKIPPED,
|
|
36
|
-
reason: 'Skipped: No package.json detected in repository',
|
|
37
|
-
findings: [],
|
|
38
|
-
rawOutput: 'No package.json found',
|
|
39
|
-
startTime,
|
|
40
|
-
endTime,
|
|
41
|
-
durationMs: endTime.getTime() - startTime.getTime()
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
47
|
-
const args = ['audit', '--json'];
|
|
48
|
-
|
|
49
|
-
const { stdout } = await execFileAsync(npmCmd, args, {
|
|
50
|
-
cwd: safePath,
|
|
51
|
-
timeout: 60000,
|
|
52
|
-
maxBuffer: 1024 * 1024 * 50,
|
|
53
|
-
shell: process.platform === 'win32'
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
rawOutput = stdout;
|
|
57
|
-
const findings = parseNpmAuditOutput(input.scanId, rawOutput);
|
|
58
|
-
const endTime = new Date();
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
scanner: this.name,
|
|
62
|
-
success: true,
|
|
63
|
-
state: ScannerState.SUCCESS,
|
|
64
|
-
findings,
|
|
65
|
-
rawOutput,
|
|
66
|
-
startTime,
|
|
67
|
-
endTime,
|
|
68
|
-
durationMs: endTime.getTime() - startTime.getTime()
|
|
69
|
-
};
|
|
70
|
-
} catch (error: any) {
|
|
71
|
-
const endTime = new Date();
|
|
72
|
-
const durationMs = endTime.getTime() - startTime.getTime();
|
|
73
|
-
|
|
74
|
-
if (error.code === 'ENOENT') {
|
|
75
|
-
return {
|
|
76
|
-
scanner: this.name,
|
|
77
|
-
success: false,
|
|
78
|
-
state: ScannerState.NOT_INSTALLED,
|
|
79
|
-
reason: 'npm executable was not found on PATH',
|
|
80
|
-
findings: [],
|
|
81
|
-
error: error.message,
|
|
82
|
-
startTime,
|
|
83
|
-
endTime,
|
|
84
|
-
durationMs
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (error.code === 'ETIMEDOUT') {
|
|
89
|
-
return {
|
|
90
|
-
scanner: this.name,
|
|
91
|
-
success: false,
|
|
92
|
-
state: ScannerState.TIMEOUT,
|
|
93
|
-
reason: 'npm-audit execution timed out',
|
|
94
|
-
findings: [],
|
|
95
|
-
error: error.message,
|
|
96
|
-
startTime,
|
|
97
|
-
endTime,
|
|
98
|
-
durationMs
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// npm audit exits > 0 if vulnerabilities are found
|
|
103
|
-
if (error.stdout && error.stdout.includes('"auditReportVersion":')) {
|
|
104
|
-
try {
|
|
105
|
-
rawOutput = error.stdout;
|
|
106
|
-
const findings = parseNpmAuditOutput(input.scanId, rawOutput);
|
|
107
|
-
return {
|
|
108
|
-
scanner: this.name,
|
|
109
|
-
success: true,
|
|
110
|
-
state: ScannerState.SUCCESS,
|
|
111
|
-
findings,
|
|
112
|
-
rawOutput,
|
|
113
|
-
startTime,
|
|
114
|
-
endTime,
|
|
115
|
-
durationMs
|
|
116
|
-
};
|
|
117
|
-
} catch {
|
|
118
|
-
// ignore parsing error
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
scanner: this.name,
|
|
124
|
-
success: false,
|
|
125
|
-
state: ScannerState.FAILED,
|
|
126
|
-
findings: [],
|
|
127
|
-
error: error.message || 'npm audit execution failed',
|
|
128
|
-
rawOutput: error.stdout || '',
|
|
129
|
-
startTime,
|
|
130
|
-
endTime,
|
|
131
|
-
durationMs
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
package/src/parser.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { NormalizedFinding, Severity, Confidence } from '@maverick006/types';
|
|
2
|
-
|
|
3
|
-
export function parseNpmAuditOutput(scanId: string, output: string): NormalizedFinding[] {
|
|
4
|
-
try {
|
|
5
|
-
const data = JSON.parse(output);
|
|
6
|
-
const findings: NormalizedFinding[] = [];
|
|
7
|
-
|
|
8
|
-
// npm audit JSON output format: version 2
|
|
9
|
-
if (data.auditReportVersion !== 2 || !data.vulnerabilities) {
|
|
10
|
-
return findings;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
for (const [pkgName, vulnData] of Object.entries<any>(data.vulnerabilities)) {
|
|
14
|
-
|
|
15
|
-
const viaArray = Array.isArray(vulnData.via) ? vulnData.via : [];
|
|
16
|
-
|
|
17
|
-
// Some 'via' entries are just strings (referencing other packages),
|
|
18
|
-
// we look for objects that contain actual vulnerability details.
|
|
19
|
-
for (const via of viaArray) {
|
|
20
|
-
if (typeof via === 'object' && via.title) {
|
|
21
|
-
|
|
22
|
-
let severity = Severity.INFO;
|
|
23
|
-
if (via.severity === 'critical') severity = Severity.CRITICAL;
|
|
24
|
-
else if (via.severity === 'high') severity = Severity.HIGH;
|
|
25
|
-
else if (via.severity === 'moderate') severity = Severity.MEDIUM;
|
|
26
|
-
else if (via.severity === 'low') severity = Severity.LOW;
|
|
27
|
-
|
|
28
|
-
const finding: NormalizedFinding = {
|
|
29
|
-
scanId,
|
|
30
|
-
scanner: 'npm-audit',
|
|
31
|
-
scannerVersion: 'v2', // refers to report version
|
|
32
|
-
ruleId: via.url || `npm-audit-${pkgName}`,
|
|
33
|
-
title: via.title,
|
|
34
|
-
description: `Dependency vulnerability in ${pkgName}. Range: ${via.range}. Fix: ${
|
|
35
|
-
vulnData.fixAvailable ? (typeof vulnData.fixAvailable === 'object' ? vulnData.fixAvailable.name + '@' + vulnData.fixAvailable.version : 'Available') : 'None'
|
|
36
|
-
}`,
|
|
37
|
-
severity,
|
|
38
|
-
confidence: Confidence.HIGH, // SCA scans matching a CVE have high confidence
|
|
39
|
-
category: 'dependency',
|
|
40
|
-
file: 'package.json', // Best guess for location
|
|
41
|
-
line: 1, // Doesn't map well to line numbers
|
|
42
|
-
cwe: Array.isArray(via.cwe) ? via.cwe.join(', ') : via.cwe,
|
|
43
|
-
owasp: 'A06:2021', // Vulnerable and Outdated Components
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
findings.push(finding);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return findings;
|
|
52
|
-
} catch (error) {
|
|
53
|
-
throw new Error('Failed to parse npm audit JSON output');
|
|
54
|
-
}
|
|
55
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"auditReportVersion": 2,
|
|
3
|
-
"vulnerabilities": {
|
|
4
|
-
"express": {
|
|
5
|
-
"name": "express",
|
|
6
|
-
"severity": "high",
|
|
7
|
-
"isDirect": true,
|
|
8
|
-
"via": [
|
|
9
|
-
{
|
|
10
|
-
"source": 1085149,
|
|
11
|
-
"name": "express",
|
|
12
|
-
"dependency": "express",
|
|
13
|
-
"title": "Path Traversal in express",
|
|
14
|
-
"url": "https://github.com/advisories/GHSA-9234-5678",
|
|
15
|
-
"severity": "high",
|
|
16
|
-
"cwe": [
|
|
17
|
-
"CWE-22"
|
|
18
|
-
],
|
|
19
|
-
"cvss": {
|
|
20
|
-
"score": 7.5,
|
|
21
|
-
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
|
|
22
|
-
},
|
|
23
|
-
"range": "<4.19.2"
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"effects": [],
|
|
27
|
-
"range": "<4.19.2",
|
|
28
|
-
"nodes": [
|
|
29
|
-
"node_modules/express"
|
|
30
|
-
],
|
|
31
|
-
"fixAvailable": {
|
|
32
|
-
"name": "express",
|
|
33
|
-
"version": "4.19.2",
|
|
34
|
-
"isSemVerMajor": false
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
"lodash": {
|
|
38
|
-
"name": "lodash",
|
|
39
|
-
"severity": "critical",
|
|
40
|
-
"isDirect": false,
|
|
41
|
-
"via": [
|
|
42
|
-
{
|
|
43
|
-
"source": 1012345,
|
|
44
|
-
"name": "lodash",
|
|
45
|
-
"dependency": "lodash",
|
|
46
|
-
"title": "Prototype Pollution in lodash",
|
|
47
|
-
"url": "https://github.com/advisories/GHSA-1234-abcd",
|
|
48
|
-
"severity": "critical",
|
|
49
|
-
"cwe": [
|
|
50
|
-
"CWE-1321"
|
|
51
|
-
],
|
|
52
|
-
"cvss": {
|
|
53
|
-
"score": 9.8,
|
|
54
|
-
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
|
|
55
|
-
},
|
|
56
|
-
"range": "<4.17.21"
|
|
57
|
-
}
|
|
58
|
-
],
|
|
59
|
-
"effects": [
|
|
60
|
-
"some-other-pkg"
|
|
61
|
-
],
|
|
62
|
-
"range": "<4.17.21",
|
|
63
|
-
"nodes": [
|
|
64
|
-
"node_modules/lodash"
|
|
65
|
-
],
|
|
66
|
-
"fixAvailable": true
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
"metadata": {
|
|
70
|
-
"vulnerabilities": {
|
|
71
|
-
"info": 0,
|
|
72
|
-
"low": 0,
|
|
73
|
-
"moderate": 0,
|
|
74
|
-
"high": 1,
|
|
75
|
-
"critical": 1,
|
|
76
|
-
"total": 2
|
|
77
|
-
},
|
|
78
|
-
"dependencies": {
|
|
79
|
-
"prod": 10,
|
|
80
|
-
"dev": 20,
|
|
81
|
-
"optional": 0,
|
|
82
|
-
"peer": 0,
|
|
83
|
-
"peerOptional": 0,
|
|
84
|
-
"total": 30
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
package/tests/parser.test.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { parseNpmAuditOutput } from '../src/parser';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { Severity, Confidence } from '@maverick006/types';
|
|
5
|
-
|
|
6
|
-
describe('npm audit Parser', () => {
|
|
7
|
-
it('should parse npm audit json output correctly', () => {
|
|
8
|
-
const fixturePath = path.join(__dirname, 'fixtures', 'npm-audit-output.json');
|
|
9
|
-
const output = fs.readFileSync(fixturePath, 'utf8');
|
|
10
|
-
|
|
11
|
-
const findings = parseNpmAuditOutput('test-scan-123', output);
|
|
12
|
-
|
|
13
|
-
expect(findings.length).toBe(2);
|
|
14
|
-
|
|
15
|
-
// Check first finding (express Path Traversal)
|
|
16
|
-
const expressVuln = findings[0];
|
|
17
|
-
expect(expressVuln.scanId).toBe('test-scan-123');
|
|
18
|
-
expect(expressVuln.scanner).toBe('npm-audit');
|
|
19
|
-
expect(expressVuln.title).toBe('Path Traversal in express');
|
|
20
|
-
expect(expressVuln.severity).toBe(Severity.HIGH);
|
|
21
|
-
expect(expressVuln.confidence).toBe(Confidence.HIGH);
|
|
22
|
-
expect(expressVuln.cwe).toContain('CWE-22');
|
|
23
|
-
expect(expressVuln.file).toBe('package.json');
|
|
24
|
-
expect(expressVuln.category).toBe('dependency');
|
|
25
|
-
expect(expressVuln.description).toContain('Fix: express@4.19.2');
|
|
26
|
-
|
|
27
|
-
// Check second finding (lodash Prototype Pollution)
|
|
28
|
-
const lodashVuln = findings[1];
|
|
29
|
-
expect(lodashVuln.title).toBe('Prototype Pollution in lodash');
|
|
30
|
-
expect(lodashVuln.severity).toBe(Severity.CRITICAL);
|
|
31
|
-
expect(lodashVuln.cwe).toContain('CWE-1321');
|
|
32
|
-
expect(lodashVuln.description).toContain('Fix: Available');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should handle malformed output gracefully', () => {
|
|
36
|
-
expect(() => parseNpmAuditOutput('123', '{ invalid json }')).toThrow();
|
|
37
|
-
|
|
38
|
-
const emptyFindings = parseNpmAuditOutput('123', '{"auditReportVersion": 2, "vulnerabilities": {}}');
|
|
39
|
-
expect(emptyFindings.length).toBe(0);
|
|
40
|
-
});
|
|
41
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "./dist",
|
|
5
|
-
"rootDir": "./src",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"declarationMap": true,
|
|
8
|
-
"sourceMap": true
|
|
9
|
-
},
|
|
10
|
-
"references": [
|
|
11
|
-
{ "path": "../../packages/types" },
|
|
12
|
-
{ "path": "../../packages/security-engine" }
|
|
13
|
-
],
|
|
14
|
-
"include": ["src/**/*"]
|
|
15
|
-
}
|