@maverick006/scanner-npm-audit 1.0.2

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.
@@ -0,0 +1,8 @@
1
+ import { SecurityScanner } from '@maverick006/security-engine';
2
+ import { ScanInput, ScannerResult } from '@maverick006/types';
3
+ export declare class NpmAuditScanner implements SecurityScanner {
4
+ name: string;
5
+ version: string;
6
+ scan(input: ScanInput): Promise<ScannerResult>;
7
+ }
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAQ9D,qBAAa,eAAgB,YAAW,eAAe;IAC9C,IAAI,SAAe;IACnB,OAAO,SAAa;IAErB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC;CAuDrD"}
package/dist/index.js ADDED
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.NpmAuditScanner = void 0;
37
+ const child_process_1 = require("child_process");
38
+ const util_1 = require("util");
39
+ const parser_1 = require("./parser");
40
+ const path = __importStar(require("path"));
41
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
42
+ class NpmAuditScanner {
43
+ name = 'npm-audit';
44
+ version = 'unknown';
45
+ async scan(input) {
46
+ const startTime = new Date();
47
+ let rawOutput = '';
48
+ try {
49
+ const safePath = path.resolve(input.repositoryPath);
50
+ // npm audit --json outputs audit data. We run it in the repository path.
51
+ // Note: this assumes package.json exists. If it doesn't, npm audit will fail, which we can catch.
52
+ const { stdout } = await execAsync(`npm audit --json --prefix "${safePath}"`, {
53
+ timeout: 300000,
54
+ maxBuffer: 1024 * 1024 * 50
55
+ });
56
+ rawOutput = stdout;
57
+ const findings = (0, parser_1.parseNpmAuditOutput)(input.scanId, rawOutput);
58
+ return {
59
+ scanner: this.name,
60
+ success: true,
61
+ findings,
62
+ rawOutput,
63
+ startTime,
64
+ endTime: new Date()
65
+ };
66
+ }
67
+ catch (error) {
68
+ // npm audit returns exit code > 0 if vulnerabilities are found
69
+ if (error.stdout && error.stdout.includes('"auditReportVersion":')) {
70
+ try {
71
+ rawOutput = error.stdout;
72
+ const findings = (0, parser_1.parseNpmAuditOutput)(input.scanId, rawOutput);
73
+ return {
74
+ scanner: this.name,
75
+ success: true,
76
+ findings,
77
+ rawOutput,
78
+ startTime,
79
+ endTime: new Date()
80
+ };
81
+ }
82
+ catch (parseError) {
83
+ // ignore parsing error
84
+ }
85
+ }
86
+ return {
87
+ scanner: this.name,
88
+ success: false,
89
+ findings: [],
90
+ error: error.message || 'npm audit execution failed',
91
+ rawOutput: error.stdout || '',
92
+ startTime,
93
+ endTime: new Date()
94
+ };
95
+ }
96
+ }
97
+ }
98
+ exports.NpmAuditScanner = NpmAuditScanner;
99
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,iDAAqC;AACrC,+BAAiC;AACjC,qCAA+C;AAC/C,2CAA6B;AAE7B,MAAM,SAAS,GAAG,IAAA,gBAAS,EAAC,oBAAI,CAAC,CAAC;AAElC,MAAa,eAAe;IACnB,IAAI,GAAG,WAAW,CAAC;IACnB,OAAO,GAAG,SAAS,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,KAAgB;QACzB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,SAAS,GAAG,EAAE,CAAC;QAEnB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YAEpD,yEAAyE;YACzE,kGAAkG;YAClG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,8BAA8B,QAAQ,GAAG,EAAE;gBAC5E,OAAO,EAAE,MAAM;gBACf,SAAS,EAAE,IAAI,GAAG,IAAI,GAAG,EAAE;aAC5B,CAAC,CAAC;YAEH,SAAS,GAAG,MAAM,CAAC;YACnB,MAAM,QAAQ,GAAG,IAAA,4BAAmB,EAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;YAE9D,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,IAAI;gBAClB,OAAO,EAAE,IAAI;gBACb,QAAQ;gBACR,SAAS;gBACT,SAAS;gBACT,OAAO,EAAE,IAAI,IAAI,EAAE;aACpB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,+DAA+D;YAC/D,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC;gBACnE,IAAI,CAAC;oBACH,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;oBACzB,MAAM,QAAQ,GAAG,IAAA,4BAAmB,EAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;oBAC9D,OAAO;wBACL,OAAO,EAAE,IAAI,CAAC,IAAI;wBAClB,OAAO,EAAE,IAAI;wBACb,QAAQ;wBACR,SAAS;wBACT,SAAS;wBACT,OAAO,EAAE,IAAI,IAAI,EAAE;qBACpB,CAAC;gBACJ,CAAC;gBAAC,OAAO,UAAU,EAAE,CAAC;oBACpB,uBAAuB;gBACzB,CAAC;YACH,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,IAAI;gBAClB,OAAO,EAAE,KAAK;gBACd,QAAQ,EAAE,EAAE;gBACZ,KAAK,EAAE,KAAK,CAAC,OAAO,IAAI,4BAA4B;gBACpD,SAAS,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;gBAC7B,SAAS;gBACT,OAAO,EAAE,IAAI,IAAI,EAAE;aACpB,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA3DD,0CA2DC"}
@@ -0,0 +1,3 @@
1
+ import { NormalizedFinding } from '@maverick006/types';
2
+ export declare function parseNpmAuditOutput(scanId: string, output: string): NormalizedFinding[];
3
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAwB,MAAM,oBAAoB,CAAC;AAE7E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAoDvF"}
package/dist/parser.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseNpmAuditOutput = parseNpmAuditOutput;
4
+ const types_1 = require("@maverick006/types");
5
+ function parseNpmAuditOutput(scanId, output) {
6
+ try {
7
+ const data = JSON.parse(output);
8
+ const findings = [];
9
+ // npm audit JSON output format: version 2
10
+ if (data.auditReportVersion !== 2 || !data.vulnerabilities) {
11
+ return findings;
12
+ }
13
+ for (const [pkgName, vulnData] of Object.entries(data.vulnerabilities)) {
14
+ const viaArray = Array.isArray(vulnData.via) ? vulnData.via : [];
15
+ // Some 'via' entries are just strings (referencing other packages),
16
+ // we look for objects that contain actual vulnerability details.
17
+ for (const via of viaArray) {
18
+ if (typeof via === 'object' && via.title) {
19
+ let severity = types_1.Severity.INFO;
20
+ if (via.severity === 'critical')
21
+ severity = types_1.Severity.CRITICAL;
22
+ else if (via.severity === 'high')
23
+ severity = types_1.Severity.HIGH;
24
+ else if (via.severity === 'moderate')
25
+ severity = types_1.Severity.MEDIUM;
26
+ else if (via.severity === 'low')
27
+ severity = types_1.Severity.LOW;
28
+ const finding = {
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: ${vulnData.fixAvailable ? (typeof vulnData.fixAvailable === 'object' ? vulnData.fixAvailable.name + '@' + vulnData.fixAvailable.version : 'Available') : 'None'}`,
35
+ severity,
36
+ confidence: types_1.Confidence.HIGH, // SCA scans matching a CVE have high confidence
37
+ category: 'dependency',
38
+ file: 'package.json', // Best guess for location
39
+ line: 1, // Doesn't map well to line numbers
40
+ cwe: Array.isArray(via.cwe) ? via.cwe.join(', ') : via.cwe,
41
+ owasp: 'A06:2021', // Vulnerable and Outdated Components
42
+ };
43
+ findings.push(finding);
44
+ }
45
+ }
46
+ }
47
+ return findings;
48
+ }
49
+ catch (error) {
50
+ throw new Error('Failed to parse npm audit JSON output');
51
+ }
52
+ }
53
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":";;AAEA,kDAoDC;AAtDD,8CAA6E;AAE7E,SAAgB,mBAAmB,CAAC,MAAc,EAAE,MAAc;IAChE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,QAAQ,GAAwB,EAAE,CAAC;QAEzC,0CAA0C;QAC1C,IAAI,IAAI,CAAC,kBAAkB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3D,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAM,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YAE5E,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAEjE,qEAAqE;YACrE,iEAAiE;YACjE,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;gBAC3B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;oBAEzC,IAAI,QAAQ,GAAG,gBAAQ,CAAC,IAAI,CAAC;oBAC7B,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU;wBAAE,QAAQ,GAAG,gBAAQ,CAAC,QAAQ,CAAC;yBACzD,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM;wBAAE,QAAQ,GAAG,gBAAQ,CAAC,IAAI,CAAC;yBACtD,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU;wBAAE,QAAQ,GAAG,gBAAQ,CAAC,MAAM,CAAC;yBAC5D,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK;wBAAE,QAAQ,GAAG,gBAAQ,CAAC,GAAG,CAAC;oBAEzD,MAAM,OAAO,GAAsB;wBACjC,MAAM;wBACN,OAAO,EAAE,WAAW;wBACpB,cAAc,EAAE,IAAI,EAAE,2BAA2B;wBACjD,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,aAAa,OAAO,EAAE;wBACzC,KAAK,EAAE,GAAG,CAAC,KAAK;wBAChB,WAAW,EAAE,+BAA+B,OAAO,YAAY,GAAG,CAAC,KAAK,UACtE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,OAAO,QAAQ,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,GAAG,GAAG,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MACzJ,EAAE;wBACF,QAAQ;wBACR,UAAU,EAAE,kBAAU,CAAC,IAAI,EAAE,gDAAgD;wBAC7E,QAAQ,EAAE,YAAY;wBACtB,IAAI,EAAE,cAAc,EAAE,0BAA0B;wBAChD,IAAI,EAAE,CAAC,EAAE,mCAAmC;wBAC5C,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG;wBAC1D,KAAK,EAAE,UAAU,EAAE,qCAAqC;qBACzD,CAAC;oBAEF,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC"}
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ };
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@maverick006/scanner-npm-audit",
3
+ "version": "1.0.2",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "clean": "rm -rf dist",
9
+ "test": "jest",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@maverick006/types": "*",
14
+ "@maverick006/security-engine": "*"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.0.0",
18
+ "jest": "^29.5.0",
19
+ "@types/jest": "^29.5.0",
20
+ "ts-jest": "^29.1.0",
21
+ "@types/node": "^20.0.0"
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { SecurityScanner } from '@maverick006/security-engine';
2
+ import { ScanInput, ScannerResult } from '@maverick006/types';
3
+ import { exec } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import { parseNpmAuditOutput } from './parser';
6
+ import * as path from 'path';
7
+
8
+ const execAsync = promisify(exec);
9
+
10
+ export class NpmAuditScanner implements SecurityScanner {
11
+ public name = 'npm-audit';
12
+ public version = 'unknown';
13
+
14
+ async scan(input: ScanInput): Promise<ScannerResult> {
15
+ const startTime = new Date();
16
+ let rawOutput = '';
17
+
18
+ try {
19
+ const safePath = path.resolve(input.repositoryPath);
20
+
21
+ // npm audit --json outputs audit data. We run it in the repository path.
22
+ // Note: this assumes package.json exists. If it doesn't, npm audit will fail, which we can catch.
23
+ const { stdout } = await execAsync(`npm audit --json --prefix "${safePath}"`, {
24
+ timeout: 300000,
25
+ maxBuffer: 1024 * 1024 * 50
26
+ });
27
+
28
+ rawOutput = stdout;
29
+ const findings = parseNpmAuditOutput(input.scanId, rawOutput);
30
+
31
+ return {
32
+ scanner: this.name,
33
+ success: true,
34
+ findings,
35
+ rawOutput,
36
+ startTime,
37
+ endTime: new Date()
38
+ };
39
+ } catch (error: any) {
40
+ // npm audit returns exit code > 0 if vulnerabilities are found
41
+ if (error.stdout && error.stdout.includes('"auditReportVersion":')) {
42
+ try {
43
+ rawOutput = error.stdout;
44
+ const findings = parseNpmAuditOutput(input.scanId, rawOutput);
45
+ return {
46
+ scanner: this.name,
47
+ success: true,
48
+ findings,
49
+ rawOutput,
50
+ startTime,
51
+ endTime: new Date()
52
+ };
53
+ } catch (parseError) {
54
+ // ignore parsing error
55
+ }
56
+ }
57
+
58
+ return {
59
+ scanner: this.name,
60
+ success: false,
61
+ findings: [],
62
+ error: error.message || 'npm audit execution failed',
63
+ rawOutput: error.stdout || '',
64
+ startTime,
65
+ endTime: new Date()
66
+ };
67
+ }
68
+ }
69
+ }
package/src/parser.ts ADDED
@@ -0,0 +1,55 @@
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
+ }
@@ -0,0 +1,87 @@
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
+ }
@@ -0,0 +1,41 @@
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 ADDED
@@ -0,0 +1,15 @@
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
+ }