fix: harden deploys and close top security holes after /events outage

Isolate next dev from production .next, add build-guard/atomic deploy/health
watchdog, error boundaries, and fix JWT startup, meetup leaks, media path
traversal, SVG/memory uploads, and JSON-LD escaping.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
bbe
2026-07-18 19:14:57 +02:00
co-authored by Cursor
parent 495289232b
commit ede8817583
22 changed files with 700 additions and 86 deletions
+61 -39
View File
@@ -180,50 +180,72 @@ function handleVideoStream(
export const dynamic = 'force-dynamic';
export const runtime = 'nodejs';
// Media blobs are stored as ULIDs (Crockford base32, 26 chars). Reject anything
// else before joining into the storage path so `../` cannot escape the root.
const MEDIA_ID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
function resolveSafeMediaPath(root: string, id: string): string | null {
if (!MEDIA_ID_RE.test(id)) return null;
const resolvedRoot = path.resolve(root);
const filePath = path.resolve(resolvedRoot, id);
const rootPrefix = resolvedRoot.endsWith(path.sep)
? resolvedRoot
: resolvedRoot + path.sep;
if (filePath !== resolvedRoot && !filePath.startsWith(rootPrefix)) {
return null;
}
return filePath;
}
export async function GET(
request: NextRequest,
context: { params: Promise<{ id: string }> | { id: string } }
) {
const params = await Promise.resolve(context.params);
const id = params.id;
if (!id) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
const root = getMediaStorageRoot();
const filePath = path.join(root, id);
if (!fileExists(filePath)) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
const meta = readMeta(root, id);
if (!meta) {
return NextResponse.json({ error: 'Metadata not found' }, { status: 404 });
}
const { searchParams } = new URL(request.url);
const widthParam = searchParams.get('w');
if (meta.type === 'image' && widthParam) {
const width = parseInt(widthParam, 10);
if (isNaN(width) || width < 1 || width > 4096) {
return NextResponse.json({ error: 'Invalid width' }, { status: 400 });
try {
const params = await Promise.resolve(context.params);
const id = params.id;
if (!id) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return handleImageResize(root, filePath, width, meta, id);
}
if (meta.type === 'video') {
const rangeHeader = request.headers.get('range');
return handleVideoStream(filePath, meta, rangeHeader);
}
const root = getMediaStorageRoot();
const filePath = resolveSafeMediaPath(root, id);
if (!filePath || !fileExists(filePath)) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
const buffer = fs.readFileSync(filePath);
return new NextResponse(new Uint8Array(buffer), {
status: 200,
headers: {
'Content-Type': meta.mimeType,
'Content-Length': String(buffer.length),
...CACHE_HEADERS,
},
});
const meta = readMeta(root, id);
if (!meta) {
return NextResponse.json({ error: 'Metadata not found' }, { status: 404 });
}
const { searchParams } = new URL(request.url);
const widthParam = searchParams.get('w');
if (meta.type === 'image' && widthParam) {
const width = parseInt(widthParam, 10);
if (isNaN(width) || width < 1 || width > 4096) {
return NextResponse.json({ error: 'Invalid width' }, { status: 400 });
}
return await handleImageResize(root, filePath, width, meta, id);
}
if (meta.type === 'video') {
const rangeHeader = request.headers.get('range');
return handleVideoStream(filePath, meta, rangeHeader);
}
const buffer = fs.readFileSync(filePath);
return new NextResponse(new Uint8Array(buffer), {
status: 200,
headers: {
'Content-Type': meta.mimeType,
'Content-Length': String(buffer.length),
...CACHE_HEADERS,
},
});
} catch (err) {
console.error('Media serve error:', err);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}