Web-Uploads mit Magika und einer Python-API verifizieren
Eine Datei namens photo.jpg könnte ein PDF enthalten. Um den Typ eines Uploads zu
prüfen, senden Sie seine Bytes an die
Python-API von Magika und wenden Sie eine Allowlist auf das
zurückgegebene Label an. Diese Anleitung erstellt eine lokale Flask-App, die sowohl die Upload-Seite
als auch deren Klassifizierungsendpunkt bereitstellt. Das Feedback folgt dabei der aktuellen Auswahl.
Der Browser sendet die vollständige Datei an Python. Die Klassifizierung erfolgt auf diesem Server, auch wenn beide Prozesse auf Ihrem Laptop laufen. Ein akzeptierter Typ belegt nicht, dass eine Datei gültig oder frei von Malware ist.
Entscheiden Sie, was das Label aussagen kann
Dateiendungen und clientseitig übermittelte MIME-Typen beschreiben, welchen Typ eine Datei vorgibt zu haben. Inhaltsbasierte Erkennungswerkzeuge untersuchen stattdessen die Bytes. Das Umbenennen einer Datei ändert ihre Eingabe daher nicht. Magika unterscheidet Binär- und Textformate mithilfe eines trainierten Modells; dennoch kann es eine Datei falsch klassifizieren.
Die Python-API stellt
identify_bytes(), ein Erfolgsflag in result.ok und die endgültige
Vorhersage in result.output bereit.
Verwenden Sie result.output.label für die Allowlist. Der Standardmodus
HIGH_CONFIDENCE kann eine Modellvorhersage mit geringer Konfidenz durch ein generisches
Label ersetzen. Dieses Beispiel lehnt solche Labels ebenso ab wie andere Typen außerhalb der
Allowlist.
Magika und signaturbasierte Erkennungswerkzeuge beantworten die Frage nach dem Dateiformat. Weder diese Antwort noch ein hoher Konfidenzwert beweist, dass ein Decoder die gesamte Datei akzeptiert. Auch die bekannten Einschränkungen von Magika schließen eine zuverlässige Erkennung polyglotter Dateien aus: Eine Datei kann sich als mehr als ein Format interpretieren lassen.
Integrieren Sie Magika in eine Browser-Anwendung
Verwenden Sie Linux, Python 3.12 mit venv und pip sowie einen aktuellen
Browser. Das Beispiel legt Magika 0.6.1 und Flask 3.1.0 als Versionen fest; die Installation benötigt
Internetzugang. Nach der Installation lädt Magika sein mitgeliefertes Modell lokal, sodass für die
Klassifizierung kein Cloud-Dienst nötig ist. Die folgenden Befehle verwenden Bash.
1. Installieren Sie Magika
Fügen Sie dies in einem Terminal in dem Verzeichnis ein, in dem Sie ein neues Projekt namens
magika-upload anlegen möchten. Die Befehle verweigern die Wiederverwendung eines
bestehenden Verzeichnisses und stoppen, wenn ein Einrichtungsschritt fehlschlägt. Durch die Klammern
bleibt Ihre Shell im ursprünglichen Verzeichnis.
(
mkdir magika-upload &&
cd magika-upload &&
mkdir static &&
python3.12 -m venv .venv &&
.venv/bin/python -m pip install magika==0.6.1 flask==3.1.0
)
Fahren Sie erst nach erfolgreicher Installation fort. Falls sie fehlschlägt, untersuchen Sie den Fehler und versuchen Sie es in einem neuen Verzeichnis erneut oder beheben Sie die Probleme der unvollständigen Umgebung, bevor Sie fortfahren. Löschen Sie kein bestehendes Projekt, um die Einrichtung erneut auszuführen.
2. Erstellen Sie eine minimale Verifizierungs-API (Flask)
Speichern Sie dies als magika-upload/app.py. Das Modell wird beim Start der Anwendung einmal
geladen. Die Route / liefert die Seite aus, die Sie als Nächstes erstellen;
Flask liefert auch das Verzeichnis static automatisch aus.
from flask import Flask, jsonify, request, send_from_directory
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
ALLOWED_TYPES = {'pdf', 'jpeg', 'png'}
magika = Magika()
@app.get('/')
def index():
return send_from_directory(app.root_path, 'index.html')
@app.errorhandler(RequestEntityTooLarge)
def request_too_large(error):
return jsonify(error='Upload is too large'), 413
@app.post('/verify')
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,
)
Das Dateilimit beträgt 5 MiB. Das separate Anfragelimit von 6 MiB lässt Platz für Multipart-Header und
begrenzt zugleich die gesamte Anfrage. Wie die
Upload-Anleitung von Flask erklärt, kann das Parsen von Uploads
temporären Festplattenspeicher nutzen. Diese App speichert akzeptierte Dateien nicht dauerhaft;
sie liest vor der Klassifizierung höchstens 5 MiB plus ein Byte in content ein.
3. Binden Sie das Frontend an
Speichern Sie dies als magika-upload/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Classify a file with Magika</title>
<script src="/static/verify.js" defer></script>
</head>
<body>
<h1>Classify a file with Magika</h1>
<p>PDF, JPEG, or PNG, up to 5 MiB. The file is sent to the local Python server.</p>
<form id="uploadForm" action="/verify" method="post" enctype="multipart/form-data">
<label for="fileInput">File to classify</label>
<input id="fileInput" name="file" type="file">
<button type="submit">Classify file</button>
</form>
<p id="result" role="status">Choose a file, then classify it.</p>
</body>
</html>
Speichern Sie Folgendes als magika-upload/static/verify.js. Wenn sich die Auswahl ändert, wird das
vorherige Ergebnis sofort gelöscht. Ein erneutes Absenden ersetzt die vorherige Anfrage, sodass eine
langsame Antwort das Feedback zu neueren Vorgängen nicht überschreiben kann.
const form = document.getElementById('uploadForm')
const input = document.getElementById('fileInput')
const result = document.getElementById('result')
const errors = {
400: 'Choose a non-empty file.',
413: 'File exceeds the upload limit.',
415: 'Only PDF, JPEG, and PNG files are allowed.',
}
let requestId = 0
let activeRequest
input.addEventListener('change', () => {
requestId += 1
activeRequest?.abort()
result.textContent = 'Selection changed. Click Classify file to check it.'
})
form.addEventListener('submit', async (event) => {
event.preventDefault()
const id = ++requestId
activeRequest?.abort()
const file = input.files[0]
if (!file) {
result.textContent = 'Choose a file, then classify it.'
return
}
const controller = new AbortController()
activeRequest = controller
const timeout = setTimeout(() => controller.abort(), 30_000)
const data = new FormData()
data.append('file', file)
result.textContent = `Classifying ${file.name}…`
try {
const response = await fetch('/verify', {
method: 'POST',
body: data,
signal: controller.signal,
})
if (id !== requestId) return
if (!response.ok) {
result.textContent = errors[response.status] ??
`Classification failed (HTTP ${response.status}).`
return
}
const analysis = await response.json()
if (id !== requestId) return
result.textContent = `${file.name}: ${analysis.file_type} (${analysis.mime_type}). ` +
'Accepted by the type allowlist.'
} catch {
if (id !== requestId) return
result.textContent = controller.signal.aborted
? 'Request timed out. Click Classify file to retry.'
: 'Could not reach or read the server. Check the terminal, then retry.'
} finally {
clearTimeout(timeout)
}
})
Die Anfrage-ID sichert sowohl die Antwort als auch deren asynchron eingelesenen JSON-Body ab.
AbortController
beendet das Warten auf ersetzte Anfragen, und ein Timer von 30 Sekunden begrenzt jeden Versuch im
Browser. Der Abbruch der Browser-Anfrage garantiert nicht, dass Python die Verarbeitung bereits
empfangener Bytes stoppt.
Starten Sie die App aus demselben übergeordneten Verzeichnis wie bei der Einrichtung:
(
cd magika-upload &&
.venv/bin/python -m flask --app app run --host 127.0.0.1 --port 5050
)
Warten Sie auf die Startmeldung von Flask und öffnen Sie dann http://127.0.0.1:5050/.
Öffnen Sie diese URL statt der HTML-Datei auf der Festplatte: Seite, Skript und der Endpunkt
/verify müssen denselben Ursprung haben. Wählen Sie eine Datei mit
File to classify aus und klicken Sie dann auf
Classify file. Sie können erneut auf die Schaltfläche
klicken, um es mit derselben Datei noch einmal zu versuchen. Stoppen Sie den Server anschließend mit
Ctrl+C.
4. Vergleichen Sie Ergebnisse mit einer Allowlist
Testen Sie eine kleine, gültige PNG-Datei, die in photo.txt umbenannt wurde.
Sie sollten folgende Meldung sehen:
photo.txt: png (image/png). Accepted by the type allowlist.
Der Dateiname autorisiert den Upload nicht: Das tun nur die serverseitigen Labels
pdf, jpeg und png.
Bei JPEG-Dateien lautet das Label jpeg, auch wenn der Dateiname auf
.jpg endet.
Testen Sie als Nächstes eine nicht leere Textdatei, die in photo.png umbenannt
wurde. Sie sollte folgende Meldung erzeugen:
Only PDF, JPEG, and PNG files are allowed.
Testen Sie auch eine leere Datei und eine Datei knapp über 5 MiB. Diese werden abgelehnt, bevor Magika
ausgeführt wird. Wählen Sie während einer laufenden Anfrage eine andere Datei aus: Die Seite sollte
das ausstehende Ergebnis löschen und darauf warten, dass Sie die neue Auswahl klassifizieren.
Beheben Sie eine fehlgeschlagene Anfrage
- Installation oder Laden des Modells schlägt fehl: Lesen Sie zuerst die Fehlermeldung im
Terminal. Vergewissern Sie sich, dass die Installation in
.venvabgeschlossen wurde und dass Sie die dortige ausführbare Python-Datei starten. Die Seite kann erst geladen werden, wenn die Anwendung erfolgreich gestartet ist. - Der Port ist belegt: Flask beendet sich mit einer Fehlermeldung zur bereits verwendeten Adresse. Wählen Sie im Startbefehl einen anderen Port und öffnen Sie die URL mit diesem Port. Dies ist ein lokaler Entwicklungsserver, keine Bereitstellungskonfiguration.
- Der Server lehnt die Anfrage ab: HTTP 400 bedeutet eine fehlende oder leere Datei, 413 ein Größenlimit und 415 ein Label außerhalb der Allowlist. Größere Anfragen können auf dem Entwicklungsserver einen Verbindungsreset statt einer lesbaren 413-Antwort auslösen.
- Der Browser kann keine Antwort lesen: Prüfen Sie das Server-Terminal und die URL und versuchen Sie es dann erneut. HTTP-Fehler, ein gestoppter Server und Zeitüberschreitungen gelten nicht als Annahme der Datei. Die Seite zeigt feste Fehlermeldungen an, statt eine Server-Fehlerseite oder einen Stacktrace darzustellen.
Nutzen Sie das Ergebnis in einem Upload-Workflow
Wenn Sie diese App um das Speichern von Dateien ergänzen, müssen sich die Allowlist-Prüfung und das Speichern auf dieselben empfangenen Bytes beziehen. Eine erfolgreiche Klassifizierungsantwort darf nicht zur Erlaubnis für einen späteren, ungeprüften Upload werden. Das Beispiel gibt nur eine Klassifizierung zurück; es erzeugt weder einen gespeicherten Upload noch eine wiederverwendbare Freigabe.
Ein erlaubtes Label kann helfen, einen nachgelagerten Bilddecoder, Dokumentparser oder Moderationsdienst auszuwählen. Diese Vorgänge benötigen weiterhin eigene Validierung und Ressourcenlimits. Ein Malware-Scan ist eine separate Aufgabe, und eine Formatvorhersage sollte nie als Sicherheitsurteil angezeigt werden.
Für einen verwalteten Upload-Workflow lesen Sie die Referenz zur Dateiverifizierung. Behalten Sie bei beiden Ansätzen dieselbe Unterscheidung bei: Ein Format zu erkennen ist eine Entscheidung beim Umgang mit einem Upload, kein Beweis dafür, dass seine Inhalte sicher sind.
