Verify web uploads with Magika and a Python API
File extensions and client-supplied MIME types can be spoofed. Google’s open-source Magika classifies files by their bytes. This tutorial runs Magika on a Python server: the browser uploads the complete file to a Flask endpoint and displays its result. Processing does not stay on the user’s device, and identifying a format does not prove that a file is harmless.
Challenges with traditional file type verification
Common identification techniques inspect metadata or file signatures:
- File extensions can be renamed in seconds.
- MIME types come from the client and are frequently wrong.
- Magic numbers work for common formats but struggle with proprietary or polyglot files.
These weak spots enable malicious uploads—evil.exe masquerading as holiday.jpg, for example—and
create a real attack surface for web apps.
Introducing Magika: AI-powered file identification
Magika uses a deep-learning model to identify binary and text
formats. It returns one predicted type and a confidence score. It does not validate every structure
inside a file, detect every polyglot, or scan for malware. This example uses the Python API in
magika==0.6.1, with Python 3.9 or later.
How Magika works
- Extracts features from selected portions of the file. The API below reads the uploaded file into memory first, so it enforces a size limit before classification.
- Feeds that data into a lightweight neural network. It offers different identification modes, with
HIGH_CONFIDENCEas the default. - Returns a label, MIME type, and confidence score. Measure latency on your deployment, including model startup and upload time.
Integrate Magika into a browser application
1. Install Magika
python -m pip install magika==0.6.1 flask==3.1.0
2. Create a minimal verification API (Flask)
from flask import Flask, request, jsonify
from magika import Magika
from werkzeug.exceptions import RequestEntityTooLarge
app = Flask(__name__)
MAX_FILE_BYTES = 5 * 1024 * 1024
app.config['MAX_CONTENT_LENGTH'] = 6 * 1024 * 1024 # Includes multipart overhead
ALLOWED_TYPES = {'pdf', 'jpeg', 'png'}
magika = Magika() # Load the model once at startup
@app.errorhandler(RequestEntityTooLarge)
def request_too_large(error):
return jsonify({'error': 'Upload is too large'}), 413
@app.route('/verify', methods=['POST'])
def verify_file():
if 'file' not in request.files:
return jsonify({'error': 'No file provided'}), 400
content = request.files['file'].read(MAX_FILE_BYTES + 1)
if not content:
return jsonify({'error': 'File is empty'}), 400
if len(content) > MAX_FILE_BYTES:
return jsonify({'error': 'Upload is too large'}), 413
result = magika.identify_bytes(content)
if not result.ok:
return jsonify({'error': 'File analysis failed'}), 500
if result.output.label not in ALLOWED_TYPES:
return jsonify({'error': 'Only PDF, JPEG, and PNG files are allowed'}), 415
return jsonify({
'file_type': result.output.label,
'mime_type': result.output.mime_type,
'description': result.output.description,
'score': result.score,
})
if __name__ == '__main__': # dev only—use Gunicorn in production
app.run()
Save the example as app.py and run python app.py for local development. Serve the frontend
from the same origin as /verify (or configure a development proxy). For production, use a WSGI
server and request timeouts, authentication, and rate limits. This endpoint only analyzes the
submitted bytes; a separate upload or storage endpoint must enforce the same checks itself.
3. Wire up the front-end
Add a file input with id="fileInput" and a result element with id="result", then run this code
after those elements exist:
async function verifyFile(file) {
const formData = new FormData()
formData.append('file', file)
try {
const response = await fetch('/verify', {
method: 'POST',
body: formData,
})
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
}
const result = await response.json()
return result
} catch (error) {
console.error('File verification request failed:', error)
throw error
}
}
document.getElementById('fileInput').addEventListener('change', async (event) => {
const file = event.target.files[0]
if (!file) {
return // No file selected
}
try {
const analysisResult = await verifyFile(file)
document.getElementById('result').textContent =
`Detected: ${analysisResult.file_type} (${(analysisResult.score * 100).toFixed(1)}% confidence)`
} catch (error) {
document.getElementById('result').textContent = 'Verification failed. See console for details.'
}
})
4. Compare results to an allowlist
The API already enforces its allowlist. A matching client-side check can improve feedback, but must never authorize a later upload on its own.
// Assuming 'analysisResult' is available from the previous step
const allowedFileTypes = ['pdf', 'jpeg', 'png'] // Using Magika's labels
if (!allowedFileTypes.includes(analysisResult.file_type)) {
alert(
`File type "${analysisResult.file_type}" is not allowed. Allowed types are: ${allowedFileTypes.join(', ')}.`,
)
// Or throw new Error(`File type ${analysisResult.file_type} is not permitted.`);
}
Magika vs. Traditional methods
| Feature | File extension / MIME | Magic numbers | Magika |
|---|---|---|---|
| Detects spoofed extensions | ❌ | ✅/Partial | ✅ |
| Speed (milliseconds per file) | ✅ | ✅ | ✅ |
| Open-source & maintained | N/A | Some | ✅ |
Practical use cases
Secure upload forms
Validate uploads before saving or passing them to further processing. This check should primarily happen on your server after receiving the file and Magika's analysis.
// Example client-side feedback based on server verification result
// const analysisResult = await verifyFile(file); // from server
if (!['jpeg', 'png', 'pdf'].includes(analysisResult.file_type)) {
// Display error to user: Only images and PDFs allowed
throw new Error('Only images and PDFs allowed')
}
Content moderation
Route files to specialised pipelines based on Magika’s label—images to an AI-moderation service, videos to FFmpeg, documents to OCR, and so on.
Pre-scan for malware
File labels can route executables or scripts for additional scrutiny. A denylist alone is insufficient: even an allowed image or PDF may need malware scanning and format validation.
# Within a server-side classification workflow, after checking result.ok:
risky_file_types = {'pebin', 'elf', 'macho', 'batch', 'shell'}
requires_additional_review = result.output.label in risky_file_types
Error handling & best practices
- Verify on the server. Client-side checks are a convenience, not a robust defence.
- Set request timeouts. Large files or cold-start containers still need limits for your API endpoint.
- Log confidence scores. They help trace edge cases when Magika is unsure or for auditing.
- Update regularly. Each Magika release adds formats and improves accuracy.
- Layer defences. Combine Magika with antivirus software, denylist logic based on other criteria, and rate-limiting for your upload endpoint.
Transloadit alternative
Prefer an off-the-shelf SaaS? Our 🤖 /file/verify Robot performs similar content-based checks in the cloud. A minimal Step looks like:
{
"robot": "/file/verify",
"use": ":original",
"verify_to_be": "pdf",
"error_on_decline": true,
"error_msg": "File type verification failed"
}
Pair it with Uppy for a complete, front-to-back upload pipeline.
Wrap-up
Magika significantly enhances file type verification by inspecting file content rather than trusting metadata. Whether you self-host Magika or use Transloadit’s Robot, content-based checks can reject unexpected formats. Combine them with malware scanning and safe handling of accepted files; classification alone is not a malware verdict.
