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:
parent
f3aaf5479f
commit
207385fbbe
10 changed files with 251 additions and 19 deletions
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { describe, it, beforeEach } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
||||
import { mkdtempSync, writeFileSync, existsSync, rmSync, chmodSync, readFileSync, readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import {
|
||||
|
|
@ -518,3 +518,28 @@ describe('mcp-description-cache — listBaselines', () => {
|
|||
cleanup(dir);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #25 (v7.8.3) — atomic saveCache (temp file + rename)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('mcp-description-cache — atomic saveCache (v7.8.3 #25)', () => {
|
||||
it('replaces the cache file via temp+rename (write survives a read-only target)', () => {
|
||||
const { dir, cacheFile } = makeTmpCache();
|
||||
saveCache({ 'mcp__a__t': { description: 'one', firstSeen: 1, lastSeen: 1 } }, { cacheFile });
|
||||
|
||||
// A direct writeFileSync to a read-only file throws (and is swallowed),
|
||||
// leaving stale content. An atomic temp+rename replaces the file because
|
||||
// only directory write permission is required.
|
||||
chmodSync(cacheFile, 0o444);
|
||||
saveCache({ 'mcp__a__t': { description: 'two', firstSeen: 1, lastSeen: 2 } }, { cacheFile });
|
||||
|
||||
const raw = JSON.parse(readFileSync(cacheFile, 'utf-8'));
|
||||
assert.equal(raw['mcp__a__t'].description, 'two',
|
||||
'expected atomic rename to replace the file content');
|
||||
|
||||
const leftovers = readdirSync(dir).filter((f) => f !== 'mcp-descriptions.json');
|
||||
assert.deepEqual(leftovers, [], 'no temp files left behind');
|
||||
cleanup(dir);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue