cve-scanner 0.0.0 → 0.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.
- package/README.md +96 -45
- package/assets/gradle-init/list-dependencies.init.gradle +20 -0
- package/data/dataset.hash +1 -0
- package/data/dataset.json +288056 -0
- package/dist/ai/AiAssistant.d.ts +11 -0
- package/dist/ai/AiAssistant.js +34 -0
- package/dist/ai/AiAssistant.js.map +1 -0
- package/dist/ai/ProjectManifestReader.d.ts +8 -0
- package/dist/ai/ProjectManifestReader.js +34 -0
- package/dist/ai/ProjectManifestReader.js.map +1 -0
- package/dist/ai/ProjectManifestReader.test.d.ts +1 -0
- package/dist/ai/ProjectManifestReader.test.js +59 -0
- package/dist/ai/ProjectManifestReader.test.js.map +1 -0
- package/dist/ai/PromptBuilder.d.ts +5 -0
- package/dist/ai/PromptBuilder.js +42 -0
- package/dist/ai/PromptBuilder.js.map +1 -0
- package/dist/ai/PromptBuilder.test.d.ts +1 -0
- package/dist/ai/PromptBuilder.test.js +66 -0
- package/dist/ai/PromptBuilder.test.js.map +1 -0
- package/dist/cmd/cve-scanner.d.ts +2 -0
- package/dist/cmd/cve-scanner.js +60 -0
- package/dist/cmd/cve-scanner.js.map +1 -0
- package/dist/core/CommandRunner.d.ts +6 -0
- package/dist/core/CommandRunner.js +16 -0
- package/dist/core/CommandRunner.js.map +1 -0
- package/dist/core/DatasetLoader.d.ts +14 -0
- package/dist/core/DatasetLoader.js +44 -0
- package/dist/core/DatasetLoader.js.map +1 -0
- package/dist/core/DatasetLoader.test.d.ts +1 -0
- package/dist/core/DatasetLoader.test.js +41 -0
- package/dist/core/DatasetLoader.test.js.map +1 -0
- package/dist/core/ProjectRootDiscovery.d.ts +9 -0
- package/dist/core/ProjectRootDiscovery.js +62 -0
- package/dist/core/ProjectRootDiscovery.js.map +1 -0
- package/dist/core/ProjectRootDiscovery.test.d.ts +1 -0
- package/dist/core/ProjectRootDiscovery.test.js +47 -0
- package/dist/core/ProjectRootDiscovery.test.js.map +1 -0
- package/dist/core/Scanner.d.ts +19 -0
- package/dist/core/Scanner.js +66 -0
- package/dist/core/Scanner.js.map +1 -0
- package/dist/core/Scanner.test.d.ts +1 -0
- package/dist/core/Scanner.test.js +157 -0
- package/dist/core/Scanner.test.js.map +1 -0
- package/dist/core/VersionComparator.d.ts +6 -0
- package/dist/core/VersionComparator.js +35 -0
- package/dist/core/VersionComparator.js.map +1 -0
- package/dist/core/VersionComparator.test.d.ts +1 -0
- package/dist/core/VersionComparator.test.js +21 -0
- package/dist/core/VersionComparator.test.js.map +1 -0
- package/dist/core/cpe.d.ts +9 -0
- package/dist/core/cpe.js +32 -0
- package/dist/core/cpe.js.map +1 -0
- package/dist/core/cpe.test.d.ts +1 -0
- package/dist/core/cpe.test.js +25 -0
- package/dist/core/cpe.test.js.map +1 -0
- package/dist/core/types.d.ts +57 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/ecosystems/enrichment/CveListCache.d.ts +13 -0
- package/dist/ecosystems/enrichment/CveListCache.js +88 -0
- package/dist/ecosystems/enrichment/CveListCache.js.map +1 -0
- package/dist/ecosystems/enrichment/CveListCache.test.d.ts +1 -0
- package/dist/ecosystems/enrichment/CveListCache.test.js +49 -0
- package/dist/ecosystems/enrichment/CveListCache.test.js.map +1 -0
- package/dist/ecosystems/enrichment/CveOrgDatasetReader.d.ts +12 -0
- package/dist/ecosystems/enrichment/CveOrgDatasetReader.js +59 -0
- package/dist/ecosystems/enrichment/CveOrgDatasetReader.js.map +1 -0
- package/dist/ecosystems/enrichment/CveOrgDatasetReader.test.d.ts +1 -0
- package/dist/ecosystems/enrichment/CveOrgDatasetReader.test.js +48 -0
- package/dist/ecosystems/enrichment/CveOrgDatasetReader.test.js.map +1 -0
- package/dist/ecosystems/enrichment/ZipExtractor.d.ts +18 -0
- package/dist/ecosystems/enrichment/ZipExtractor.js +105 -0
- package/dist/ecosystems/enrichment/ZipExtractor.js.map +1 -0
- package/dist/ecosystems/enrichment/ZipExtractor.test.d.ts +1 -0
- package/dist/ecosystems/enrichment/ZipExtractor.test.js +36 -0
- package/dist/ecosystems/enrichment/ZipExtractor.test.js.map +1 -0
- package/dist/ecosystems/java/GradleDependencyDetector.d.ts +9 -0
- package/dist/ecosystems/java/GradleDependencyDetector.js +33 -0
- package/dist/ecosystems/java/GradleDependencyDetector.js.map +1 -0
- package/dist/ecosystems/java/MavenDependencyDetector.d.ts +9 -0
- package/dist/ecosystems/java/MavenDependencyDetector.js +39 -0
- package/dist/ecosystems/java/MavenDependencyDetector.js.map +1 -0
- package/dist/ecosystems/java/parsers/gradleDependencyOutputParser.d.ts +12 -0
- package/dist/ecosystems/java/parsers/gradleDependencyOutputParser.js +32 -0
- package/dist/ecosystems/java/parsers/gradleDependencyOutputParser.js.map +1 -0
- package/dist/ecosystems/java/parsers/gradleDependencyOutputParser.test.d.ts +1 -0
- package/dist/ecosystems/java/parsers/gradleDependencyOutputParser.test.js +36 -0
- package/dist/ecosystems/java/parsers/gradleDependencyOutputParser.test.js.map +1 -0
- package/dist/ecosystems/java/parsers/mavenDependencyListParser.d.ts +11 -0
- package/dist/ecosystems/java/parsers/mavenDependencyListParser.js +31 -0
- package/dist/ecosystems/java/parsers/mavenDependencyListParser.js.map +1 -0
- package/dist/ecosystems/java/parsers/mavenDependencyListParser.test.d.ts +1 -0
- package/dist/ecosystems/java/parsers/mavenDependencyListParser.test.js +37 -0
- package/dist/ecosystems/java/parsers/mavenDependencyListParser.test.js.map +1 -0
- package/dist/ecosystems/java/resolveExecutable.d.ts +8 -0
- package/dist/ecosystems/java/resolveExecutable.js +22 -0
- package/dist/ecosystems/java/resolveExecutable.js.map +1 -0
- package/dist/ecosystems/java/resolveExecutable.test.d.ts +1 -0
- package/dist/ecosystems/java/resolveExecutable.test.js +42 -0
- package/dist/ecosystems/java/resolveExecutable.test.js.map +1 -0
- package/dist/ecosystems/npm/NpmDetector.d.ts +6 -0
- package/dist/ecosystems/npm/NpmDetector.js +31 -0
- package/dist/ecosystems/npm/NpmDetector.js.map +1 -0
- package/dist/ecosystems/npm/PackageLockParser.d.ts +11 -0
- package/dist/ecosystems/npm/PackageLockParser.js +65 -0
- package/dist/ecosystems/npm/PackageLockParser.js.map +1 -0
- package/dist/ecosystems/npm/PackageLockParser.test.d.ts +1 -0
- package/dist/ecosystems/npm/PackageLockParser.test.js +59 -0
- package/dist/ecosystems/npm/PackageLockParser.test.js.map +1 -0
- package/dist/lib/CveScannerApi.d.ts +13 -0
- package/dist/lib/CveScannerApi.js +55 -0
- package/dist/lib/CveScannerApi.js.map +1 -0
- package/dist/lib/report.d.ts +4 -0
- package/dist/lib/report.js +47 -0
- package/dist/lib/report.js.map +1 -0
- package/dist/pipeline/dedupeEntries.d.ts +8 -0
- package/dist/pipeline/dedupeEntries.js +20 -0
- package/dist/pipeline/dedupeEntries.js.map +1 -0
- package/dist/pipeline/dedupeEntries.test.d.ts +1 -0
- package/dist/pipeline/dedupeEntries.test.js +27 -0
- package/dist/pipeline/dedupeEntries.test.js.map +1 -0
- package/dist/pipeline/hashDataset.d.ts +7 -0
- package/dist/pipeline/hashDataset.js +13 -0
- package/dist/pipeline/hashDataset.js.map +1 -0
- package/dist/pipeline/hashDataset.test.d.ts +1 -0
- package/dist/pipeline/hashDataset.test.js +28 -0
- package/dist/pipeline/hashDataset.test.js.map +1 -0
- package/dist/pipeline/mapSeverityToScore.d.ts +9 -0
- package/dist/pipeline/mapSeverityToScore.js +26 -0
- package/dist/pipeline/mapSeverityToScore.js.map +1 -0
- package/dist/pipeline/mapSeverityToScore.test.d.ts +1 -0
- package/dist/pipeline/mapSeverityToScore.test.js +20 -0
- package/dist/pipeline/mapSeverityToScore.test.js.map +1 -0
- package/dist/pipeline/parseOsvRecord.d.ts +37 -0
- package/dist/pipeline/parseOsvRecord.js +88 -0
- package/dist/pipeline/parseOsvRecord.js.map +1 -0
- package/dist/pipeline/parseOsvRecord.test.d.ts +1 -0
- package/dist/pipeline/parseOsvRecord.test.js +220 -0
- package/dist/pipeline/parseOsvRecord.test.js.map +1 -0
- package/package.json +37 -6
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls any OpenAI-chat-completions-compatible endpoint. No SDK dependency —
|
|
3
|
+
* uses Node's built-in global fetch. See specs/90-ai-assistance.md.
|
|
4
|
+
*/
|
|
5
|
+
export declare class AiAssistant {
|
|
6
|
+
private endpoint;
|
|
7
|
+
private apiKey;
|
|
8
|
+
private model;
|
|
9
|
+
constructor(endpoint: string, apiKey: string, model: string);
|
|
10
|
+
analyze(prompt: string): Promise<string>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AiAssistant = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Calls any OpenAI-chat-completions-compatible endpoint. No SDK dependency —
|
|
6
|
+
* uses Node's built-in global fetch. See specs/90-ai-assistance.md.
|
|
7
|
+
*/
|
|
8
|
+
class AiAssistant {
|
|
9
|
+
constructor(endpoint, apiKey, model) {
|
|
10
|
+
this.endpoint = endpoint;
|
|
11
|
+
this.apiKey = apiKey;
|
|
12
|
+
this.model = model;
|
|
13
|
+
}
|
|
14
|
+
async analyze(prompt) {
|
|
15
|
+
const res = await fetch(`${this.endpoint}/chat/completions`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify({
|
|
22
|
+
model: this.model,
|
|
23
|
+
messages: [{ role: 'user', content: prompt }],
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
throw new Error(`AI endpoint returned ${res.status}: ${await res.text()}`);
|
|
28
|
+
}
|
|
29
|
+
const body = (await res.json());
|
|
30
|
+
return body.choices[0].message.content;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.AiAssistant = AiAssistant;
|
|
34
|
+
//# sourceMappingURL=AiAssistant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiAssistant.js","sourceRoot":"","sources":["../../src/ai/AiAssistant.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH;IACE,YACU,QAAgB,EAChB,MAAc,EACd,KAAa;wBAFb,QAAQ;sBACR,MAAM;qBACN,KAAK;IACZ,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,QAAQ,mBAAmB,EAAE;YAC3D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;aACvC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;aAC9C,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,wBAAwB,GAAG,CAAC,MAAM,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAC1D,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE7B,CAAC;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IACzC,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Ecosystem, ProjectManifest } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* Reads the project manifest file for an ecosystem (pom.xml, build.gradle(.kts),
|
|
4
|
+
* package.json) from scanDir, so the AI investigation prompt can tell what kind
|
|
5
|
+
* of software this actually is. Best-effort: returns undefined if none of the
|
|
6
|
+
* candidate files exist. See specs/90-ai-assistance.md.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readProjectManifest(scanDir: string, ecosystem: Ecosystem): ProjectManifest | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readProjectManifest = readProjectManifest;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const MANIFEST_CANDIDATES = {
|
|
7
|
+
maven: ['pom.xml'],
|
|
8
|
+
gradle: ['build.gradle', 'build.gradle.kts'],
|
|
9
|
+
npm: ['package.json'],
|
|
10
|
+
};
|
|
11
|
+
const MAX_CONTENT_LENGTH = 8000;
|
|
12
|
+
/**
|
|
13
|
+
* Reads the project manifest file for an ecosystem (pom.xml, build.gradle(.kts),
|
|
14
|
+
* package.json) from scanDir, so the AI investigation prompt can tell what kind
|
|
15
|
+
* of software this actually is. Best-effort: returns undefined if none of the
|
|
16
|
+
* candidate files exist. See specs/90-ai-assistance.md.
|
|
17
|
+
*/
|
|
18
|
+
function readProjectManifest(scanDir, ecosystem) {
|
|
19
|
+
for (const fileName of MANIFEST_CANDIDATES[ecosystem]) {
|
|
20
|
+
const filePath = (0, path_1.join)(scanDir, fileName);
|
|
21
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
22
|
+
continue;
|
|
23
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
24
|
+
const truncated = content.length > MAX_CONTENT_LENGTH;
|
|
25
|
+
return {
|
|
26
|
+
fileName,
|
|
27
|
+
content: truncated
|
|
28
|
+
? `${content.slice(0, MAX_CONTENT_LENGTH)}\n... (truncated)`
|
|
29
|
+
: content,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=ProjectManifestReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectManifestReader.js","sourceRoot":"","sources":["../../src/ai/ProjectManifestReader.ts"],"names":[],"mappings":";;;AAAA,2BAA8C;AAC9C,+BAA4B;AAG5B,MAAM,mBAAmB,GAAgC;IACvD,KAAK,EAAE,CAAC,SAAS,CAAC;IAClB,MAAM,EAAE,CAAC,cAAc,EAAE,kBAAkB,CAAC;IAC5C,GAAG,EAAE,CAAC,cAAc,CAAC;CACtB,CAAC;AAEF,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC;;;;;GAKG;AACH,6BACE,OAAe,EACf,SAAoB;IAEpB,KAAK,MAAM,QAAQ,IAAI,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,IAAA,eAAU,EAAC,QAAQ,CAAC;YAAE,SAAS;QAEpC,MAAM,OAAO,GAAG,IAAA,iBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,kBAAkB,CAAC;QACtD,OAAO;YACL,QAAQ;YACR,OAAO,EAAE,SAAS;gBAChB,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,mBAAmB;gBAC5D,CAAC,CAAC,OAAO;SACZ,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const node_test_1 = require("node:test");
|
|
11
|
+
const ProjectManifestReader_1 = require("./ProjectManifestReader");
|
|
12
|
+
function makeScanDir() {
|
|
13
|
+
return (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'cve-scanner-manifest-'));
|
|
14
|
+
}
|
|
15
|
+
(0, node_test_1.test)('reads pom.xml for maven', () => {
|
|
16
|
+
const dir = makeScanDir();
|
|
17
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'pom.xml'), '<project></project>');
|
|
18
|
+
strict_1.default.deepEqual((0, ProjectManifestReader_1.readProjectManifest)(dir, 'maven'), {
|
|
19
|
+
fileName: 'pom.xml',
|
|
20
|
+
content: '<project></project>',
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
(0, node_test_1.test)('reads build.gradle.kts for gradle when build.gradle is absent', () => {
|
|
24
|
+
const dir = makeScanDir();
|
|
25
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'build.gradle.kts'), 'plugins { }');
|
|
26
|
+
strict_1.default.deepEqual((0, ProjectManifestReader_1.readProjectManifest)(dir, 'gradle'), {
|
|
27
|
+
fileName: 'build.gradle.kts',
|
|
28
|
+
content: 'plugins { }',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
(0, node_test_1.test)('prefers build.gradle over build.gradle.kts when both exist', () => {
|
|
32
|
+
const dir = makeScanDir();
|
|
33
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'build.gradle'), 'groovy');
|
|
34
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'build.gradle.kts'), 'kotlin');
|
|
35
|
+
strict_1.default.deepEqual((0, ProjectManifestReader_1.readProjectManifest)(dir, 'gradle'), {
|
|
36
|
+
fileName: 'build.gradle',
|
|
37
|
+
content: 'groovy',
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
(0, node_test_1.test)('reads package.json for npm', () => {
|
|
41
|
+
const dir = makeScanDir();
|
|
42
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'package.json'), '{"name":"fixture"}');
|
|
43
|
+
strict_1.default.deepEqual((0, ProjectManifestReader_1.readProjectManifest)(dir, 'npm'), {
|
|
44
|
+
fileName: 'package.json',
|
|
45
|
+
content: '{"name":"fixture"}',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
(0, node_test_1.test)('returns undefined when no candidate file exists', () => {
|
|
49
|
+
const dir = makeScanDir();
|
|
50
|
+
strict_1.default.equal((0, ProjectManifestReader_1.readProjectManifest)(dir, 'maven'), undefined);
|
|
51
|
+
});
|
|
52
|
+
(0, node_test_1.test)('truncates very large manifest files', () => {
|
|
53
|
+
const dir = makeScanDir();
|
|
54
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'package.json'), 'x'.repeat(10000));
|
|
55
|
+
const manifest = (0, ProjectManifestReader_1.readProjectManifest)(dir, 'npm');
|
|
56
|
+
strict_1.default.ok(manifest.content.endsWith('... (truncated)'));
|
|
57
|
+
strict_1.default.ok(manifest.content.length < 10000);
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=ProjectManifestReader.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectManifestReader.test.js","sourceRoot":"","sources":["../../src/ai/ProjectManifestReader.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildInvestigationPrompt = buildInvestigationPrompt;
|
|
4
|
+
/**
|
|
5
|
+
* Pure function, no I/O. See specs/90-ai-assistance.md.
|
|
6
|
+
*/
|
|
7
|
+
function buildInvestigationPrompt(finding) {
|
|
8
|
+
const { artifact, cve, enrichment, projectManifest } = finding;
|
|
9
|
+
const lines = [
|
|
10
|
+
'I found a potentially vulnerable dependency in my project. Please help me assess the risk and suggest remediation.',
|
|
11
|
+
'',
|
|
12
|
+
`- Ecosystem: ${artifact.ecosystem}`,
|
|
13
|
+
`- Artifact: ${artifact.coordinate}`,
|
|
14
|
+
`- Version in use: ${artifact.version}`,
|
|
15
|
+
`- Vulnerability: ${cve.id} (CVSS score ${cve.score})`,
|
|
16
|
+
`- Title: ${cve.title}`,
|
|
17
|
+
];
|
|
18
|
+
if (enrichment) {
|
|
19
|
+
lines.push(`- Description: ${enrichment.description}`);
|
|
20
|
+
if (enrichment.cvssVector)
|
|
21
|
+
lines.push(`- CVSS vector: ${enrichment.cvssVector}`);
|
|
22
|
+
if (enrichment.cwe && enrichment.cwe.length > 0)
|
|
23
|
+
lines.push(`- CWE: ${enrichment.cwe.join(', ')}`);
|
|
24
|
+
if (enrichment.references.length > 0) {
|
|
25
|
+
lines.push('- References:');
|
|
26
|
+
for (const ref of enrichment.references)
|
|
27
|
+
lines.push(` - ${ref}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
lines.push('- No further detail available locally — please answer based on general knowledge of this CVE if you have it.');
|
|
32
|
+
}
|
|
33
|
+
if (projectManifest) {
|
|
34
|
+
lines.push('', `Here is my project's ${projectManifest.fileName}, for context on what kind of software this actually is:`, '', '```', projectManifest.content, '```');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
lines.push('', `I don't have my project's manifest file (e.g. pom.xml, build.gradle, package.json) available to attach — ask me to paste relevant parts of it if that would help.`);
|
|
38
|
+
}
|
|
39
|
+
lines.push('', 'My goal is to decide whether this can be safely ignored or needs a real fix, based on how my project actually uses this dependency — not just the CVE in the abstract. Please:', '1. Suggest concrete commands I can run in this project (e.g. grep/search for specific APIs, classes, or config flags related to the vulnerable code path) to check whether the vulnerable functionality is actually reachable from my code.', '2. If there is a lightweight way to try to reproduce or exploit the vulnerability locally (a crafted input, a minimal test script, a known proof-of-concept pattern), give me the exact command(s) or script to run.', "3. Tell me what results from those commands would indicate it's safe to ignore, versus results that mean I need to upgrade or otherwise fix it.");
|
|
40
|
+
return lines.join('\n');
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=PromptBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PromptBuilder.js","sourceRoot":"","sources":["../../src/ai/PromptBuilder.ts"],"names":[],"mappings":";;;AAEA;;GAEG;AACH,kCAAyC,OAAgB;IACvD,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IAE/D,MAAM,KAAK,GAAG;QACZ,oHAAoH;QACpH,EAAE;QACF,gBAAgB,QAAQ,CAAC,SAAS,EAAE;QACpC,eAAe,QAAQ,CAAC,UAAU,EAAE;QACpC,qBAAqB,QAAQ,CAAC,OAAO,EAAE;QACvC,oBAAoB,GAAG,CAAC,EAAE,gBAAgB,GAAG,CAAC,KAAK,GAAG;QACtD,YAAY,GAAG,CAAC,KAAK,EAAE;KACxB,CAAC;IAEF,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,kBAAkB,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;QACvD,IAAI,UAAU,CAAC,UAAU;YACvB,KAAK,CAAC,IAAI,CAAC,kBAAkB,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC;QACxD,IAAI,UAAU,CAAC,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,UAAU,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpD,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC5B,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,UAAU;gBAAE,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CACR,8GAA8G,CAC/G,CAAC;IACJ,CAAC;IAED,IAAI,eAAe,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CACR,EAAE,EACF,wBAAwB,eAAe,CAAC,QAAQ,0DAA0D,EAC1G,EAAE,EACF,KAAK,EACL,eAAe,CAAC,OAAO,EACvB,KAAK,CACN,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CACR,EAAE,EACF,mKAAmK,CACpK,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CACR,EAAE,EACF,gLAAgL,EAChL,6OAA6O,EAC7O,sNAAsN,EACtN,iJAAiJ,CAClJ,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = require("node:test");
|
|
8
|
+
const PromptBuilder_1 = require("./PromptBuilder");
|
|
9
|
+
const baseFinding = {
|
|
10
|
+
artifact: {
|
|
11
|
+
ecosystem: 'npm',
|
|
12
|
+
coordinate: 'lodash',
|
|
13
|
+
version: '4.17.11',
|
|
14
|
+
source: 'package-lock.json',
|
|
15
|
+
projectRoot: '/project',
|
|
16
|
+
},
|
|
17
|
+
cve: { id: 'CVE-2019-10744', score: 9.1, title: 'prototype pollution' },
|
|
18
|
+
};
|
|
19
|
+
(0, node_test_1.test)('includes core finding details without enrichment', () => {
|
|
20
|
+
const prompt = (0, PromptBuilder_1.buildInvestigationPrompt)(baseFinding);
|
|
21
|
+
strict_1.default.match(prompt, /Ecosystem: npm/);
|
|
22
|
+
strict_1.default.match(prompt, /Artifact: lodash/);
|
|
23
|
+
strict_1.default.match(prompt, /Version in use: 4\.17\.11/);
|
|
24
|
+
strict_1.default.match(prompt, /CVE-2019-10744/);
|
|
25
|
+
strict_1.default.match(prompt, /No further detail available locally/);
|
|
26
|
+
});
|
|
27
|
+
(0, node_test_1.test)('includes enrichment detail when present', () => {
|
|
28
|
+
const prompt = (0, PromptBuilder_1.buildInvestigationPrompt)({
|
|
29
|
+
...baseFinding,
|
|
30
|
+
enrichment: {
|
|
31
|
+
description: 'Prototype pollution via defaultsDeep.',
|
|
32
|
+
cvssVector: 'CVSS:3.1/AV:N',
|
|
33
|
+
cwe: ['CWE-1321'],
|
|
34
|
+
references: ['https://example.com/ref'],
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
strict_1.default.match(prompt, /Prototype pollution via defaultsDeep\./);
|
|
38
|
+
strict_1.default.match(prompt, /CVSS:3\.1\/AV:N/);
|
|
39
|
+
strict_1.default.match(prompt, /CWE-1321/);
|
|
40
|
+
strict_1.default.match(prompt, /https:\/\/example\.com\/ref/);
|
|
41
|
+
strict_1.default.doesNotMatch(prompt, /No further detail available locally/);
|
|
42
|
+
});
|
|
43
|
+
(0, node_test_1.test)('asks for investigation/exploit commands instead of the old fixed questions', () => {
|
|
44
|
+
const prompt = (0, PromptBuilder_1.buildInvestigationPrompt)(baseFinding);
|
|
45
|
+
strict_1.default.match(prompt, /commands I can run in this project/);
|
|
46
|
+
strict_1.default.match(prompt, /reproduce or exploit the vulnerability locally/);
|
|
47
|
+
strict_1.default.match(prompt, /safe to ignore/);
|
|
48
|
+
strict_1.default.doesNotMatch(prompt, /What is the minimum version I should upgrade to\?/);
|
|
49
|
+
});
|
|
50
|
+
(0, node_test_1.test)('mentions the manifest is unavailable when projectManifest is not set', () => {
|
|
51
|
+
const prompt = (0, PromptBuilder_1.buildInvestigationPrompt)(baseFinding);
|
|
52
|
+
strict_1.default.match(prompt, /don't have my project's manifest file/);
|
|
53
|
+
});
|
|
54
|
+
(0, node_test_1.test)('includes the project manifest content when present', () => {
|
|
55
|
+
const prompt = (0, PromptBuilder_1.buildInvestigationPrompt)({
|
|
56
|
+
...baseFinding,
|
|
57
|
+
projectManifest: {
|
|
58
|
+
fileName: 'package.json',
|
|
59
|
+
content: '{"name":"my-app","dependencies":{"lodash":"^4.17.11"}}',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
strict_1.default.match(prompt, /my project's package\.json/);
|
|
63
|
+
strict_1.default.match(prompt, /"name":"my-app"/);
|
|
64
|
+
strict_1.default.doesNotMatch(prompt, /don't have my project's manifest file/);
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=PromptBuilder.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PromptBuilder.test.js","sourceRoot":"","sources":["../../src/ai/PromptBuilder.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const PromptBuilder_1 = require("../ai/PromptBuilder");
|
|
6
|
+
const CveScannerApi_1 = require("../lib/CveScannerApi");
|
|
7
|
+
const report_1 = require("../lib/report");
|
|
8
|
+
const program = new commander_1.Command()
|
|
9
|
+
.option('-s, --scan-dir <dir>', 'Directory to scan.', process.cwd())
|
|
10
|
+
.option('-f, --format <table|json>', 'Report output format.', 'table')
|
|
11
|
+
.option('-c, --cve-data-dir <dir>', 'Path to an extracted full CVE.org bulk download (https://www.cve.org/Downloads), used to enrich findings.')
|
|
12
|
+
.option('-d, --cve-data-download', 'Download the full CVE List (github.com/CVEProject/cvelistV5) and use it to enrich findings, instead of --cve-data-dir. Cached locally for 12 hours.', false)
|
|
13
|
+
.option('-p, --print-prompt', 'Print a ready-to-paste AI investigation prompt per finding, for manual use in a web AI chat UI.', false)
|
|
14
|
+
.option('-e, --ai-endpoint <url>', "OpenAI-compatible base URL. When set, sends each finding's details to this endpoint and prints its response. Opt-in only.")
|
|
15
|
+
.option('-k, --ai-api-key <key>', 'API key for --ai-endpoint. Prefer the CVE_SCANNER_AI_API_KEY env var instead of this flag.')
|
|
16
|
+
.option('-m, --ai-model <name>', 'Model name, required when --ai-endpoint is set.')
|
|
17
|
+
.option('-x, --fail-on-score <number>', "Only exit with a failure code if a finding's score is >= this value.")
|
|
18
|
+
.option('-v, --version', 'Display version', false)
|
|
19
|
+
.parse(process.argv);
|
|
20
|
+
const options = program.opts();
|
|
21
|
+
if (options.version) {
|
|
22
|
+
const pkg = require('../../package.json');
|
|
23
|
+
console.log(`${pkg.name}@${pkg.version}`);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
const aiApiKey = options.aiApiKey ?? process.env.CVE_SCANNER_AI_API_KEY;
|
|
27
|
+
if (options.aiEndpoint && (!options.aiModel || !aiApiKey)) {
|
|
28
|
+
console.error('--ai-endpoint requires --ai-model and an API key (--ai-api-key or CVE_SCANNER_AI_API_KEY).');
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
if (options.cveDataDir && options.cveDataDownload) {
|
|
32
|
+
console.error('Specify either --cve-data-dir or --cve-data-download, not both.');
|
|
33
|
+
process.exit(2);
|
|
34
|
+
}
|
|
35
|
+
const failOnScore = options.failOnScore !== undefined ? Number(options.failOnScore) : undefined;
|
|
36
|
+
async function main() {
|
|
37
|
+
const api = new CveScannerApi_1.CveScannerApi({
|
|
38
|
+
scanDir: options.scanDir,
|
|
39
|
+
cveDataDir: options.cveDataDir,
|
|
40
|
+
cveDataDownload: options.cveDataDownload,
|
|
41
|
+
aiEndpoint: options.aiEndpoint,
|
|
42
|
+
aiApiKey,
|
|
43
|
+
aiModel: options.aiModel,
|
|
44
|
+
});
|
|
45
|
+
const result = await api.scan();
|
|
46
|
+
console.log((0, report_1.formatReport)(result, options.format, options.scanDir));
|
|
47
|
+
if (options.printPrompt) {
|
|
48
|
+
for (const finding of result.findings) {
|
|
49
|
+
console.log('\n---');
|
|
50
|
+
console.log(`Prompt for ${finding.artifact.coordinate}@${finding.artifact.version} (${finding.cve.id}):\n`);
|
|
51
|
+
console.log((0, PromptBuilder_1.buildInvestigationPrompt)(finding));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
process.exit((0, report_1.computeExitCode)(result, failOnScore));
|
|
55
|
+
}
|
|
56
|
+
main().catch((err) => {
|
|
57
|
+
console.error(err instanceof Error ? err.message : err);
|
|
58
|
+
process.exit(2);
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=cve-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cve-scanner.js","sourceRoot":"","sources":["../../src/cmd/cve-scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RealCommandRunner = void 0;
|
|
4
|
+
const util = require('util');
|
|
5
|
+
const exec = util.promisify(require('child_process').exec);
|
|
6
|
+
class RealCommandRunner {
|
|
7
|
+
async run(cmd, workingDir) {
|
|
8
|
+
const { stdout } = await exec(cmd, {
|
|
9
|
+
cwd: workingDir,
|
|
10
|
+
maxBuffer: 1024 * 1024 * 32,
|
|
11
|
+
});
|
|
12
|
+
return stdout;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.RealCommandRunner = RealCommandRunner;
|
|
16
|
+
//# sourceMappingURL=CommandRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandRunner.js","sourceRoot":"","sources":["../../src/core/CommandRunner.ts"],"names":[],"mappings":";;;AAAA,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC;AAM3D;IACE,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,UAAkB;QACvC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE;YACjC,GAAG,EAAE,UAAU;YACf,SAAS,EAAE,IAAI,GAAG,IAAI,GAAG,EAAE;SAC5B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Ecosystem, VulnerabilityDataset, VulnerableEntry } from './types';
|
|
2
|
+
export declare class DatasetLoader {
|
|
3
|
+
private byVendorProduct;
|
|
4
|
+
readonly generatedAt: string;
|
|
5
|
+
constructor(dataset: VulnerabilityDataset);
|
|
6
|
+
static fromFile(filePath: string): DatasetLoader;
|
|
7
|
+
private key;
|
|
8
|
+
/**
|
|
9
|
+
* `coordinate` is "groupId:artifactId" for maven/gradle, or just the package
|
|
10
|
+
* name for npm (which has no vendor/product split). "gradle" artifacts are
|
|
11
|
+
* matched against "maven"-keyed entries — see specs/20-data-format.md.
|
|
12
|
+
*/
|
|
13
|
+
findEntries(ecosystem: Ecosystem, coordinate: string): VulnerableEntry[];
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatasetLoader = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const cpe_1 = require("./cpe");
|
|
6
|
+
class DatasetLoader {
|
|
7
|
+
constructor(dataset) {
|
|
8
|
+
this.byVendorProduct = new Map();
|
|
9
|
+
this.generatedAt = dataset.generatedAt;
|
|
10
|
+
for (const entry of dataset.entries) {
|
|
11
|
+
const key = this.key((0, cpe_1.datasetEcosystem)(entry.ecosystem), (0, cpe_1.vendorProductFromCpe)(entry.cpe));
|
|
12
|
+
const existing = this.byVendorProduct.get(key) ?? [];
|
|
13
|
+
existing.push(entry);
|
|
14
|
+
this.byVendorProduct.set(key, existing);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
static fromFile(filePath) {
|
|
18
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
19
|
+
throw new Error(`Vulnerability dataset not found at ${filePath}. If you're developing this tool from a clone ` +
|
|
20
|
+
`(rather than running it via npx), build it first: npm run refresh-dataset ` +
|
|
21
|
+
'(see specs/60-data-pipeline.md).');
|
|
22
|
+
}
|
|
23
|
+
const raw = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
24
|
+
return new DatasetLoader(JSON.parse(raw));
|
|
25
|
+
}
|
|
26
|
+
key(ecosystem, vendorProduct) {
|
|
27
|
+
return `${ecosystem}:${vendorProduct.toLowerCase()}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `coordinate` is "groupId:artifactId" for maven/gradle, or just the package
|
|
31
|
+
* name for npm (which has no vendor/product split). "gradle" artifacts are
|
|
32
|
+
* matched against "maven"-keyed entries — see specs/20-data-format.md.
|
|
33
|
+
*/
|
|
34
|
+
findEntries(ecosystem, coordinate) {
|
|
35
|
+
const normalizedEcosystem = (0, cpe_1.datasetEcosystem)(ecosystem);
|
|
36
|
+
const vendorProduct = normalizedEcosystem === 'npm'
|
|
37
|
+
? `${coordinate}:${coordinate}`
|
|
38
|
+
: coordinate;
|
|
39
|
+
return (this.byVendorProduct.get(this.key(normalizedEcosystem, vendorProduct)) ??
|
|
40
|
+
[]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.DatasetLoader = DatasetLoader;
|
|
44
|
+
//# sourceMappingURL=DatasetLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DatasetLoader.js","sourceRoot":"","sources":["../../src/core/DatasetLoader.ts"],"names":[],"mappings":";;;AAAA,2BAA8C;AAC9C,+BAA+D;AAG/D;IAIE,YAAY,OAA6B;QAHjC,oBAAe,GAAG,IAAI,GAAG,EAA6B,CAAC;QAI7D,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAClB,IAAA,sBAAgB,EAAC,KAAK,CAAC,SAAS,CAAC,EACjC,IAAA,0BAAoB,EAAC,KAAK,CAAC,GAAG,CAAC,CAChC,CAAC;YACF,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACrD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,QAAgB;QAC9B,IAAI,CAAC,IAAA,eAAU,EAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,sCAAsC,QAAQ,gDAAgD;gBAC5F,4EAA4E;gBAC5E,kCAAkC,CACrC,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,IAAA,iBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC3C,OAAO,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC,CAAC;IACpE,CAAC;IAEO,GAAG,CAAC,SAA0B,EAAE,aAAqB;QAC3D,OAAO,GAAG,SAAS,IAAI,aAAa,CAAC,WAAW,EAAE,EAAE,CAAC;IACvD,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,SAAoB,EAAE,UAAkB;QAClD,MAAM,mBAAmB,GAAG,IAAA,sBAAgB,EAAC,SAAS,CAAC,CAAC;QACxD,MAAM,aAAa,GACjB,mBAAmB,KAAK,KAAK;YAC3B,CAAC,CAAC,GAAG,UAAU,IAAI,UAAU,EAAE;YAC/B,CAAC,CAAC,UAAU,CAAC;QACjB,OAAO,CACL,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,aAAa,CAAC,CAAC;YACtE,EAAE,CACH,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const node_test_1 = require("node:test");
|
|
11
|
+
const DatasetLoader_1 = require("./DatasetLoader");
|
|
12
|
+
const dataset = {
|
|
13
|
+
generatedAt: '2026-01-01T00:00:00.000Z',
|
|
14
|
+
entries: [],
|
|
15
|
+
};
|
|
16
|
+
(0, node_test_1.test)('exposes the dataset generatedAt timestamp', () => {
|
|
17
|
+
strict_1.default.equal(new DatasetLoader_1.DatasetLoader(dataset).generatedAt, '2026-01-01T00:00:00.000Z');
|
|
18
|
+
});
|
|
19
|
+
(0, node_test_1.test)('fromFile round-trips generatedAt and entries', () => {
|
|
20
|
+
const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'cve-scanner-dataset-'));
|
|
21
|
+
const filePath = (0, node_path_1.join)(dir, 'dataset.json');
|
|
22
|
+
(0, node_fs_1.writeFileSync)(filePath, JSON.stringify({
|
|
23
|
+
generatedAt: '2026-02-02T12:00:00.000Z',
|
|
24
|
+
entries: [
|
|
25
|
+
{
|
|
26
|
+
ecosystem: 'npm',
|
|
27
|
+
cpe: 'cpe:2.3:a:left-pad:left-pad:*:*:*:*:*:*:*:*',
|
|
28
|
+
versionExact: '1.0.0',
|
|
29
|
+
cves: [{ id: 'CVE-1', score: 5, title: 'test' }],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
}));
|
|
33
|
+
const loader = DatasetLoader_1.DatasetLoader.fromFile(filePath);
|
|
34
|
+
strict_1.default.equal(loader.generatedAt, '2026-02-02T12:00:00.000Z');
|
|
35
|
+
strict_1.default.equal(loader.findEntries('npm', 'left-pad').length, 1);
|
|
36
|
+
});
|
|
37
|
+
(0, node_test_1.test)('fromFile gives a friendly error pointing at npm run refresh-dataset when the file is missing', () => {
|
|
38
|
+
const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'cve-scanner-dataset-'));
|
|
39
|
+
strict_1.default.throws(() => DatasetLoader_1.DatasetLoader.fromFile((0, node_path_1.join)(dir, 'does-not-exist.json')), /npm run refresh-dataset/);
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=DatasetLoader.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DatasetLoader.test.js","sourceRoot":"","sources":["../../src/core/DatasetLoader.test.ts"],"names":[],"mappings":";;;;;AAAA,gEAAwC;AACxC,qCAAqD;AACrD,qCAAiC;AACjC,yCAAiC;AACjC,yCAAiC;AACjC,mDAAgD;AAGhD,MAAM,OAAO,GAAyB;IACpC,WAAW,EAAE,0BAA0B;IACvC,OAAO,EAAE,EAAE;CACZ,CAAC;AAEF,IAAA,gBAAI,EAAC,2CAA2C,EAAE,GAAG,EAAE;IACrD,gBAAM,CAAC,KAAK,CACV,IAAI,6BAAa,CAAC,OAAO,CAAC,CAAC,WAAW,EACtC,0BAA0B,CAC3B,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAA,gBAAI,EAAC,8CAA8C,EAAE,GAAG,EAAE;IACxD,MAAM,GAAG,GAAG,IAAA,qBAAW,EAAC,IAAA,gBAAI,EAAC,IAAA,gBAAM,GAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,IAAA,gBAAI,EAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAC3C,IAAA,uBAAa,EACX,QAAQ,EACR,IAAI,CAAC,SAAS,CAAC;QACb,WAAW,EAAE,0BAA0B;QACvC,OAAO,EAAE;YACP;gBACE,SAAS,EAAE,KAAK;gBAChB,GAAG,EAAE,6CAA6C;gBAClD,YAAY,EAAE,OAAO;gBACrB,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;aACjD;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,MAAM,GAAG,6BAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChD,gBAAM,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC,CAAC;IAC7D,gBAAM,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAChE,CAAC,CAAC,CAAC;AAEH,IAAA,gBAAI,EAAC,8FAA8F,EAAE,GAAG,EAAE;IACxG,MAAM,GAAG,GAAG,IAAA,qBAAW,EAAC,IAAA,gBAAI,EAAC,IAAA,gBAAM,GAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAChE,gBAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,6BAAa,CAAC,QAAQ,CAAC,IAAA,gBAAI,EAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,EAC9D,yBAAyB,CAC1B,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively discovers independent project roots under `scanDir` — every
|
|
3
|
+
* directory containing at least one supported manifest file. A directory
|
|
4
|
+
* that qualifies is not descended into further (that project's own tooling
|
|
5
|
+
* already resolves everything nested below it — e.g. a Maven multi-module
|
|
6
|
+
* build, or an npm workspaces root lockfile), and common noise directories
|
|
7
|
+
* are skipped outright. See specs/35-project-discovery.md.
|
|
8
|
+
*/
|
|
9
|
+
export declare function findProjectRoots(scanDir: string): string[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findProjectRoots = findProjectRoots;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const MANIFEST_FILENAMES = [
|
|
7
|
+
'pom.xml',
|
|
8
|
+
'build.gradle',
|
|
9
|
+
'build.gradle.kts',
|
|
10
|
+
'package-lock.json',
|
|
11
|
+
'npm-shrinkwrap.json',
|
|
12
|
+
];
|
|
13
|
+
const SKIP_DIRECTORY_NAMES = new Set([
|
|
14
|
+
'node_modules',
|
|
15
|
+
'.git',
|
|
16
|
+
'target',
|
|
17
|
+
'build',
|
|
18
|
+
'dist',
|
|
19
|
+
'out',
|
|
20
|
+
'.gradle',
|
|
21
|
+
'.mvn',
|
|
22
|
+
'vendor',
|
|
23
|
+
'venv',
|
|
24
|
+
'.venv',
|
|
25
|
+
]);
|
|
26
|
+
function hasManifest(dir) {
|
|
27
|
+
return MANIFEST_FILENAMES.some((name) => (0, fs_1.existsSync)((0, path_1.join)(dir, name)));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Recursively discovers independent project roots under `scanDir` — every
|
|
31
|
+
* directory containing at least one supported manifest file. A directory
|
|
32
|
+
* that qualifies is not descended into further (that project's own tooling
|
|
33
|
+
* already resolves everything nested below it — e.g. a Maven multi-module
|
|
34
|
+
* build, or an npm workspaces root lockfile), and common noise directories
|
|
35
|
+
* are skipped outright. See specs/35-project-discovery.md.
|
|
36
|
+
*/
|
|
37
|
+
function findProjectRoots(scanDir) {
|
|
38
|
+
const roots = [];
|
|
39
|
+
function walk(dir) {
|
|
40
|
+
if (hasManifest(dir)) {
|
|
41
|
+
roots.push(dir);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let entries;
|
|
45
|
+
try {
|
|
46
|
+
entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (!entry.isDirectory())
|
|
53
|
+
continue;
|
|
54
|
+
if (SKIP_DIRECTORY_NAMES.has(entry.name))
|
|
55
|
+
continue;
|
|
56
|
+
walk((0, path_1.join)(dir, entry.name));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
walk(scanDir);
|
|
60
|
+
return roots;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=ProjectRootDiscovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectRootDiscovery.js","sourceRoot":"","sources":["../../src/core/ProjectRootDiscovery.ts"],"names":[],"mappings":";;;AAAA,2BAA6C;AAC7C,+BAA4B;AAE5B,MAAM,kBAAkB,GAAG;IACzB,SAAS;IACT,cAAc;IACd,kBAAkB;IAClB,mBAAmB;IACnB,qBAAqB;CACtB,CAAC;AAEF,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,cAAc;IACd,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,KAAK;IACL,SAAS;IACT,MAAM;IACN,QAAQ;IACR,MAAM;IACN,OAAO;CACR,CAAC,CAAC;AAEH,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;GAOG;AACH,0BAAiC,OAAe;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,SAAS,IAAI,CAAC,GAAW;QACvB,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,OAAO;QACT,CAAC;QAED,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,IAAA,gBAAW,EAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;gBAAE,SAAS;YACnC,IAAI,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YACnD,IAAI,CAAC,IAAA,WAAI,EAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,CAAC;IACd,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|