UploadValidator
File upload validation via magic bytes for Elixir. Detects spoofed file extensions by checking the actual binary signature of uploaded files.
Built for and extracted from Shiko, a veterinary practice management platform.
Why?
Checking file extensions is not enough. A user can rename malware.exe to photo.jpg and your app happily accepts it. This library reads the first bytes of the file and compares them against known signatures (magic bytes) to verify the file is what it claims to be.
Installation
def deps do
[
{:upload_validator, "~> 0.1.0"}
]
endUsage
# Basic validation
UploadValidator.validate("photo.jpg", allowed: :images)
#=> :ok
# With size limit (5MB)
UploadValidator.validate("doc.pdf", allowed: :documents, max_bytes: 5_000_000)
#=> :ok
# Spoofed file (text file renamed to .jpg)
UploadValidator.validate("fake.jpg")
#=> {:error, :mime_type_mismatch}
# Works with Plug.Upload
UploadValidator.validate(upload, allowed: :images, max_bytes: 5_000_000)
#=> :ok
# Detect actual file type (ignores extension)
UploadValidator.detect_type("mystery_file.bin")
#=> {:ok, "image/png"}Presets
| Preset | Types |
|---|---|
:images | JPEG, PNG, GIF, WebP, BMP, TIFF, SVG |
:documents | |
:audio | MP3, WAV, OGG, FLAC, WebM audio |
:video | MP4, WebM, AVI |
:all | Everything above |
Or pass a list of specific MIME types: allowed: ["image/png", "application/pdf"]
Custom file types
UploadValidator.validate("data.csv",
allowed: ["text/csv"],
extra_signatures: %{"text/csv" => []} # empty = skip magic byte check
)Error handling
case UploadValidator.validate(path, allowed: :images) do
:ok -> # proceed
{:error, reason} -> UploadValidator.error_message(reason)
end| Error | Message |
|---|---|
:file_too_large | File exceeds maximum allowed size |
:invalid_file_type | File type not allowed |
:mime_type_mismatch | File content does not match its extension |
:empty_file | File is empty |
:file_not_found | File not found |
License
MIT