🛡️ Sentinel: Fix Stored XSS via Content-Type spoofing in asset proxies (#237)
Sanitized the Content-Type header on image and video proxy routes (`/api/image/*`, `/api/video/*`). The content type is forced to lowercase, validated against a safe prefix (`image/`, `video/`, `audio/`), and restricted to block `svg` and `xml`. Any invalid type falls back to `application/octet-stream`. This prevents an attacker from uploading an SVG file containing a malicious script and triggering a Stored XSS attack via these proxy endpoints, which are excluded from the global CSP. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
@@ -81,10 +81,19 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
return NextResponse.json({ error: 'Asset not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
let contentType = result.contentType.toLowerCase();
|
||||
if (contentType.includes('application/octet-stream')) {
|
||||
contentType = 'image/jpeg';
|
||||
} else if (
|
||||
!contentType.startsWith('image/') ||
|
||||
contentType.includes('svg') ||
|
||||
contentType.includes('xml')
|
||||
) {
|
||||
contentType = 'application/octet-stream';
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': result.contentType.includes('application/octet-stream')
|
||||
? 'image/jpeg'
|
||||
: result.contentType,
|
||||
'Content-Type': contentType,
|
||||
// Images are immutable once uploaded to Immich — cache aggressively
|
||||
'Cache-Control': 'public, max-age=31536000, immutable',
|
||||
ETag: etag,
|
||||
|
||||
@@ -44,8 +44,13 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
return NextResponse.json({ error: 'Asset not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
let contentType = result.contentType.toLowerCase();
|
||||
if (!contentType.startsWith('video/') && !contentType.startsWith('audio/')) {
|
||||
contentType = 'application/octet-stream';
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': result.contentType,
|
||||
'Content-Type': contentType,
|
||||
// Videos are immutable once uploaded — cache aggressively
|
||||
'Cache-Control': 'public, max-age=31536000, immutable',
|
||||
'Accept-Ranges': 'bytes',
|
||||
|
||||
Reference in New Issue
Block a user