fix(llm-security): scanner robustness — ReDoS, MCP-stdout DoS, redirect loop, atomic writes (#24,#53,#31,#25,#51)

#24 the HTML-obfuscation injection patterns had overlapping unbounded runs plus a required closing quote, so a non-closing input backtracked O(N^2) (~28.7s at the 512KB cap); quantifiers bounded, pathological input now 4ms. #53 mcp-live-inspect buffered MCP-server stdout via readline with no cap, so a hostile stdio server could exhaust memory / throw an uncaught RangeError; replaced with manual line buffering capped at 4MB that rejects pending RPCs and destroys stdout. #31 vsix-fetch's same-host redirect follower had no depth cap (loop hang); added depth>=5 cap mirroring the sibling fetcher.

#25/#51 mcp-description-cache and skill-registry wrote JSON via bare writeFileSync (non-atomic: concurrent load-modify-save loses updates, a torn read silently yields an empty registry); both now write a temp file then renameSync. Suite 1931/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TcQyMTQfyrsAapaCMPxTtQ
This commit is contained in:
Kjell Tore Guttormsen 2026-07-18 10:15:11 +02:00
commit 207385fbbe
10 changed files with 251 additions and 19 deletions

View file

@ -22,7 +22,7 @@
//
// OWASP: MCP05 (Tool Description Manipulation / Rug Pull)
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { homedir } from 'node:os';
import { levenshtein } from './string-utils.mjs';
@ -144,7 +144,12 @@ export function saveCache(cache, opts = {}) {
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(cacheFile, JSON.stringify(cache, null, 2), 'utf-8');
// Atomic write (v7.8.3 #25): temp file + rename so a concurrent
// load-modify-save never reads a torn file (which parses as {} and
// silently drops every baseline).
const tmpPath = `${cacheFile}.tmp-${process.pid}-${Date.now()}`;
writeFileSync(tmpPath, JSON.stringify(cache, null, 2), 'utf-8');
renameSync(tmpPath, cacheFile);
} catch {
// Silently fail — drift detection is advisory, not critical
}