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.
Medium because unvalidated image uploads enable stored XSS via SVG polyglots and file-size denial-of-service without requiring elevated privileges.
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.
ID: ecommerce-reviews.moderation-trust.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/submit with 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' }
}