gotify/server · error
file must be an image
Error message
file must be an image
What it means
After reading the first 261 bytes of the uploaded file, the handler runs filetype.IsImage(head) to sniff the magic bytes. If the content does not match a known image format, it rejects the upload with this 400 error — content sniffing takes precedence over the client-declared filename or MIME type.
Source
Thrown at api/application.go:427
withID(ctx, "id", func(id uint) {
app, err := a.DB.GetApplicationByID(id)
if success := successOrAbort(ctx, 500, err); !success {
return
}
if app != nil && app.UserID == auth.GetUserID(ctx) {
file, err := ctx.FormFile("file")
if err == http.ErrMissingFile {
ctx.AbortWithError(400, errors.New("file with key 'file' must be present"))
return
} else if err != nil {
ctx.AbortWithError(500, err)
return
}
head := make([]byte, 261)
open, _ := file.Open()
open.Read(head)
if !filetype.IsImage(head) {
ctx.AbortWithError(400, errors.New("file must be an image"))
return
}
ext := filepath.Ext(file.Filename)
if !ValidApplicationImageExt(ext) {
ctx.AbortWithError(400, errors.New("invalid file extension"))
return
}
name := generateNonExistingImageName(a.ImageDir, func() string {
return generateImageName() + ext
})
err = ctx.SaveUploadedFile(file, a.ImageDir+name)
if err != nil {
ctx.AbortWithError(500, err)
return
}View on GitHub (pinned to 14bfc25627)
Solutions
- Upload an actual image file in a supported format (png, jpg, gif, webp, etc.)
- Do not rely on renaming the extension — the content must genuinely be image bytes
- Re-export/convert the file to PNG or JPEG if using an exotic format the sniffer rejects
- If it is a valid image that fails, check the file is not truncated/empty and that the client is not sending only a partial body
Example fix
// before
fd.append('file', new Blob(['not an image'], {type:'image/png'}));
// after
fd.append('file', file, 'photo.png'); // real image bytes from input[type=file] Defensive patterns
Strategy: validation
Validate before calling
const isImage = (f) => f.type.startsWith('image/') && f.size > 0 && !f.name.endsWith('.svg');
if (!isImage(file)) throw new Error('upload must be a real image file'); Type guard
function isUploadableImage(f) {
return f instanceof File && f.size > 0 && ['image/png','image/jpeg','image/gif','image/webp'].includes(f.type);
} Prevention
- Validate MIME type and magic bytes client-side before uploading
- Never rename non-image files to image extensions expecting the server to accept them
- Check the server's supported formats (filetype lib) — avoid svg/heic unless confirmed
- Reject empty (0-byte) files before upload
When it happens
Trigger: Uploading a file whose actual bytes are not an image (PDF, text, zip, executable) to the image-upload endpoint; also occurs when the file is smaller than the sniffed header or corrupted so magic bytes are unreadable.
Common situations: Renaming evil.txt to evil.png and uploading; uploading SVG/HEIC/other formats not recognized by the filetype library; empty or truncated files; build artifacts accidentally attached.
Related errors
- invalid file extension
- file with key 'file' must be present
- cannot delete internal application
- sort key is not unique
- appid is required when not authenticating with an applicatio
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/5d49575346d07ca1.
Report an issue: GitHub.