mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Escape project names before building hand-test patterns, compare transport identifiers as literal tokens, preserve boundaries when extracting prose and XML facts, and require canonical containment for smoke static assets. Add regression coverage for metacharacters, dollar identifiers, markup joining, traversal, symlinks, and malformed URL encoding. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
40 lines
1.5 KiB
JavaScript
40 lines
1.5 KiB
JavaScript
import { createReadStream, realpathSync, statSync } from 'node:fs';
|
|
import { resolve, extname, sep } from 'node:path';
|
|
|
|
/** Serve the smoke fixture and installed assets only, including after resolving symlinks. */
|
|
export function serveStaticAsset(root, req, res, mime = {}) {
|
|
const notFound = () => {
|
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
res.end('not found');
|
|
};
|
|
let candidate;
|
|
try {
|
|
const urlPath = decodeURIComponent((req.url ?? '/').split('?')[0]);
|
|
candidate = resolve(root, `.${urlPath}`);
|
|
} catch {
|
|
notFound();
|
|
return;
|
|
}
|
|
for (const directory of ['spike', 'node_modules']) {
|
|
const allowedRoot = resolve(root, directory);
|
|
if (!candidate.startsWith(allowedRoot + sep)) continue;
|
|
try {
|
|
const realRoot = realpathSync(allowedRoot);
|
|
const realCandidate = realpathSync(candidate);
|
|
if (!realCandidate.startsWith(realRoot + sep)) continue;
|
|
if (!statSync(realCandidate).isFile()) break;
|
|
const stream = createReadStream(realCandidate);
|
|
stream.once('error', () => {
|
|
if (!res.headersSent) notFound();
|
|
else res.destroy();
|
|
});
|
|
res.writeHead(200, { 'Content-Type': mime[extname(realCandidate)] ?? 'application/octet-stream' });
|
|
stream.pipe(res);
|
|
return;
|
|
} catch {
|
|
break;
|
|
}
|
|
}
|
|
notFound();
|
|
}
|