Images/photos in reviews are moderated or require explicit approval
Why it matters
Review image uploads with no file type validation, size limits, or approval gate are a CWE-434 (Unrestricted File Upload) and CWE-79 (XSS) exposure: an attacker can upload an SVG with embedded <script> tags or a polyglot file that executes when rendered inline. OWASP A04:2021 (Insecure Design) covers the absence of upload constraints at the design level. Even without malicious intent, a user uploading a 50MB RAW photo file will exhaust serverless function memory and trigger a 500 error for all concurrent visitors. Images served directly from an S3 path with no content-type header enforcement allow MIME-type confusion attacks in older browsers.
Severity rationale
Medium because unvalidated image uploads enable stored XSS via SVG polyglots and file-size denial-of-service without requiring elevated privileges.
Remediation
Enforce file type and size limits at the upload handler in api/reviews/submit, and require admin approval before images are displayed publicly.
// api/reviews/submit — image validation
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp']
const MAX_SIZE_BYTES = 5 * 1024 * 1024 // 5MB
for (const file of uploadedFiles) {
if (!ALLOWED_TYPES.includes(file.type)) {
return Response.json({ error: `File type ${file.type} not allowed` }, { status: 400 })
}
if (file.size > MAX_SIZE_BYTES) {
return Response.json({ error: 'Image exceeds 5MB limit' }, { status: 400 })
}
}
// Store with pending status — never display immediately
await db.reviewImages.create({
data: { review_id: reviewId, file_path: uploadedPath, status: 'pending' }
})
Only query WHERE status = 'approved' when rendering images on the product page. Reject SVG and GIF entirely to eliminate the highest-risk polyglot vectors.
Detection
-
ID:
review-images-moderated -
Severity:
medium -
What to look for: Check whether the review submission form includes a file upload input (type="file", accept="image/*"). If uploads are supported, enumerate all image processing steps: (1) file type validation, (2) file size limit check (under 5MB recommended), (3) virus/malware scan, (4) explicit content detection, (5) manual approval status. Count how many of these 5 safety layers are present.
-
Pass criteria: If image uploads are supported, at least 2 of 5 safety layers are implemented before images are displayed publicly (e.g., file type validation + size limit, or approval status + virus scan). Alternatively, the review form does not support image uploads at all (this is also a pass).
-
Fail criteria: Image uploads are supported but fewer than 2 of 5 safety layers are present — images are displayed immediately after upload with no validation or moderation.
-
Skip (N/A) when: The review submission form does not include any file upload input and no image upload feature exists in the review flow.
-
Detail on fail:
"Image upload supported via file input. 0 of 5 safety layers found. Photos are stored and displayed immediately without validation or moderation." -
Remediation: Add image moderation in the review submission handler at
api/reviews/submitwith a separate images table:// Option 1: Require approval await db.reviewImages.create({ data: { review_id: reviewId, file_path: uploadedPath, status: 'pending' } }) // Only display approved images const images = await db.reviewImages.findMany({ where: { review_id: reviewId, status: 'approved' } }) // Option 2: Use a virus scanning library import { scanFile } from 'some-virus-scan-library' const result = await scanFile(uploadedFile) if (!result.clean) { return { error: 'Image failed security scan' } }
External references
- cwe · CWE-434 — Unrestricted Upload of File with Dangerous Type
- owasp:2021 · A04 — Insecure Design
- cwe · CWE-79 — Improper Neutralization of Input During Web Page Generation (XSS)
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ecommerce-reviews·automated