@owasp-aghast/aghast 0.8.0-beta.0
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/LICENSE +661 -0
- package/README.md +127 -0
- package/assets/img/aghastowaspcaption.png +0 -0
- package/assets/txt/logo.txt +52 -0
- package/config/pricing.json +42 -0
- package/config/prompts/false-positive-validation.md +78 -0
- package/config/prompts/general-vuln-discovery.md +99 -0
- package/config/prompts/generic-instructions.md +59 -0
- package/config/prompts/test-cheaper-instructions.md +57 -0
- package/dist/budget.d.ts +62 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +137 -0
- package/dist/budget.js.map +1 -0
- package/dist/build-config.d.ts +15 -0
- package/dist/build-config.d.ts.map +1 -0
- package/dist/build-config.js +568 -0
- package/dist/build-config.js.map +1 -0
- package/dist/check-library.d.ts +88 -0
- package/dist/check-library.d.ts.map +1 -0
- package/dist/check-library.js +461 -0
- package/dist/check-library.js.map +1 -0
- package/dist/check-types.d.ts +35 -0
- package/dist/check-types.d.ts.map +1 -0
- package/dist/check-types.js +66 -0
- package/dist/check-types.js.map +1 -0
- package/dist/claude-code-provider.d.ts +29 -0
- package/dist/claude-code-provider.d.ts.map +1 -0
- package/dist/claude-code-provider.js +347 -0
- package/dist/claude-code-provider.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +109 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors.d.ts +7 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +18 -0
- package/dist/colors.js.map +1 -0
- package/dist/cost-calculator.d.ts +80 -0
- package/dist/cost-calculator.d.ts.map +1 -0
- package/dist/cost-calculator.js +226 -0
- package/dist/cost-calculator.js.map +1 -0
- package/dist/defaults.d.ts +21 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +21 -0
- package/dist/defaults.js.map +1 -0
- package/dist/diff-filter.d.ts +50 -0
- package/dist/diff-filter.d.ts.map +1 -0
- package/dist/diff-filter.js +130 -0
- package/dist/diff-filter.js.map +1 -0
- package/dist/diff-parser.d.ts +40 -0
- package/dist/diff-parser.d.ts.map +1 -0
- package/dist/diff-parser.js +112 -0
- package/dist/diff-parser.js.map +1 -0
- package/dist/diff-unit-matcher.d.ts +53 -0
- package/dist/diff-unit-matcher.d.ts.map +1 -0
- package/dist/diff-unit-matcher.js +156 -0
- package/dist/diff-unit-matcher.js.map +1 -0
- package/dist/discoveries/openant-discovery.d.ts +10 -0
- package/dist/discoveries/openant-discovery.d.ts.map +1 -0
- package/dist/discoveries/openant-discovery.js +56 -0
- package/dist/discoveries/openant-discovery.js.map +1 -0
- package/dist/discoveries/sarif-discovery.d.ts +9 -0
- package/dist/discoveries/sarif-discovery.d.ts.map +1 -0
- package/dist/discoveries/sarif-discovery.js +56 -0
- package/dist/discoveries/sarif-discovery.js.map +1 -0
- package/dist/discoveries/semgrep-discovery.d.ts +9 -0
- package/dist/discoveries/semgrep-discovery.d.ts.map +1 -0
- package/dist/discoveries/semgrep-discovery.js +61 -0
- package/dist/discoveries/semgrep-discovery.js.map +1 -0
- package/dist/discovery.d.ts +100 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +49 -0
- package/dist/discovery.js.map +1 -0
- package/dist/error-codes.d.ts +46 -0
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +70 -0
- package/dist/error-codes.js.map +1 -0
- package/dist/formatters/index.d.ts +10 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +23 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/json-formatter.d.ts +11 -0
- package/dist/formatters/json-formatter.d.ts.map +1 -0
- package/dist/formatters/json-formatter.js +11 -0
- package/dist/formatters/json-formatter.js.map +1 -0
- package/dist/formatters/sarif-formatter.d.ts +18 -0
- package/dist/formatters/sarif-formatter.d.ts.map +1 -0
- package/dist/formatters/sarif-formatter.js +103 -0
- package/dist/formatters/sarif-formatter.js.map +1 -0
- package/dist/formatters/types.d.ts +11 -0
- package/dist/formatters/types.d.ts.map +1 -0
- package/dist/formatters/types.js +6 -0
- package/dist/formatters/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +754 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +140 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +344 -0
- package/dist/logging.js.map +1 -0
- package/dist/mock-agent-provider.d.ts +24 -0
- package/dist/mock-agent-provider.d.ts.map +1 -0
- package/dist/mock-agent-provider.js +38 -0
- package/dist/mock-agent-provider.js.map +1 -0
- package/dist/mock-ai-provider.d.ts +21 -0
- package/dist/mock-ai-provider.d.ts.map +1 -0
- package/dist/mock-ai-provider.js +31 -0
- package/dist/mock-ai-provider.js.map +1 -0
- package/dist/new-check.d.ts +13 -0
- package/dist/new-check.d.ts.map +1 -0
- package/dist/new-check.js +515 -0
- package/dist/new-check.js.map +1 -0
- package/dist/openant-loader.d.ts +105 -0
- package/dist/openant-loader.d.ts.map +1 -0
- package/dist/openant-loader.js +135 -0
- package/dist/openant-loader.js.map +1 -0
- package/dist/openant-runner.d.ts +30 -0
- package/dist/openant-runner.d.ts.map +1 -0
- package/dist/openant-runner.js +113 -0
- package/dist/openant-runner.js.map +1 -0
- package/dist/opencode-provider.d.ts +63 -0
- package/dist/opencode-provider.d.ts.map +1 -0
- package/dist/opencode-provider.js +719 -0
- package/dist/opencode-provider.js.map +1 -0
- package/dist/prompt-template.d.ts +12 -0
- package/dist/prompt-template.d.ts.map +1 -0
- package/dist/prompt-template.js +36 -0
- package/dist/prompt-template.js.map +1 -0
- package/dist/provider-registry.d.ts +15 -0
- package/dist/provider-registry.d.ts.map +1 -0
- package/dist/provider-registry.js +29 -0
- package/dist/provider-registry.js.map +1 -0
- package/dist/provider-utils.d.ts +52 -0
- package/dist/provider-utils.d.ts.map +1 -0
- package/dist/provider-utils.js +40 -0
- package/dist/provider-utils.js.map +1 -0
- package/dist/repository-analyzer.d.ts +68 -0
- package/dist/repository-analyzer.d.ts.map +1 -0
- package/dist/repository-analyzer.js +238 -0
- package/dist/repository-analyzer.js.map +1 -0
- package/dist/response-parser.d.ts +14 -0
- package/dist/response-parser.d.ts.map +1 -0
- package/dist/response-parser.js +111 -0
- package/dist/response-parser.js.map +1 -0
- package/dist/runtime-config.d.ts +15 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +187 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/sarif-parser.d.ts +20 -0
- package/dist/sarif-parser.d.ts.map +1 -0
- package/dist/sarif-parser.js +76 -0
- package/dist/sarif-parser.js.map +1 -0
- package/dist/scan-history.d.ts +82 -0
- package/dist/scan-history.d.ts.map +1 -0
- package/dist/scan-history.js +127 -0
- package/dist/scan-history.js.map +1 -0
- package/dist/scan-runner.d.ts +122 -0
- package/dist/scan-runner.d.ts.map +1 -0
- package/dist/scan-runner.js +928 -0
- package/dist/scan-runner.js.map +1 -0
- package/dist/semgrep-runner.d.ts +25 -0
- package/dist/semgrep-runner.d.ts.map +1 -0
- package/dist/semgrep-runner.js +100 -0
- package/dist/semgrep-runner.js.map +1 -0
- package/dist/snippet-extractor.d.ts +25 -0
- package/dist/snippet-extractor.d.ts.map +1 -0
- package/dist/snippet-extractor.js +56 -0
- package/dist/snippet-extractor.js.map +1 -0
- package/dist/stats.d.ts +11 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +197 -0
- package/dist/stats.js.map +1 -0
- package/dist/types.d.ts +321 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git diff parsing utility.
|
|
3
|
+
*
|
|
4
|
+
* Parses unified diff format (output of `git diff`) into a structured
|
|
5
|
+
* map of changed regions per file. Used by the diff filter to
|
|
6
|
+
* determine which code was changed.
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
// --- Parsing ---
|
|
11
|
+
/**
|
|
12
|
+
* Parse a unified diff string into a DiffMap.
|
|
13
|
+
*
|
|
14
|
+
* Extracts file paths and hunk headers, using the new-file line numbers
|
|
15
|
+
* (the `+` side) since we analyze the current state of the code.
|
|
16
|
+
*
|
|
17
|
+
* Handles:
|
|
18
|
+
* - Regular file modifications
|
|
19
|
+
* - Renames (maps to new path)
|
|
20
|
+
* - New files (entire file is changed)
|
|
21
|
+
* - Deleted files (excluded — no code to analyze)
|
|
22
|
+
* - Binary files (skipped)
|
|
23
|
+
*/
|
|
24
|
+
export function parseDiff(diffText) {
|
|
25
|
+
const result = new Map();
|
|
26
|
+
if (!diffText.trim())
|
|
27
|
+
return result;
|
|
28
|
+
const lines = diffText.split('\n').map(l => l.replace(/\r$/, ''));
|
|
29
|
+
let currentFile = null;
|
|
30
|
+
let isDeletedFile = false;
|
|
31
|
+
let isBinaryFile = false;
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const line = lines[i];
|
|
34
|
+
// New file diff header: "diff --git a/... b/..."
|
|
35
|
+
if (line.startsWith('diff --git ')) {
|
|
36
|
+
currentFile = null;
|
|
37
|
+
isDeletedFile = false;
|
|
38
|
+
isBinaryFile = false;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Binary file marker
|
|
42
|
+
if (line.startsWith('Binary files ') || line === 'GIT binary patch') {
|
|
43
|
+
isBinaryFile = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// Deleted file marker
|
|
47
|
+
if (line === 'deleted file mode 100644' || line === 'deleted file mode 100755' || line.startsWith('deleted file mode ')) {
|
|
48
|
+
isDeletedFile = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// New file path: "+++ b/path" (the destination side of the diff)
|
|
52
|
+
if (line.startsWith('+++ b/')) {
|
|
53
|
+
if (isDeletedFile || isBinaryFile)
|
|
54
|
+
continue;
|
|
55
|
+
currentFile = line.slice(6); // Remove "+++ b/"
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// Deleted file: "+++ /dev/null"
|
|
59
|
+
if (line === '+++ /dev/null') {
|
|
60
|
+
isDeletedFile = true;
|
|
61
|
+
currentFile = null;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Hunk header: "@@ -old,len +new,len @@"
|
|
65
|
+
if (line.startsWith('@@ ') && currentFile && !isDeletedFile && !isBinaryFile) {
|
|
66
|
+
const match = line.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
|
|
67
|
+
if (match) {
|
|
68
|
+
const startLine = parseInt(match[1], 10);
|
|
69
|
+
const lineCount = match[2] !== undefined ? parseInt(match[2], 10) : 1;
|
|
70
|
+
// A hunk with 0 new lines means pure deletion in this region — skip
|
|
71
|
+
if (lineCount === 0)
|
|
72
|
+
continue;
|
|
73
|
+
const endLine = startLine + lineCount - 1;
|
|
74
|
+
const hunk = { file: currentFile, startLine, endLine };
|
|
75
|
+
if (!result.has(currentFile)) {
|
|
76
|
+
result.set(currentFile, []);
|
|
77
|
+
}
|
|
78
|
+
result.get(currentFile).push(hunk);
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
// --- Git integration ---
|
|
86
|
+
/**
|
|
87
|
+
* Run `git diff <ref>` against a repository and return the raw unified diff.
|
|
88
|
+
*/
|
|
89
|
+
export async function getDiff(repoPath, ref) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
execFile('git', ['diff', ref], { cwd: repoPath, maxBuffer: 50 * 1024 * 1024 }, // 50MB buffer
|
|
92
|
+
(error, stdout, stderr) => {
|
|
93
|
+
if (error) {
|
|
94
|
+
reject(new Error(`git diff failed: ${stderr?.trim() || error.message}`));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
resolve(stdout);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Load a diff from a file path.
|
|
103
|
+
*/
|
|
104
|
+
export async function loadDiffFromFile(filePath) {
|
|
105
|
+
try {
|
|
106
|
+
return await readFile(filePath, 'utf-8');
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
throw new Error(`Failed to read diff file: ${filePath}`, { cause: err });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=diff-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-parser.js","sourceRoot":"","sources":["../src/diff-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit-to-diff matching with call graph traversal.
|
|
3
|
+
*
|
|
4
|
+
* Determines which OpenAnt code units are "touched" by a git diff:
|
|
5
|
+
* - Tier 1 (direct): unit's code region overlaps with a changed region
|
|
6
|
+
* - Tier 2 (flow): unit is a caller or callee of a directly-touched unit
|
|
7
|
+
*
|
|
8
|
+
* Also filters Semgrep findings to only those within touched units
|
|
9
|
+
* (or in files without OpenAnt coverage that have diff changes).
|
|
10
|
+
*/
|
|
11
|
+
import type { OpenAntUnit } from './openant-loader.js';
|
|
12
|
+
import type { DiffMap, DiffHunk } from './diff-parser.js';
|
|
13
|
+
/**
|
|
14
|
+
* Normalize file path to forward slashes for cross-platform comparison.
|
|
15
|
+
* Diffs always use forward slashes; OpenAnt uses backslashes on Windows.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizePath(filePath: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Look up a file in the DiffMap, normalising the key for cross-platform matching.
|
|
20
|
+
*/
|
|
21
|
+
export declare function diffMapGet(diffMap: DiffMap, filePath: string): DiffHunk[] | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Check whether two line ranges overlap.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rangesOverlap(aStart: number, aEnd: number, bStart: number, bEnd: number): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Find all OpenAnt units that are "touched" by the diff.
|
|
28
|
+
*
|
|
29
|
+
* A unit is touched if:
|
|
30
|
+
* 1. Its code region directly overlaps with a changed region (Tier 1)
|
|
31
|
+
* 2. It is within `depth` hops in the call graph of a directly-touched unit (Tier 2)
|
|
32
|
+
*
|
|
33
|
+
* @param units All OpenAnt units (after any configured filtering)
|
|
34
|
+
* @param diffMap Changed regions from the parsed diff
|
|
35
|
+
* @param depth Call graph hops to follow (default: 1). 0 = direct only.
|
|
36
|
+
*/
|
|
37
|
+
export declare function findTouchedUnits(units: OpenAntUnit[], diffMap: DiffMap, depth?: number): OpenAntUnit[];
|
|
38
|
+
/**
|
|
39
|
+
* Filter discovered findings to only those within the scope of touched units.
|
|
40
|
+
*
|
|
41
|
+
* Two-path logic:
|
|
42
|
+
* - Path A: Files with OpenAnt coverage — finding must be inside a touched unit
|
|
43
|
+
* - Path B: Files without OpenAnt coverage — include all findings if the file has diff changes
|
|
44
|
+
*
|
|
45
|
+
* Generic over T so any object carrying file/startLine/endLine (DiscoveredTarget,
|
|
46
|
+
* CheckTarget, etc.) can be filtered without conversion.
|
|
47
|
+
*/
|
|
48
|
+
export declare function filterFindingsByScope<T extends {
|
|
49
|
+
file: string;
|
|
50
|
+
startLine: number;
|
|
51
|
+
endLine: number;
|
|
52
|
+
}>(findings: T[], touchedUnits: OpenAntUnit[], allUnits: OpenAntUnit[], diffMap: DiffMap): T[];
|
|
53
|
+
//# sourceMappingURL=diff-unit-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-unit-matcher.d.ts","sourceRoot":"","sources":["../src/diff-unit-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,SAAS,CAUrF;AASD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAC5B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAC3B,OAAO,CAET;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,OAAO,EAChB,KAAK,GAAE,MAAU,GAChB,WAAW,EAAE,CAkEf;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAClG,QAAQ,EAAE,CAAC,EAAE,EACb,YAAY,EAAE,WAAW,EAAE,EAC3B,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE,OAAO,GACf,CAAC,EAAE,CAyBL"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit-to-diff matching with call graph traversal.
|
|
3
|
+
*
|
|
4
|
+
* Determines which OpenAnt code units are "touched" by a git diff:
|
|
5
|
+
* - Tier 1 (direct): unit's code region overlaps with a changed region
|
|
6
|
+
* - Tier 2 (flow): unit is a caller or callee of a directly-touched unit
|
|
7
|
+
*
|
|
8
|
+
* Also filters Semgrep findings to only those within touched units
|
|
9
|
+
* (or in files without OpenAnt coverage that have diff changes).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Normalize file path to forward slashes for cross-platform comparison.
|
|
13
|
+
* Diffs always use forward slashes; OpenAnt uses backslashes on Windows.
|
|
14
|
+
*/
|
|
15
|
+
export function normalizePath(filePath) {
|
|
16
|
+
return filePath.replace(/\\/g, '/');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Look up a file in the DiffMap, normalising the key for cross-platform matching.
|
|
20
|
+
*/
|
|
21
|
+
export function diffMapGet(diffMap, filePath) {
|
|
22
|
+
// Try exact match first (fast path)
|
|
23
|
+
const exact = diffMap.get(filePath);
|
|
24
|
+
if (exact)
|
|
25
|
+
return exact;
|
|
26
|
+
// Fall back to normalised comparison
|
|
27
|
+
const normalized = normalizePath(filePath);
|
|
28
|
+
for (const [key, value] of diffMap) {
|
|
29
|
+
if (normalizePath(key) === normalized)
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check whether a file exists in the DiffMap, normalising for cross-platform matching.
|
|
36
|
+
*/
|
|
37
|
+
function diffMapHas(diffMap, filePath) {
|
|
38
|
+
return diffMapGet(diffMap, filePath) !== undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check whether two line ranges overlap.
|
|
42
|
+
*/
|
|
43
|
+
export function rangesOverlap(aStart, aEnd, bStart, bEnd) {
|
|
44
|
+
return aStart <= bEnd && aEnd >= bStart;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Find all OpenAnt units that are "touched" by the diff.
|
|
48
|
+
*
|
|
49
|
+
* A unit is touched if:
|
|
50
|
+
* 1. Its code region directly overlaps with a changed region (Tier 1)
|
|
51
|
+
* 2. It is within `depth` hops in the call graph of a directly-touched unit (Tier 2)
|
|
52
|
+
*
|
|
53
|
+
* @param units All OpenAnt units (after any configured filtering)
|
|
54
|
+
* @param diffMap Changed regions from the parsed diff
|
|
55
|
+
* @param depth Call graph hops to follow (default: 1). 0 = direct only.
|
|
56
|
+
*/
|
|
57
|
+
export function findTouchedUnits(units, diffMap, depth = 1) {
|
|
58
|
+
const touchedIds = new Set();
|
|
59
|
+
// Tier 1: direct overlap with diff
|
|
60
|
+
for (const unit of units) {
|
|
61
|
+
const origin = unit.code.primary_origin;
|
|
62
|
+
const regions = diffMapGet(diffMap, origin.file_path);
|
|
63
|
+
if (regions && regions.some(r => rangesOverlap(origin.start_line, origin.end_line, r.startLine, r.endLine))) {
|
|
64
|
+
touchedIds.add(unit.id);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Tier 2: call graph adjacency (N hops)
|
|
68
|
+
//
|
|
69
|
+
// Build a lookup of all identifiers that can refer to a unit: its id,
|
|
70
|
+
// its function_name, and its file_path:function_name. OpenAnt uses
|
|
71
|
+
// different formats across versions (qualified "file:func" in direct_calls
|
|
72
|
+
// vs unqualified "func" in function_name), so we match against all.
|
|
73
|
+
const unitIdsByName = new Map();
|
|
74
|
+
for (const unit of units) {
|
|
75
|
+
const origin = unit.code.primary_origin;
|
|
76
|
+
const names = [
|
|
77
|
+
unit.id,
|
|
78
|
+
origin.function_name,
|
|
79
|
+
`${normalizePath(origin.file_path)}:${origin.function_name}`,
|
|
80
|
+
];
|
|
81
|
+
for (const name of names) {
|
|
82
|
+
if (!unitIdsByName.has(name))
|
|
83
|
+
unitIdsByName.set(name, new Set());
|
|
84
|
+
unitIdsByName.get(name).add(unit.id);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (let hop = 0; hop < depth; hop++) {
|
|
88
|
+
const newlyTouched = new Set();
|
|
89
|
+
// For each currently touched unit, find its direct callees and callers
|
|
90
|
+
for (const unit of units) {
|
|
91
|
+
if (!touchedIds.has(unit.id))
|
|
92
|
+
continue;
|
|
93
|
+
const meta = unit.metadata;
|
|
94
|
+
// Units that this touched unit calls (callees)
|
|
95
|
+
for (const calleeName of (meta.direct_calls ?? [])) {
|
|
96
|
+
const calleeIds = unitIdsByName.get(calleeName) ?? unitIdsByName.get(normalizePath(calleeName));
|
|
97
|
+
if (calleeIds) {
|
|
98
|
+
for (const id of calleeIds) {
|
|
99
|
+
if (!touchedIds.has(id))
|
|
100
|
+
newlyTouched.add(id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Units that call this touched unit (callers)
|
|
105
|
+
for (const callerName of (meta.direct_callers ?? [])) {
|
|
106
|
+
const callerIds = unitIdsByName.get(callerName) ?? unitIdsByName.get(normalizePath(callerName));
|
|
107
|
+
if (callerIds) {
|
|
108
|
+
for (const id of callerIds) {
|
|
109
|
+
if (!touchedIds.has(id))
|
|
110
|
+
newlyTouched.add(id);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (newlyTouched.size === 0)
|
|
116
|
+
break;
|
|
117
|
+
for (const id of newlyTouched)
|
|
118
|
+
touchedIds.add(id);
|
|
119
|
+
}
|
|
120
|
+
return units.filter(u => touchedIds.has(u.id));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Filter discovered findings to only those within the scope of touched units.
|
|
124
|
+
*
|
|
125
|
+
* Two-path logic:
|
|
126
|
+
* - Path A: Files with OpenAnt coverage — finding must be inside a touched unit
|
|
127
|
+
* - Path B: Files without OpenAnt coverage — include all findings if the file has diff changes
|
|
128
|
+
*
|
|
129
|
+
* Generic over T so any object carrying file/startLine/endLine (DiscoveredTarget,
|
|
130
|
+
* CheckTarget, etc.) can be filtered without conversion.
|
|
131
|
+
*/
|
|
132
|
+
export function filterFindingsByScope(findings, touchedUnits, allUnits, diffMap) {
|
|
133
|
+
// Determine which files have OpenAnt coverage (normalised for cross-platform)
|
|
134
|
+
const coveredFiles = new Set();
|
|
135
|
+
for (const unit of allUnits) {
|
|
136
|
+
coveredFiles.add(normalizePath(unit.code.primary_origin.file_path));
|
|
137
|
+
}
|
|
138
|
+
return findings.filter(finding => {
|
|
139
|
+
const normalizedFindingFile = normalizePath(finding.file);
|
|
140
|
+
if (coveredFiles.has(normalizedFindingFile)) {
|
|
141
|
+
// Path A: file has OpenAnt units — finding must overlap with a touched unit
|
|
142
|
+
// Uses overlap rather than strict containment because discovery tools may
|
|
143
|
+
// include context lines (e.g. Semgrep includes decorators above a function)
|
|
144
|
+
return touchedUnits.some(unit => {
|
|
145
|
+
const origin = unit.code.primary_origin;
|
|
146
|
+
return (normalizedFindingFile === normalizePath(origin.file_path) &&
|
|
147
|
+
rangesOverlap(finding.startLine, finding.endLine, origin.start_line, origin.end_line));
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Path B: file has NO OpenAnt units — include if file has diff changes
|
|
152
|
+
return diffMapHas(diffMap, finding.file);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=diff-unit-matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-unit-matcher.js","sourceRoot":"","sources":["../src/diff-unit-matcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAnt-based target discovery.
|
|
3
|
+
*
|
|
4
|
+
* Runs `openant parse` against the repository (or uses a mock dataset),
|
|
5
|
+
* filters the resulting code units, and returns them as targets with
|
|
6
|
+
* rich prompt enrichment (call graph, entry points, metadata).
|
|
7
|
+
*/
|
|
8
|
+
import type { TargetDiscovery } from '../discovery.js';
|
|
9
|
+
export declare const openantDiscovery: TargetDiscovery;
|
|
10
|
+
//# sourceMappingURL=openant-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openant-discovery.d.ts","sourceRoot":"","sources":["../../src/discoveries/openant-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAsC,MAAM,iBAAiB,CAAC;AAK3F,eAAO,MAAM,gBAAgB,EAAE,eAiD9B,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAnt-based target discovery.
|
|
3
|
+
*
|
|
4
|
+
* Runs `openant parse` against the repository (or uses a mock dataset),
|
|
5
|
+
* filters the resulting code units, and returns them as targets with
|
|
6
|
+
* rich prompt enrichment (call graph, entry points, metadata).
|
|
7
|
+
*/
|
|
8
|
+
import { runOpenAnt } from '../openant-runner.js';
|
|
9
|
+
import { loadDatasetFromFile, filterUnits, formatUnitPromptSection } from '../openant-loader.js';
|
|
10
|
+
import { logProgress, logDebug } from '../logging.js';
|
|
11
|
+
import { DEFAULT_GENERIC_PROMPT } from '../defaults.js';
|
|
12
|
+
const TAG = 'openant-discovery';
|
|
13
|
+
export const openantDiscovery = {
|
|
14
|
+
name: 'openant',
|
|
15
|
+
defaultGenericPrompt: DEFAULT_GENERIC_PROMPT,
|
|
16
|
+
needsInstructions: true,
|
|
17
|
+
supportsDiffFilter: true,
|
|
18
|
+
async discover(check, repoPath, options) {
|
|
19
|
+
const checkTarget = check.checkTarget;
|
|
20
|
+
// Reuse the scan runner's dataset when provided, otherwise run OpenAnt ourselves.
|
|
21
|
+
let datasetPath;
|
|
22
|
+
let cleanup;
|
|
23
|
+
if (options?.openantDatasetPath) {
|
|
24
|
+
logDebug(TAG, `Reusing preloaded OpenAnt dataset: ${options.openantDatasetPath}`);
|
|
25
|
+
datasetPath = options.openantDatasetPath;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
({ datasetPath, cleanup } = await runOpenAnt(repoPath));
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
// Load and filter units
|
|
32
|
+
const dataset = await loadDatasetFromFile(datasetPath);
|
|
33
|
+
const totalUnits = dataset.units.length;
|
|
34
|
+
const units = filterUnits(dataset.units, checkTarget.openant);
|
|
35
|
+
logProgress(TAG, `Loaded ${totalUnits} units (${units.length} after filtering)`);
|
|
36
|
+
return units.map((unit, idx) => {
|
|
37
|
+
const origin = unit.code.primary_origin;
|
|
38
|
+
return {
|
|
39
|
+
file: origin.file_path,
|
|
40
|
+
startLine: origin.start_line,
|
|
41
|
+
endLine: origin.end_line,
|
|
42
|
+
label: `[unit ${idx + 1}/${units.length}]`,
|
|
43
|
+
promptEnrichment: formatUnitPromptSection(unit),
|
|
44
|
+
agentOptions: { maxTurns: 20 },
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
if (cleanup) {
|
|
50
|
+
await cleanup();
|
|
51
|
+
logDebug(TAG, 'Cleaned up temporary OpenAnt output');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=openant-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openant-discovery.js","sourceRoot":"","sources":["../../src/discoveries/openant-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAIxD,MAAM,GAAG,GAAG,mBAAmB,CAAC;AAEhC,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,IAAI,EAAE,SAAS;IACf,oBAAoB,EAAE,sBAAsB;IAC5C,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,IAAI;IAExB,KAAK,CAAC,QAAQ,CACZ,KAAoB,EACpB,QAAgB,EAChB,OAA0B;QAE1B,MAAM,WAAW,GAAG,KAAK,CAAC,WAAY,CAAC;QAEvC,kFAAkF;QAClF,IAAI,WAAmB,CAAC;QACxB,IAAI,OAA0C,CAAC;QAC/C,IAAI,OAAO,EAAE,kBAAkB,EAAE,CAAC;YAChC,QAAQ,CAAC,GAAG,EAAE,sCAAsC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;YAClF,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,CAAC;YACH,wBAAwB;YACxB,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,WAAW,CAAC,CAAC;YACvD,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;YACxC,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;YAE9D,WAAW,CAAC,GAAG,EAAE,UAAU,UAAU,WAAW,KAAK,CAAC,MAAM,mBAAmB,CAAC,CAAC;YAEjF,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;gBAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC;gBACxC,OAAO;oBACL,IAAI,EAAE,MAAM,CAAC,SAAS;oBACtB,SAAS,EAAE,MAAM,CAAC,UAAU;oBAC5B,OAAO,EAAE,MAAM,CAAC,QAAQ;oBACxB,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG;oBAC1C,gBAAgB,EAAE,uBAAuB,CAAC,IAAI,CAAC;oBAC/C,YAAY,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;iBAC/B,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,OAAO,EAAE,CAAC;gBAChB,QAAQ,CAAC,GAAG,EAAE,qCAAqC,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SARIF-based target discovery.
|
|
3
|
+
*
|
|
4
|
+
* Reads an external SARIF file (e.g. from another SAST tool) and
|
|
5
|
+
* returns findings as targets for AI analysis.
|
|
6
|
+
*/
|
|
7
|
+
import type { TargetDiscovery } from '../discovery.js';
|
|
8
|
+
export declare const sarifDiscovery: TargetDiscovery;
|
|
9
|
+
//# sourceMappingURL=sarif-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif-discovery.d.ts","sourceRoot":"","sources":["../../src/discoveries/sarif-discovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,OAAO,KAAK,EAAE,eAAe,EAAsC,MAAM,iBAAiB,CAAC;AA0B3F,eAAO,MAAM,cAAc,EAAE,eA8C5B,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SARIF-based target discovery.
|
|
3
|
+
*
|
|
4
|
+
* Reads an external SARIF file (e.g. from another SAST tool) and
|
|
5
|
+
* returns findings as targets for AI analysis.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile } from 'node:fs/promises';
|
|
8
|
+
import { resolve } from 'node:path';
|
|
9
|
+
import { parseSARIF, deduplicateTargets } from '../sarif-parser.js';
|
|
10
|
+
import { logDebug } from '../logging.js';
|
|
11
|
+
import { ERROR_CODES, formatError } from '../error-codes.js';
|
|
12
|
+
import { DEFAULT_GENERIC_PROMPT } from '../defaults.js';
|
|
13
|
+
const TAG = 'sarif-discovery';
|
|
14
|
+
function buildFindingPromptEnrichment(file, startLine, endLine, message, snippet) {
|
|
15
|
+
const snippetSection = snippet ? `\n- Code snippet from tool: ${snippet}` : '';
|
|
16
|
+
return `\n\nFINDING DETAILS:
|
|
17
|
+
|
|
18
|
+
- File: ${file}
|
|
19
|
+
- Lines: ${startLine}-${endLine}
|
|
20
|
+
- Tool's finding description: ${message}${snippetSection}
|
|
21
|
+
|
|
22
|
+
You MUST:
|
|
23
|
+
- Analyze ONLY this specific finding — do not search for or report issues at other locations
|
|
24
|
+
- You may read other files to understand context (e.g., imports, type definitions, data flow), but only report issues for this finding
|
|
25
|
+
- Do NOT scan the broader repository for other vulnerability patterns
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
export const sarifDiscovery = {
|
|
29
|
+
name: 'sarif',
|
|
30
|
+
defaultGenericPrompt: DEFAULT_GENERIC_PROMPT,
|
|
31
|
+
needsInstructions: true,
|
|
32
|
+
supportsDiffFilter: true,
|
|
33
|
+
async discover(check, repoPath, _options) {
|
|
34
|
+
const checkTarget = check.checkTarget;
|
|
35
|
+
if (!checkTarget.sarifFile) {
|
|
36
|
+
throw new Error(formatError(ERROR_CODES.E2004, `Check "${check.id}" uses sarif discovery but has no "sarifFile" in its check definition`));
|
|
37
|
+
}
|
|
38
|
+
// Resolve sarifFile relative to the target repo
|
|
39
|
+
const sarifFilePath = resolve(repoPath, checkTarget.sarifFile);
|
|
40
|
+
logDebug(TAG, `Reading SARIF file: ${sarifFilePath}`);
|
|
41
|
+
const sarifContent = await readFile(sarifFilePath, 'utf-8');
|
|
42
|
+
let targets = parseSARIF(sarifContent);
|
|
43
|
+
targets = deduplicateTargets(targets);
|
|
44
|
+
logDebug(TAG, `Discovered ${targets.length} findings`);
|
|
45
|
+
return targets.map((target, idx) => ({
|
|
46
|
+
file: target.file,
|
|
47
|
+
startLine: target.startLine,
|
|
48
|
+
endLine: target.endLine,
|
|
49
|
+
label: `[finding ${idx + 1}/${targets.length}]`,
|
|
50
|
+
message: target.message,
|
|
51
|
+
snippet: target.snippet,
|
|
52
|
+
promptEnrichment: buildFindingPromptEnrichment(target.file, target.startLine, target.endLine, target.message, target.snippet),
|
|
53
|
+
}));
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=sarif-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif-discovery.js","sourceRoot":"","sources":["../../src/discoveries/sarif-discovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAIxD,MAAM,GAAG,GAAG,iBAAiB,CAAC;AAE9B,SAAS,4BAA4B,CACnC,IAAY,EACZ,SAAiB,EACjB,OAAe,EACf,OAAe,EACf,OAAgB;IAEhB,MAAM,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,+BAA+B,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,OAAO;;UAEC,IAAI;WACH,SAAS,IAAI,OAAO;gCACC,OAAO,GAAG,cAAc;;;;;;CAMvD,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAoB;IAC7C,IAAI,EAAE,OAAO;IACb,oBAAoB,EAAE,sBAAsB;IAC5C,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,IAAI;IAExB,KAAK,CAAC,QAAQ,CACZ,KAAoB,EACpB,QAAgB,EAChB,QAA2B;QAE3B,MAAM,WAAW,GAAG,KAAK,CAAC,WAAY,CAAC;QAEvC,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,WAAW,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,KAAK,CAAC,EAAE,uEAAuE,CAAC,CAC1H,CAAC;QACJ,CAAC;QAED,gDAAgD;QAChD,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;QAE/D,QAAQ,CAAC,GAAG,EAAE,uBAAuB,aAAa,EAAE,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAE5D,IAAI,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;QACvC,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAEtC,QAAQ,CAAC,GAAG,EAAE,cAAc,OAAO,CAAC,MAAM,WAAW,CAAC,CAAC;QAEvD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG;YAC/C,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,gBAAgB,EAAE,4BAA4B,CAC5C,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,OAAO,CACf;SACF,CAAC,CAAC,CAAC;IACN,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semgrep-based target discovery.
|
|
3
|
+
*
|
|
4
|
+
* Runs Semgrep rules against the repository, parses SARIF output,
|
|
5
|
+
* and returns discovered targets with inline prompt enrichment.
|
|
6
|
+
*/
|
|
7
|
+
import type { TargetDiscovery } from '../discovery.js';
|
|
8
|
+
export declare const semgrepDiscovery: TargetDiscovery;
|
|
9
|
+
//# sourceMappingURL=semgrep-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep-discovery.d.ts","sourceRoot":"","sources":["../../src/discoveries/semgrep-discovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAsC,MAAM,iBAAiB,CAAC;AA4B3F,eAAO,MAAM,gBAAgB,EAAE,eAoC9B,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semgrep-based target discovery.
|
|
3
|
+
*
|
|
4
|
+
* Runs Semgrep rules against the repository, parses SARIF output,
|
|
5
|
+
* and returns discovered targets with inline prompt enrichment.
|
|
6
|
+
*/
|
|
7
|
+
import { runSemgrep } from '../semgrep-runner.js';
|
|
8
|
+
import { parseSARIF, deduplicateTargets } from '../sarif-parser.js';
|
|
9
|
+
import { logDebug } from '../logging.js';
|
|
10
|
+
import { DEFAULT_GENERIC_PROMPT } from '../defaults.js';
|
|
11
|
+
const TAG = 'semgrep-discovery';
|
|
12
|
+
function buildTargetPromptEnrichment(file, startLine, endLine) {
|
|
13
|
+
return `\n\nTARGET LOCATION:
|
|
14
|
+
|
|
15
|
+
You are analyzing a specific code location:
|
|
16
|
+
- File: ${file}
|
|
17
|
+
- Lines: ${startLine}-${endLine}
|
|
18
|
+
|
|
19
|
+
You MUST:
|
|
20
|
+
- Analyze ONLY this specific target location — do not search for or report issues at other locations
|
|
21
|
+
- You may read other files to understand context (e.g., imports, type definitions, data flow), but only report issues for this target
|
|
22
|
+
- If the code at this location is not vulnerable, return {"issues": []} — do not "spend" the target by reporting an issue you noticed somewhere else in the file
|
|
23
|
+
- Do NOT scan the broader repository for other instances of this vulnerability pattern
|
|
24
|
+
|
|
25
|
+
REPORTING RULES (strict — these prevent cross-target hallucinations):
|
|
26
|
+
- Each reported issue's "file"/"startLine"/"endLine" MUST point at this target's range above, OR at a line inside a function this target directly/transitively calls. They must NOT point at a sibling location (e.g. another function, another route handler, another class) that simply happens to live in the same file. If that sibling is genuinely vulnerable, it has its own target run — leave it alone here.
|
|
27
|
+
|
|
28
|
+
DESCRIPTION OPENING (mandatory output contract):
|
|
29
|
+
- Your description's first heading MUST identify the entry point this target represents — its function name, route path+verb, class method, or equivalent — exactly as it appears in the source. For example: a route handler → "## Missing X in DELETE /:id"; a function → "## Missing X in processPayment".
|
|
30
|
+
- The rest of the description must then describe THAT specific symbol's vulnerability — not a sibling's. If, while writing, you find yourself describing a different route/function than the one you opened with, stop: you are hallucinating across targets. Discard the issue and return {"issues": []}.
|
|
31
|
+
- If you noticed a real vulnerability while reading the file but it is in a sibling location, do NOT smuggle it into this target's report. That sibling has its own target run.
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
export const semgrepDiscovery = {
|
|
35
|
+
name: 'semgrep',
|
|
36
|
+
defaultGenericPrompt: DEFAULT_GENERIC_PROMPT,
|
|
37
|
+
needsInstructions: true,
|
|
38
|
+
supportsDiffFilter: true,
|
|
39
|
+
async discover(check, repoPath, _options) {
|
|
40
|
+
const checkTarget = check.checkTarget;
|
|
41
|
+
logDebug(TAG, `Running Semgrep for check: ${check.id}`);
|
|
42
|
+
const sarifContent = await runSemgrep({
|
|
43
|
+
repositoryPath: repoPath,
|
|
44
|
+
rules: checkTarget.rules,
|
|
45
|
+
config: checkTarget.config,
|
|
46
|
+
});
|
|
47
|
+
let targets = parseSARIF(sarifContent);
|
|
48
|
+
targets = deduplicateTargets(targets);
|
|
49
|
+
logDebug(TAG, `Discovered ${targets.length} targets`);
|
|
50
|
+
return targets.map((target, idx) => ({
|
|
51
|
+
file: target.file,
|
|
52
|
+
startLine: target.startLine,
|
|
53
|
+
endLine: target.endLine,
|
|
54
|
+
label: `[target ${idx + 1}/${targets.length}]`,
|
|
55
|
+
message: target.message,
|
|
56
|
+
snippet: target.snippet,
|
|
57
|
+
promptEnrichment: buildTargetPromptEnrichment(target.file, target.startLine, target.endLine),
|
|
58
|
+
}));
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=semgrep-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep-discovery.js","sourceRoot":"","sources":["../../src/discoveries/semgrep-discovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAIxD,MAAM,GAAG,GAAG,mBAAmB,CAAC;AAEhC,SAAS,2BAA2B,CAAC,IAAY,EAAE,SAAiB,EAAE,OAAe;IACnF,OAAO;;;UAGC,IAAI;WACH,SAAS,IAAI,OAAO;;;;;;;;;;;;;;;CAe9B,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,IAAI,EAAE,SAAS;IACf,oBAAoB,EAAE,sBAAsB;IAC5C,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,IAAI;IAExB,KAAK,CAAC,QAAQ,CACZ,KAAoB,EACpB,QAAgB,EAChB,QAA2B;QAE3B,MAAM,WAAW,GAAG,KAAK,CAAC,WAAY,CAAC;QAEvC,QAAQ,CAAC,GAAG,EAAE,8BAA8B,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAExD,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC;YACpC,cAAc,EAAE,QAAQ;YACxB,KAAK,EAAE,WAAW,CAAC,KAAK;YACxB,MAAM,EAAE,WAAW,CAAC,MAAM;SAC3B,CAAC,CAAC;QAEH,IAAI,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;QACvC,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAEtC,QAAQ,CAAC,GAAG,EAAE,cAAc,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;QAEtD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,WAAW,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG;YAC9C,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,gBAAgB,EAAE,2BAA2B,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC;SAC7F,CAAC,CAAC,CAAC;IACN,CAAC;CACF,CAAC"}
|