@kb-labs/review-llm 0.5.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/README.md +23 -0
- package/dist/analyzers/architecture-analyzer.d.ts +22 -0
- package/dist/analyzers/architecture-analyzer.js +226 -0
- package/dist/analyzers/architecture-analyzer.js.map +1 -0
- package/dist/analyzers/naming-analyzer.d.ts +22 -0
- package/dist/analyzers/naming-analyzer.js +200 -0
- package/dist/analyzers/naming-analyzer.js.map +1 -0
- package/dist/analyzers/security-analyzer.d.ts +35 -0
- package/dist/analyzers/security-analyzer.js +261 -0
- package/dist/analyzers/security-analyzer.js.map +1 -0
- package/dist/index.d.ts +558 -0
- package/dist/index.js +2020 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { BaseLLMAnalyzer } from '@kb-labs/review-contracts';
|
|
2
|
+
import { useCache, useLogger, useAnalytics, useLLM } from '@kb-labs/sdk';
|
|
3
|
+
|
|
4
|
+
// src/analyzers/security-analyzer.ts
|
|
5
|
+
|
|
6
|
+
// src/prompts/security.prompts.ts
|
|
7
|
+
function buildSystemPrompt(context) {
|
|
8
|
+
const sections = [
|
|
9
|
+
"You are an expert security auditor specializing in application security.",
|
|
10
|
+
"",
|
|
11
|
+
"**Your role:**",
|
|
12
|
+
"- Identify security vulnerabilities and weaknesses",
|
|
13
|
+
"- Check for common security anti-patterns (OWASP Top 10)",
|
|
14
|
+
"- Suggest secure coding practices with clear rationale",
|
|
15
|
+
"",
|
|
16
|
+
"**CRITICAL RULES:**",
|
|
17
|
+
"1. ONLY report security issues that exist in the provided code",
|
|
18
|
+
"2. Reference line numbers that actually exist in the file",
|
|
19
|
+
"3. Quote exact code snippets when referencing issues",
|
|
20
|
+
"4. Classify severity accurately (error = exploitable, warning = weakness)",
|
|
21
|
+
"5. Provide concrete, actionable mitigation steps",
|
|
22
|
+
"",
|
|
23
|
+
"**Focus areas:**",
|
|
24
|
+
"- SQL injection, XSS, CSRF",
|
|
25
|
+
"- Authentication/authorization bypass",
|
|
26
|
+
"- Insecure cryptography",
|
|
27
|
+
"- Secrets in code",
|
|
28
|
+
"- Command injection",
|
|
29
|
+
"- Path traversal",
|
|
30
|
+
"- Unsafe deserialization",
|
|
31
|
+
""
|
|
32
|
+
];
|
|
33
|
+
for (const [category, content] of Object.entries(context.conventions)) {
|
|
34
|
+
sections.push(`**Project Conventions (${category}):**`);
|
|
35
|
+
sections.push(content);
|
|
36
|
+
sections.push("");
|
|
37
|
+
}
|
|
38
|
+
if (context.taskContext) {
|
|
39
|
+
sections.push("**Current Task Context:**");
|
|
40
|
+
sections.push(context.taskContext);
|
|
41
|
+
sections.push("");
|
|
42
|
+
sections.push("IMPORTANT: Pay special attention to security implications in the context of this task.");
|
|
43
|
+
sections.push("");
|
|
44
|
+
}
|
|
45
|
+
if (context.repoScope && context.repoScope.length > 0) {
|
|
46
|
+
sections.push("**Repositories in Scope:**");
|
|
47
|
+
sections.push(context.repoScope.map((r) => `- ${r}`).join("\n"));
|
|
48
|
+
sections.push("");
|
|
49
|
+
sections.push("NOTE: Check for security issues that may arise from cross-repo interactions.");
|
|
50
|
+
sections.push("");
|
|
51
|
+
}
|
|
52
|
+
sections.push("Use the report_security_finding tool to report vulnerabilities.");
|
|
53
|
+
return sections.join("\n");
|
|
54
|
+
}
|
|
55
|
+
function analyzeFile(file) {
|
|
56
|
+
const lines = file.content.split("\n");
|
|
57
|
+
const numbered = lines.map((line, idx) => `${idx + 1}: ${line}`).join("\n");
|
|
58
|
+
return `Review this file for security vulnerabilities:
|
|
59
|
+
|
|
60
|
+
File: ${file.path}
|
|
61
|
+
Language: ${file.language}
|
|
62
|
+
|
|
63
|
+
\`\`\`${file.language}
|
|
64
|
+
${numbered}
|
|
65
|
+
\`\`\`
|
|
66
|
+
|
|
67
|
+
Report any security vulnerabilities or weaknesses. Focus on exploitable issues.`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/analyzers/security-analyzer.ts
|
|
71
|
+
var SECURITY_FINDING_TOOL = {
|
|
72
|
+
name: "report_security_finding",
|
|
73
|
+
description: "Report a security vulnerability or weakness",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
severity: {
|
|
78
|
+
type: "string",
|
|
79
|
+
enum: ["blocker", "high", "medium", "low"],
|
|
80
|
+
description: "Severity level (blocker=exploitable, high=serious weakness)"
|
|
81
|
+
},
|
|
82
|
+
vulnerabilityType: {
|
|
83
|
+
type: "string",
|
|
84
|
+
description: "Type of vulnerability (e.g., SQL injection, XSS, CSRF)"
|
|
85
|
+
},
|
|
86
|
+
message: {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "Clear description of the security issue"
|
|
89
|
+
},
|
|
90
|
+
line: {
|
|
91
|
+
type: "number",
|
|
92
|
+
description: "Line number where vulnerability occurs"
|
|
93
|
+
},
|
|
94
|
+
mitigation: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description: "Concrete mitigation steps with code example"
|
|
97
|
+
},
|
|
98
|
+
impact: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "Potential impact if exploited"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
required: ["severity", "vulnerabilityType", "message", "line", "mitigation", "impact"]
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var VALID_SEVERITIES = ["blocker", "high", "medium", "low"];
|
|
107
|
+
var MAX_RETRIES = 3;
|
|
108
|
+
var INITIAL_DELAY_MS = 1e3;
|
|
109
|
+
var BATCH_SIZE = 5;
|
|
110
|
+
function sleep(ms) {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
setTimeout(resolve, ms);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
var SecurityAnalyzer = class extends BaseLLMAnalyzer {
|
|
116
|
+
id = "security";
|
|
117
|
+
name = "Security Analysis";
|
|
118
|
+
/** Track failed files for reporting */
|
|
119
|
+
failedFiles = [];
|
|
120
|
+
async analyze(files, context) {
|
|
121
|
+
const findings = [];
|
|
122
|
+
const cache = useCache();
|
|
123
|
+
this.failedFiles = [];
|
|
124
|
+
const filesToAnalyze = [];
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
const cacheKey = this.generateCacheKey(file, context.preset);
|
|
127
|
+
if (cache) {
|
|
128
|
+
const cached = await cache.get(cacheKey);
|
|
129
|
+
if (cached) {
|
|
130
|
+
findings.push(...cached);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
filesToAnalyze.push(file);
|
|
135
|
+
}
|
|
136
|
+
for (let i = 0; i < filesToAnalyze.length; i += BATCH_SIZE) {
|
|
137
|
+
const batch = filesToAnalyze.slice(i, i + BATCH_SIZE);
|
|
138
|
+
const batchResults = await Promise.allSettled(
|
|
139
|
+
batch.map((file) => this.analyzeFile(file, context, cache))
|
|
140
|
+
);
|
|
141
|
+
for (let j = 0; j < batchResults.length; j++) {
|
|
142
|
+
const result = batchResults[j];
|
|
143
|
+
const file = batch[j];
|
|
144
|
+
if (result.status === "fulfilled") {
|
|
145
|
+
findings.push(...result.value);
|
|
146
|
+
} else {
|
|
147
|
+
this.failedFiles.push({ file: file.path, error: result.reason?.message ?? "Unknown error" });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (this.failedFiles.length > 0) {
|
|
152
|
+
useLogger()?.debug(`[SecurityAnalyzer] Failed to analyze ${this.failedFiles.length} file(s):`, {
|
|
153
|
+
files: this.failedFiles
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return findings;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Analyze a single file with retry logic
|
|
160
|
+
*/
|
|
161
|
+
async analyzeFile(file, context, cache) {
|
|
162
|
+
const analytics = useAnalytics();
|
|
163
|
+
const systemPrompt = buildSystemPrompt(context);
|
|
164
|
+
const userPrompt = analyzeFile(file);
|
|
165
|
+
const llm = useLLM({ tier: "medium" });
|
|
166
|
+
if (!llm?.chatWithTools) {
|
|
167
|
+
throw new Error("LLM not configured or does not support tool calling");
|
|
168
|
+
}
|
|
169
|
+
let lastError = null;
|
|
170
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
171
|
+
try {
|
|
172
|
+
const response = await llm.chatWithTools(
|
|
173
|
+
[
|
|
174
|
+
{ role: "system", content: systemPrompt },
|
|
175
|
+
{ role: "user", content: userPrompt }
|
|
176
|
+
],
|
|
177
|
+
{
|
|
178
|
+
tools: [SECURITY_FINDING_TOOL],
|
|
179
|
+
temperature: 0.1
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
if (analytics) {
|
|
183
|
+
await analytics.track("review.security.complete", {
|
|
184
|
+
file: file.path,
|
|
185
|
+
tokensUsed: response.usage.promptTokens + response.usage.completionTokens,
|
|
186
|
+
toolCalls: response.toolCalls?.length ?? 0,
|
|
187
|
+
attempts: attempt
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const fileFindings = this.processToolCalls(response.toolCalls || [], file);
|
|
191
|
+
if (cache) {
|
|
192
|
+
const cacheKey = this.generateCacheKey(file, context.preset);
|
|
193
|
+
await cache.set(cacheKey, fileFindings, 864e5);
|
|
194
|
+
}
|
|
195
|
+
return fileFindings;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
198
|
+
useLogger()?.debug(`[SecurityAnalyzer] Attempt ${attempt}/${MAX_RETRIES} failed for ${file.path}:`, { error });
|
|
199
|
+
if (attempt < MAX_RETRIES) {
|
|
200
|
+
const delay = INITIAL_DELAY_MS * Math.pow(2, attempt - 1);
|
|
201
|
+
await sleep(delay);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
throw lastError ?? new Error("Analysis failed");
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get list of files that failed analysis
|
|
209
|
+
*/
|
|
210
|
+
getFailedFiles() {
|
|
211
|
+
return [...this.failedFiles];
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Process LLM tool calls into ReviewFindings
|
|
215
|
+
*/
|
|
216
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- Complex validation and mapping of LLM tool call results to structured findings
|
|
217
|
+
processToolCalls(toolCalls, file) {
|
|
218
|
+
const findings = [];
|
|
219
|
+
for (const call of toolCalls) {
|
|
220
|
+
if (call.name === "report_security_finding") {
|
|
221
|
+
const args = call.input;
|
|
222
|
+
const lineCount = file.content.split("\n").length;
|
|
223
|
+
const line = typeof args.line === "number" ? args.line : parseInt(args.line, 10);
|
|
224
|
+
if (isNaN(line) || line < 1 || line > lineCount) {
|
|
225
|
+
useLogger()?.debug(`[SecurityAnalyzer] Invalid line number ${args.line} for ${file.path}`);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const severity = args.severity;
|
|
229
|
+
if (!VALID_SEVERITIES.includes(severity)) {
|
|
230
|
+
useLogger()?.debug(`[SecurityAnalyzer] Invalid severity "${severity}" for finding in ${file.path}:${line}`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const vulnerabilityType = String(args.vulnerabilityType || "unknown").replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 50);
|
|
234
|
+
findings.push({
|
|
235
|
+
id: this.buildFindingId(file, line, "sec"),
|
|
236
|
+
ruleId: `llm:security:${vulnerabilityType}`,
|
|
237
|
+
type: "security",
|
|
238
|
+
severity,
|
|
239
|
+
confidence: "likely",
|
|
240
|
+
// LLM findings are "likely", not "certain"
|
|
241
|
+
file: file.path,
|
|
242
|
+
line,
|
|
243
|
+
message: `[${vulnerabilityType}] ${String(args.message || "").slice(0, 500)}`,
|
|
244
|
+
suggestion: args.mitigation ? String(args.mitigation).slice(0, 1e3) : void 0,
|
|
245
|
+
rationale: args.impact ? `Impact: ${String(args.impact).slice(0, 500)}` : void 0,
|
|
246
|
+
engine: "llm",
|
|
247
|
+
source: "llm-security",
|
|
248
|
+
// For agent mode gating
|
|
249
|
+
scope: "local",
|
|
250
|
+
automated: false
|
|
251
|
+
// Security fixes need human review
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return findings;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export { SecurityAnalyzer };
|
|
260
|
+
//# sourceMappingURL=security-analyzer.js.map
|
|
261
|
+
//# sourceMappingURL=security-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/prompts/security.prompts.ts","../../src/analyzers/security-analyzer.ts"],"names":[],"mappings":"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","file":"security-analyzer.js","sourcesContent":["/**\n * @module @kb-labs/review-llm/prompts/security\n * Security review prompts\n */\n\nimport type { ReviewContext, ParsedFile } from '@kb-labs/review-contracts';\n\n/**\n * Build system prompt with context\n */\nexport function buildSystemPrompt(context: ReviewContext): string {\n const sections: string[] = [\n 'You are an expert security auditor specializing in application security.',\n '',\n '**Your role:**',\n '- Identify security vulnerabilities and weaknesses',\n '- Check for common security anti-patterns (OWASP Top 10)',\n '- Suggest secure coding practices with clear rationale',\n '',\n '**CRITICAL RULES:**',\n '1. ONLY report security issues that exist in the provided code',\n '2. Reference line numbers that actually exist in the file',\n '3. Quote exact code snippets when referencing issues',\n '4. Classify severity accurately (error = exploitable, warning = weakness)',\n '5. Provide concrete, actionable mitigation steps',\n '',\n '**Focus areas:**',\n '- SQL injection, XSS, CSRF',\n '- Authentication/authorization bypass',\n '- Insecure cryptography',\n '- Secrets in code',\n '- Command injection',\n '- Path traversal',\n '- Unsafe deserialization',\n '',\n ];\n\n // Add all project conventions\n for (const [category, content] of Object.entries(context.conventions)) {\n sections.push(`**Project Conventions (${category}):**`);\n sections.push(content);\n sections.push('');\n }\n\n // Add task context (if reviewing a specific task)\n if (context.taskContext) {\n sections.push('**Current Task Context:**');\n sections.push(context.taskContext);\n sections.push('');\n sections.push('IMPORTANT: Pay special attention to security implications in the context of this task.');\n sections.push('');\n }\n\n // Add repo scope (if multi-repo review)\n if (context.repoScope && context.repoScope.length > 0) {\n sections.push('**Repositories in Scope:**');\n sections.push(context.repoScope.map((r) => `- ${r}`).join('\\n'));\n sections.push('');\n sections.push('NOTE: Check for security issues that may arise from cross-repo interactions.');\n sections.push('');\n }\n\n sections.push('Use the report_security_finding tool to report vulnerabilities.');\n\n return sections.join('\\n');\n}\n\n/**\n * Build user prompt for file analysis\n */\nexport function analyzeFile(file: ParsedFile): string {\n const lines = file.content.split('\\n');\n const numbered = lines\n .map((line, idx) => `${idx + 1}: ${line}`)\n .join('\\n');\n\n return `Review this file for security vulnerabilities:\\n\\n` +\n `File: ${file.path}\\n` +\n `Language: ${file.language}\\n\\n` +\n `\\`\\`\\`${file.language}\\n${numbered}\\n\\`\\`\\`\\n\\n` +\n `Report any security vulnerabilities or weaknesses. Focus on exploitable issues.`;\n}\n","/**\n * @module @kb-labs/review-llm/analyzers/security-analyzer\n * Security vulnerability analyzer using LLM with tool calling\n */\n\nimport type { ReviewFinding, ParsedFile, ReviewContext } from '@kb-labs/review-contracts';\nimport { BaseLLMAnalyzer } from '@kb-labs/review-contracts';\nimport { useLLM, useCache, useAnalytics, useLogger } from '@kb-labs/sdk';\nimport * as securityPrompts from '../prompts/security.prompts.js';\n\n/**\n * Tool schema for security findings (structured output)\n */\nconst SECURITY_FINDING_TOOL = {\n name: 'report_security_finding',\n description: 'Report a security vulnerability or weakness',\n inputSchema: {\n type: 'object',\n properties: {\n severity: {\n type: 'string',\n enum: ['blocker', 'high', 'medium', 'low'],\n description: 'Severity level (blocker=exploitable, high=serious weakness)',\n },\n vulnerabilityType: {\n type: 'string',\n description: 'Type of vulnerability (e.g., SQL injection, XSS, CSRF)',\n },\n message: {\n type: 'string',\n description: 'Clear description of the security issue',\n },\n line: {\n type: 'number',\n description: 'Line number where vulnerability occurs',\n },\n mitigation: {\n type: 'string',\n description: 'Concrete mitigation steps with code example',\n },\n impact: {\n type: 'string',\n description: 'Potential impact if exploited',\n },\n },\n required: ['severity', 'vulnerabilityType', 'message', 'line', 'mitigation', 'impact'],\n },\n} as const;\n\n/** Valid severity values */\nconst VALID_SEVERITIES = ['blocker', 'high', 'medium', 'low'] as const;\ntype ValidSeverity = (typeof VALID_SEVERITIES)[number];\n\n/** Retry configuration */\nconst MAX_RETRIES = 3;\nconst INITIAL_DELAY_MS = 1000;\n\n/** Batch size for parallel file processing */\nconst BATCH_SIZE = 5;\n\n/**\n * Sleep helper for retry delays\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => { setTimeout(resolve, ms); });\n}\n\n/**\n * Security analyzer\n * Uses LLM with tool calling for structured findings\n */\nexport class SecurityAnalyzer extends BaseLLMAnalyzer {\n readonly id = 'security';\n readonly name = 'Security Analysis';\n\n /** Track failed files for reporting */\n private failedFiles: Array<{ file: string; error: string }> = [];\n\n async analyze(files: ParsedFile[], context: ReviewContext): Promise<ReviewFinding[]> {\n const findings: ReviewFinding[] = [];\n const cache = useCache();\n this.failedFiles = [];\n\n // Separate cached files from files needing analysis\n const filesToAnalyze: ParsedFile[] = [];\n\n for (const file of files) {\n const cacheKey = this.generateCacheKey(file, context.preset);\n\n if (cache) {\n // eslint-disable-next-line no-await-in-loop -- Sequential cache check before batching\n const cached = await cache.get<ReviewFinding[]>(cacheKey);\n if (cached) {\n findings.push(...cached);\n continue;\n }\n }\n\n filesToAnalyze.push(file);\n }\n\n // Process files in batches for controlled parallelism\n for (let i = 0; i < filesToAnalyze.length; i += BATCH_SIZE) {\n const batch = filesToAnalyze.slice(i, i + BATCH_SIZE);\n\n // Process batch in parallel\n // eslint-disable-next-line no-await-in-loop -- Intentional batching to limit concurrency\n const batchResults = await Promise.allSettled(\n batch.map(file => this.analyzeFile(file, context, cache))\n );\n\n // Collect results\n for (let j = 0; j < batchResults.length; j++) {\n const result = batchResults[j]!;\n const file = batch[j]!;\n\n if (result.status === 'fulfilled') {\n findings.push(...result.value);\n } else {\n this.failedFiles.push({ file: file.path, error: result.reason?.message ?? 'Unknown error' });\n }\n }\n }\n\n // Log summary of failures if any\n if (this.failedFiles.length > 0) {\n useLogger()?.debug(`[SecurityAnalyzer] Failed to analyze ${this.failedFiles.length} file(s):`, {\n files: this.failedFiles,\n });\n }\n\n return findings;\n }\n\n /**\n * Analyze a single file with retry logic\n */\n private async analyzeFile(\n file: ParsedFile,\n context: ReviewContext,\n cache: ReturnType<typeof useCache>\n ): Promise<ReviewFinding[]> {\n const analytics = useAnalytics();\n const systemPrompt = securityPrompts.buildSystemPrompt(context);\n const userPrompt = securityPrompts.analyzeFile(file);\n\n const llm = useLLM({ tier: 'medium' });\n if (!llm?.chatWithTools) {\n throw new Error('LLM not configured or does not support tool calling');\n }\n\n // Retry with exponential backoff\n let lastError: Error | null = null;\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n try {\n // eslint-disable-next-line no-await-in-loop -- Retry loop requires sequential attempts\n const response = await llm.chatWithTools(\n [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: userPrompt },\n ],\n {\n tools: [SECURITY_FINDING_TOOL],\n temperature: 0.1,\n }\n );\n\n // Track analytics\n if (analytics) {\n // eslint-disable-next-line no-await-in-loop -- Analytics tracking in retry loop\n await analytics.track('review.security.complete', {\n file: file.path,\n tokensUsed: response.usage.promptTokens + response.usage.completionTokens,\n toolCalls: response.toolCalls?.length ?? 0,\n attempts: attempt,\n });\n }\n\n // Process tool calls\n const fileFindings = this.processToolCalls(response.toolCalls || [], file);\n\n // Cache results (24 hours)\n if (cache) {\n const cacheKey = this.generateCacheKey(file, context.preset);\n // eslint-disable-next-line no-await-in-loop -- Cache storage in retry loop\n await cache.set(cacheKey, fileFindings, 86400000);\n }\n\n return fileFindings;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n useLogger()?.debug(`[SecurityAnalyzer] Attempt ${attempt}/${MAX_RETRIES} failed for ${file.path}:`, { error });\n\n if (attempt < MAX_RETRIES) {\n const delay = INITIAL_DELAY_MS * Math.pow(2, attempt - 1);\n // eslint-disable-next-line no-await-in-loop -- Intentional delay between retry attempts\n await sleep(delay);\n }\n }\n }\n\n throw lastError ?? new Error('Analysis failed');\n }\n\n /**\n * Get list of files that failed analysis\n */\n getFailedFiles(): Array<{ file: string; error: string }> {\n return [...this.failedFiles];\n }\n\n /**\n * Process LLM tool calls into ReviewFindings\n */\n // eslint-disable-next-line sonarjs/cognitive-complexity -- Complex validation and mapping of LLM tool call results to structured findings\n private processToolCalls(toolCalls: any[], file: ParsedFile): ReviewFinding[] {\n const findings: ReviewFinding[] = [];\n\n for (const call of toolCalls) {\n if (call.name === 'report_security_finding') {\n \n const args = call.input as any;\n\n // Validate line number\n const lineCount = file.content.split('\\n').length;\n const line = typeof args.line === 'number' ? args.line : parseInt(args.line, 10);\n if (isNaN(line) || line < 1 || line > lineCount) {\n useLogger()?.debug(`[SecurityAnalyzer] Invalid line number ${args.line} for ${file.path}`);\n continue;\n }\n\n // Validate severity (reject invalid values instead of normalizing)\n const severity = args.severity as string;\n if (!VALID_SEVERITIES.includes(severity as ValidSeverity)) {\n useLogger()?.debug(`[SecurityAnalyzer] Invalid severity \"${severity}\" for finding in ${file.path}:${line}`);\n continue;\n }\n\n // Sanitize vulnerability type (alphanumeric, dash, underscore only)\n const vulnerabilityType = String(args.vulnerabilityType || 'unknown')\n .replace(/[^a-zA-Z0-9_-]/g, '_')\n .slice(0, 50);\n\n findings.push({\n id: this.buildFindingId(file, line, 'sec'),\n ruleId: `llm:security:${vulnerabilityType}`,\n type: 'security',\n severity: severity as ValidSeverity,\n confidence: 'likely', // LLM findings are \"likely\", not \"certain\"\n\n file: file.path,\n line,\n\n message: `[${vulnerabilityType}] ${String(args.message || '').slice(0, 500)}`,\n suggestion: args.mitigation ? String(args.mitigation).slice(0, 1000) : undefined,\n rationale: args.impact ? `Impact: ${String(args.impact).slice(0, 500)}` : undefined,\n\n engine: 'llm',\n source: 'llm-security',\n\n // For agent mode gating\n scope: 'local',\n automated: false, // Security fixes need human review\n });\n }\n }\n\n return findings;\n }\n}\n"]}
|