Summary
The Analyst agent is instructed to return approximately fileLimit source files, but this is a soft hint in the LLM prompt — not an enforced limit. validateAnalysis() validates only that the returned value is a non-empty string[] with no upper bound check. A model misalignment, prompt injection, or large repository can cause the analyst to return thousands of files. This exhausts browser memory (rendering a huge list), crashes the React UI, and burns through API credits running thousands of concurrent scan agents.
Panel verdict: P2, VERIFIED, EXISTING_DEFECT (Completeness Audit finding G-03).
Affected Files
| File |
Lines |
Issue |
src/scanner.ts |
L122–L129 |
validateAnalysis() has no upper bound on array length |
src/server/routes.ts |
L60 |
MAX_PARALLEL = 16 caps concurrency but not total file count |
Root Cause — Code Evidence
validateAnalysis() — no length cap (src/scanner.ts, lines 122–129):
export function validateAnalysis(json: string): string[] {
const parsed = JSON.parse(json);
if (!parsed || typeof parsed !== 'object') throw new Error('not an object');
const files = (parsed as { files?: unknown }).files;
if (!Array.isArray(files) || !files.every(f => typeof f === 'string' && f.length > 0)) {
throw new Error('.files must be a non-empty string[]');
}
return files as string[]; // ← may be 10,000 files — no cap applied
}
Analyst prompt uses fileLimit as a soft hint:
function analystPrompt(repoPath: string, outputPath: string, fileLimit: number): string {
// ...
return `...Identify ~${fileLimit} source files...`; // ← '~' makes it approximate
// LLMs may return 5x or 10x this limit on large monorepos
}
Impact
- OOM crash: 10,000+ file paths sent to the React UI as a single JSON payload can exhaust browser memory.
- Credit burn: At
parallel=16, 10,000 files = ~625 API batches. At ~$0.01/scan, an accidental monorepo analysis costs $6.25 without warning.
- No rate limiting: Concurrent agent lanes lack HTTP 429 backoff budgets (see also: G-04), so rapid API exhaustion causes unrecoverable scan failure.
Remediation
Step 1 — Enforce a hard cap in validateAnalysis()
const MAX_ANALYST_FILES = 500; // sensible default for a security scanner
export function validateAnalysis(json: string, maxFiles = MAX_ANALYST_FILES): string[] {
const parsed = JSON.parse(json);
if (!parsed || typeof parsed !== 'object') throw new Error('not an object');
const files = (parsed as { files?: unknown }).files;
if (!Array.isArray(files) || !files.every(f => typeof f === 'string' && f.length > 0)) {
throw new Error('.files must be a non-empty string[]');
}
if (files.length > maxFiles) {
console.warn(`[analyst] Truncating ${files.length} files to ${maxFiles}`);
return (files as string[]).slice(0, maxFiles);
}
return files as string[];
}
Step 2 — Expose fileLimit cap as a user-configurable option
In src/server/routes.ts, add a fileLimit parameter to the POST /api/scans body with a maximum cap:
const MAX_FILE_LIMIT = 500;
const fileLimit = Math.min(
Number.isFinite(body.fileLimit) ? Math.floor(body.fileLimit as number) : 50,
MAX_FILE_LIMIT
);
Step 3 — Show an estimate before scanning
Before starting a scan, emit a preview event with the analyst's file count so the user can cancel if unexpected:
emit({ type: 'files-preview', count: files.length, capped: files.length > maxFiles });
// Wait for user confirmation if count > some threshold (e.g. > 200)
Summary
The Analyst agent is instructed to return approximately
fileLimitsource files, but this is a soft hint in the LLM prompt — not an enforced limit.validateAnalysis()validates only that the returned value is a non-emptystring[]with no upper bound check. A model misalignment, prompt injection, or large repository can cause the analyst to return thousands of files. This exhausts browser memory (rendering a huge list), crashes the React UI, and burns through API credits running thousands of concurrent scan agents.Panel verdict: P2, VERIFIED, EXISTING_DEFECT (Completeness Audit finding G-03).
Affected Files
src/scanner.tsvalidateAnalysis()has no upper bound on array lengthsrc/server/routes.tsMAX_PARALLEL = 16caps concurrency but not total file countRoot Cause — Code Evidence
validateAnalysis()— no length cap (src/scanner.ts, lines 122–129):Analyst prompt uses
fileLimitas a soft hint:Impact
parallel=16, 10,000 files = ~625 API batches. At ~$0.01/scan, an accidental monorepo analysis costs $6.25 without warning.Remediation
Step 1 — Enforce a hard cap in validateAnalysis()
Step 2 — Expose fileLimit cap as a user-configurable option
In
src/server/routes.ts, add afileLimitparameter to thePOST /api/scansbody with a maximum cap:Step 3 — Show an estimate before scanning
Before starting a scan, emit a preview event with the analyst's file count so the user can cancel if unexpected: