Description
In internal/handlers/image.go:66, io.ReadAll(file) reads the entire uploaded file into a single in-memory buffer:
fileBytes, err := io.ReadAll(file)
The header.Size check on line 60 relies on the client-supplied Content-Length header value, which can be trivially spoofed — an attacker can set a small Content-Length while streaming gigabytes of data. This creates an OOM denial-of-service vector.
Contrast with Safe Pattern
internal/handlers/import.go:74 correctly uses:
body, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024))
Impact
- A single authenticated request can exhaust server memory
- With multiple concurrent uploads, can crash the service
Suggested Fix
const maxImageBytes = 10 * 1024 * 1024 // 10 MB, matching existing limit logic
limited := io.LimitReader(file, maxImageBytes+1)
fileBytes, err := io.ReadAll(limited)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read file"})
return
}
if int64(len(fileBytes)) > maxImageBytes {
c.JSON(http.StatusBadRequest, gin.H{"error": "file exceeds maximum size"})
return
}
Description
In
internal/handlers/image.go:66,io.ReadAll(file)reads the entire uploaded file into a single in-memory buffer:The
header.Sizecheck on line 60 relies on the client-suppliedContent-Lengthheader value, which can be trivially spoofed — an attacker can set a smallContent-Lengthwhile streaming gigabytes of data. This creates an OOM denial-of-service vector.Contrast with Safe Pattern
internal/handlers/import.go:74correctly uses:Impact
Suggested Fix