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
|
|
@ -4,6 +4,7 @@ import { describe, it } from 'node:test';
|
|||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
detectUrlType,
|
||||
fetchDirectVsix,
|
||||
fetchJetBrainsPlugin,
|
||||
fetchPluginFromUrl,
|
||||
__testing,
|
||||
|
|
@ -301,3 +302,31 @@ describe('fetchPluginFromUrl — routes JetBrains vs VSIX', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #31 (v7.8.3) — httpsFetchSameHost redirect depth cap
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('fetchDirectVsix — same-host redirect loop cap (v7.8.3 #31)', () => {
|
||||
it('terminates a same-host redirect loop with an error instead of looping', { timeout: 5000 }, async () => {
|
||||
const origFetch = globalThis.fetch;
|
||||
let calls = 0;
|
||||
try {
|
||||
// Every hop 302-redirects back to the same URL on the same host.
|
||||
globalThis.fetch = async () => {
|
||||
calls++;
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { location: 'https://example.com/loop.vsix' },
|
||||
});
|
||||
};
|
||||
await assert.rejects(
|
||||
() => fetchDirectVsix('https://example.com/loop.vsix'),
|
||||
/too many redirects/,
|
||||
);
|
||||
assert.ok(calls <= 7, `expected bounded redirect attempts, got ${calls}`);
|
||||
} finally {
|
||||
globalThis.fetch = origFetch;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue