Wie führe ich nicht vertrauenswürdigen Benutzercode in Node.js oder Python am sichersten auf meinem Backend aus?
Führen Sie nicht vertrauenswürdigen Benutzercode in einer kurzlebigen Ausführungsumgebung außerhalb Ihres Web-Backends aus. Für einen öffentlichen Dienst, der beliebigen Node.js- oder Python-Code annimmt, ist eine gut konfigurierte MicroVM pro Job oder Mandantensitzung eine gute Standardwahl. Halten Sie Produktions-Anmeldeinformationen fern, blockieren Sie unnötigen Netzwerkzugriff und setzen Sie Ressourcenlimits und eine Ablaufzeit von außerhalb der Ausführungsumgebung durch.
Nutzen Sie Ihre Express-, FastAPI- oder Django-Anwendung, um Benutzer zu authentifizieren, Jobs in eine Warteschlange zu stellen und Ergebnisse zurückzugeben. Lassen Sie einen separaten Worker die Ausführung verwalten. Einschränkungen auf Sprachebene allein bieten nicht die Isolierung, die dieser Aufbau benötigt.
Das richtige Setup hängt davon ab, was Benutzer einreichen können. Ein Taschenrechner kann eine kleine Ausdruckssprache akzeptieren. Ein Coding-Agent, der Pakete installiert und Shell-Befehle ausführt, braucht eine vollständige Ausführungsumgebung. Wir behandeln diesen zweiten Fall, einschließlich Code, den eine KI aus Benutzereingaben erzeugt.
Zuerst die Isolationsgrenze wählen
| Ansatz | Was er trennt | Eignung für beliebigen Benutzercode |
|---|---|---|
node:vm, Python eval oder exec | Sprachkontexte oder Ausführungsbereiche | Als Sicherheitsgrenze unzureichend |
| Ein untergeordneter Prozess | Prozessspeicher und Lebenszyklus | Benötigt weiterhin Kontrollen für Betriebssystem, Dateisystem, Netzwerk und Ressourcen |
| Standard-Docker | Namespaces, Dateisystemsichten und Ressourcengruppen | Nützliche Kontrollen, aber Container teilen sich den Kernel des Docker-Hosts |
| gVisor | Ein Anwendungskernel verarbeitet Systemaufrufe, bevor sie den Host erreichen | Eine Option, wenn Kompatibilität und Leistung zur Arbeitslast passen |
| Eine MicroVM | Ein Gast-Kernel und eine Grenze durch eine virtuelle Maschine | Eine gute Standardwahl für bösartigen Code; Host und Gast benötigen weiterhin Patches |
Die Sicherheit von Docker hängt vom Host, Daemon und der Konfiguration ab. gVisor ergänzt einen Anwendungskernel im Userspace. Firecracker nutzt KVM, um MicroVMs auszuführen. Keine dieser Optionen legt fest, welche Anmeldeinformationen oder Netzwerkziele Ihre Anwendung zugänglich machen sollte. Docker-Sicherheit, gVisor-Architektur, Firecracker.
Ziehen Sie bei strengeren Anforderungen auch dedizierte Worker-Hosts oder Konten in Betracht. Eine VM-Grenze verringert die Risiken durch einen gemeinsam genutzten Kernel; sie belegt nicht, dass jeder denkbare Ausbruch oder Hardware-Seitenkanal unmöglich ist.
Warum Node.js und Python eine externe Sandbox brauchen
Node.js: Ein VM-Kontext ist keine Sicherheitsgrenze
Die Dokumentation von Node selbst sagt, dass node:vm nicht als Sicherheitsmechanismus für nicht vertrauenswürdigen Code dienen darf. Ein leeres Objekt oder ein Timeout für vm.runInNewContext ändert daran nichts. Node.js-VM-Dokumentation.
Worker-Threads helfen bei der parallelen Ausführung. Ein untergeordneter Prozess kann den Webserver vor einem gewöhnlichen Absturz des Interpreters schützen. Keines von beiden entzieht automatisch den Dateisystemzugriff, geerbte Umgebungsvariablen oder den Netzwerkzugriff. Ein untergeordneter Prozess mit den Betriebssystemberechtigungen Ihres Servers kann weiterhin auf alle Ressourcen zugreifen, die diese Berechtigungen erlauben.
Auch das Permission Model von Node weist darauf hin, dass es keine Sicherheitsgarantien gegen bösartigen Code bietet. Nutzen Sie es als zusätzliche Einschränkung, wo es sinnvoll ist, zusammen mit einer externen Grenze für bösartige Einreichungen. Node.js-Berechtigungen.
Python: Namen einzuschränken isoliert keinen Prozess
Python warnt, dass exec und eval beliebigen Code ausführen. Das Entfernen von __builtins__ oder das Blockieren einiger Imports macht aus uneingeschränktem Python keinen sicheren Ausführungsdienst. RestrictedPython beschreibt ebenfalls eine Teilmenge der Sprache, ohne zu behaupten, selbst eine Sandbox bereitzustellen. Integrierte Python-Funktionen, RestrictedPython.
Nehmen Sie für Daten ein größenbegrenztes JSON-Dokument an und prüfen Sie dessen Struktur. ast.literal_eval führt keinen beliebigen Python-Code aus, doch Python dokumentiert Risiken durch die Erschöpfung von Speicher, CPU und Stack bei bösartigen Eingaben. Allgemeine Programme unterstützt es ebenfalls nicht. Python-AST-Dokumentation.
Starten Sie für den vollen Python-Sprachumfang einen separaten Interpreter in der gewählten Sandbox. Halten Sie das Parsen von Webanfragen und die Benutzerauthentifizierung außerhalb davon.
Eine Backend-Architektur zur Ausführung nicht vertrauenswürdigen Codes
Das Backend sollte den Job-Datensatz und die Zugriffsregeln verwalten:
- Authentifizieren Sie den Aufrufer und prüfen Sie sein Kontingent. Begrenzen Sie die Anfragegröße in Bytes, bevor Sie den Quellcode puffern.
- Speichern Sie den Quellcode als Daten. Ordnen Sie den Job einem Mandanten zu und wählen Sie eine Laufzeitumgebung aus einer festen Allowlist.
- Stellen Sie den Job in die Warteschlange. Ein vertrauenswürdiger Worker erstellt eine neue Sandbox mit ausdrücklichen Limits und einer Ablaufzeit.
- Übertragen Sie nur die Eingaben des Jobs. Führen Sie einen festen Interpreter-Befehl aus und übergeben Sie den Quellcode über eine Datei oder stdin.
- Erfassen Sie eine begrenzte Ausgabe und den Status. Prüfen Sie Ergebnisse, bevor Sie sie zurückgeben oder in einem anderen Dienst verwenden.
- Stoppen und entfernen Sie die Ausführungsumgebung. Ein separater Bereinigungs-Worker kümmert sich um verwaiste Jobs.
Bewahren Sie den Schlüssel des Sandbox-Anbieters, den Docker-Socket und die Anmeldeinformationen zur Cloud-Verwaltung im vertrauenswürdigen Worker auf. Benutzercode darf keine Anmeldeinformationen erhalten, mit denen sich andere Sandboxes erstellen oder verwalten lassen.
Prüfen Sie bei jedem Vorgang die Mandantenzugehörigkeit: Ausführen, Abbrechen, Herunterladen, Fortsetzen und Löschen. Eine vom Client übermittelte Sandbox-ID ist eine Kennung, kein Zugriffsnachweis. Den gesamten Ausführungsablauf beschreibt unser Leitfaden zu KI-Sandboxes.
Kontrollen vor dem ersten Benutzerjob festlegen
| Kontrolle | Ausgangsrichtlinie für einen kleinen Code-Runner |
|---|---|
| CPU und RAM | Feste Zuweisung; vom Benutzer übermittelte Ressourcen-Flags ablehnen |
| Gesamtlaufzeit | Eine kurze externe Frist sowie eine maximale Sandbox-Lebensdauer |
| Prozesse und Dateien | Limits für Prozessanzahl, offene Dateien und beschreibbaren Speicher |
| Eingabe und Ausgabe | Zum Beispiel 32 KiB Quellcode und 64 KiB für stdout/stderr zusammen; wählen Sie Limits für Ihr Produkt |
| Dateisystem | Schreibgeschütztes Basis-Image und ein kleines temporäres Verzeichnis; keine Mounts des Home-Verzeichnisses oder der Anwendung auf dem Host |
| Anmeldeinformationen | Keine Produktionsgeheimnisse; nur bei Bedarf eine eng begrenzte, kurzlebige Berechtigung verwenden |
| Netzwerk | Ausgehenden Zugriff standardmäßig verweigern; benötigte Ziele ausdrücklich erlauben |
| Mandantenzustand | Neuer Arbeitsbereich pro Job oder eine Sitzung, die an einen Mandanten gebunden ist |
| Kontonutzung | Limits pro Benutzer für parallele Ausführungen, Einreichungsrate und Ausgaben |
Die CPU-Zuweisung begrenzt, wie schnell eine aktive Warteschleife läuft. Eine Frist für die Gesamtlaufzeit begrenzt, wie lange sie läuft. Setzen Sie beides durch. Docker dokumentiert, dass Container keine CPU- oder Speicherlimits haben, solange Sie diese nicht konfigurieren. Docker-Ressourcenlimits.
Die Netzwerkrichtlinie muss neben dem öffentlichen Internet auch private Dienste und Cloud-Metadaten abdecken. Wenn Jobs HTTP benötigen, setzen Sie eine Allowlist an einem Egress-Proxy oder auf einer Netzwerkschicht durch, die der Gast nicht ändern kann. Prüfen Sie DNS-Auflösung, IPv4/IPv6-Ziele und Weiterleitungen. Ein Wrapper um fetch innerhalb des Gasts kann nicht jedes Programm kontrollieren, das der Gast starten kann. OWASP-Leitfaden zur SSRF-Prävention.
Getestete Docker-Startbeispiele für Node.js und Python
Diese Befehle zeigen Kontrollen beim Container-Start mit festen Beispielprogrammen. Sie enthalten nicht den externen Überwachungsprozess für Gesamtlaufzeit und Ausgabe, den öffentliche Einreichungen erfordern. Führen Sie sie auf einem Ausführungs-Worker aus, der von Ihren Anwendungsservern getrennt ist. Betreiben Sie diesen Worker für einen öffentlichen Dienst für bösartigen Code innerhalb der gewählten VM-Grenze oder nutzen Sie eine geeignete Sandbox-Laufzeitumgebung.
Nutzen Sie ein vertrauenswürdiges Image mit aktuellen Patches. Die folgenden Tags sind für das Beispiel praktisch; legen Sie beim Deployment den geprüften Image-Digest fest und aktualisieren Sie ihn über Ihren üblichen Patch-Prozess. Laden Sie Images herunter, bevor Sie Jobs annehmen.
Node.js-Beispiel
docker run --rm -i --init \
--network none --read-only \
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=16m \
--user 65534:65534 --cap-drop ALL \
--security-opt no-new-privileges \
--memory 128m --memory-swap 128m --cpus 0.5 \
--pids-limit 32 --ulimit nofile=64:64 \
--log-driver none \
node:24-bookworm-slim node - <<'JS'
console.log(JSON.stringify({answer: 6 * 7}));
JSDie erwartete Ausgabe ist {"answer":42}. Der Befehl läuft als UID 65534, entfernt Linux-Capabilities, hält das Root-Dateisystem schreibgeschützt und erlaubt ein kleines beschreibbares /tmp. Er stellt keine externe Netzwerkschnittstelle bereit und bindet kein Host-Verzeichnis ein.
Python-Beispiel
docker run --rm -i --init \
--network none --read-only \
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=16m \
--user 65534:65534 --cap-drop ALL \
--security-opt no-new-privileges \
--memory 128m --memory-swap 128m --cpus 0.5 \
--pids-limit 32 --ulimit nofile=64:64 \
--log-driver none \
python:3.13-slim-bookworm python -I -B -u - <<'PYCODE'
import json
print(json.dumps({"answer": 6 * 7}))
PYCODEDie erwartete Ausgabe ist {"answer": 42}. Pythons -I verringert Einflüsse durch Importpfade und die Interpreter-Umgebung. Es hindert das Programm nicht daran, die Betriebssystemressourcen zu nutzen, die sein Container erlaubt. Python-Befehlszeilenoptionen.
Behalten Sie das standardmäßige seccomp-Profil von Docker bei. no-new-privileges blockiert die Ausweitung von Berechtigungen durch Mechanismen wie ausführbare setuid-Dateien. Der Modus --network none lässt nur Loopback zu. Diese Kontrollen haben unterschiedliche Aufgaben; keine ersetzt die anderen. Docker-run-Optionen, seccomp, Netzwerkisolierung.
Die Option noexec des temporären Verzeichnisses verhindert die direkte Ausführung von diesem Mount. Ein Interpreter kann ein dort gespeichertes Skript aber weiterhin lesen und ausführen. Die Option verbietet keine Benutzerprogramme. Auch das Limit von 128 MiB passt zu diesen kleinen Beispielen, nicht zu jedem npm-Paket oder jeder Data-Science-Arbeitslast.
Ohne Shell-Interpolation an ein Backend anbinden
Erstellen Sie die Docker- oder Anbieteranfrage auf dem vertrauenswürdigen Worker aus festen Argumenten. Node kann spawn mit shell: false nutzen; Python kann subprocess.Popen mit einer Argumentliste verwenden. Übergeben Sie den eingereichten Quellcode über stdin oder einen festen Dateipfad. Setzen Sie ihn niemals in einen Shell-Befehl ein, etwa in eine Zeichenfolge, die Sie an sh -c übergeben. Siehe Untergeordnete Prozesse in Node und Sicherheit von Python-subprocess.
Lassen Sie Benutzer zwischen freigegebenen Laufzeitnamen wählen. Eine Anfrage darf keine Docker-Flags, beliebigen Images, Host-Pfade oder Namen von Umgebungsvariablen vorgeben. Auch die Paketinstallation führt Code aus; wenn Sie sie unterstützen, führen Sie sie unter denselben Isolations- und Ressourcenregeln aus.
Ausführung stoppen, Ausgabe begrenzen und Bereinigung bestätigen
Ein Timeout für eine HTTP-Anfrage oder einen SDK-Aufruf kann das Warten Ihres Backends beenden, während das Remote-Programm weiterläuft. Ebenso kann der Container weiterlaufen, wenn Sie einen lokalen Docker-CLI-Prozess beenden.
Der vertrauenswürdige Überwachungsprozess muss:
- Die Container- oder Sandbox-ID vor dem Start der Ausführung erfassen.
- stdout und stderr schrittweise mit einem gemeinsamen Byte-Limit lesen.
- Bei einem Timeout oder zu viel Ausgabe die tatsächliche Ausführung über die Laufzeit-API stoppen.
- Verbundene Streams schließen oder leeren, ohne unbegrenzt Daten zu speichern.
- Die Beendigung bestätigen, kurzlebige Zustandsdaten entfernen und Fehler bei der Bereinigung erfassen.
--log-driver none verhindert in den Beispielen, dass Docker Container-Logs speichert. Ihr Backend muss die gelesenen Streams trotzdem begrenzen. Stoppen Sie bei Docker den benannten Container, bevor Sie die verbundene CLI beenden. Nutzen Sie bei einem MicroVM-Dienst dessen dokumentierten Vorgang zum Beenden und prüfen Sie den Endzustand.
Legen Sie, falls verfügbar, eine Ablaufzeit beim Anbieter fest. Ein finally-Block kann nach einem Stromausfall des Workers nicht mehr laufen. Nutzen Sie daher einen zeitgesteuerten Bereinigungsprozess, der abgelaufene Job-Datensätze mit aktiven Ressourcen abgleicht. Sorgen Sie dafür, dass sich die Bereinigung sicher wiederholen lässt. Markieren Sie einen verwaisten Job nicht als erfolgreich bereinigt, nur weil der Client die Verbindung getrennt hat.
Was wir geprüft haben
Am 31. Oktober 2026 haben wir beide Befehle auf Docker Desktop mit Engine 28.1.1, Linux-Kernel 6.10.14-linuxkit, ARM64, Node.js 24.22.2 und Python 3.13.18 ausgeführt.
| Prüfung | Ergebnis für beide Laufzeitumgebungen |
|---|---|
| Feste Berechnung | Gab 42 zurück |
| Effektiver Benutzer | UID 65534 |
Schreiben unter /etc | Blockiert |
Schreiben und Lesen unter /tmp | Erfolgreich |
| Ausgehender Socket zu einer Testadresse | Schlug mit ENETUNREACH fehl |
| Künstlicher Umgebungsmarker nur auf dem Host | Im Container nicht vorhanden |
| Ressourcenkonfiguration | 128 MiB Speicher, kein Swap, 32 Prozesse/Threads, eine halbe CPU |
| Berechtigungsrichtlinie | NoNewPrivs aktiviert |
| Aktive Warteschleife unter dem Test-Überwachungsprozess | Nach Ablauf der Zwei-Sekunden-Frist gestoppt |
| Übermäßige Ausgabe unter dem Test-Überwachungsprozess | Erfassung bei 64 KiB gestoppt; Ausführung beendet |
| Neuer Container nach einem vorherigen Job | Vorherige temporäre Datei nicht vorhanden |
Unsere erste Prüfung des Ausgabelimits zeigte einen Bereinigungsfehler: Die verbundene Docker CLI wartete nach dem Stopp des Containers an einer vollen Pipe. Wir korrigierten den Test-Überwachungsprozess so, dass er zuerst die Ausführung und dann den verbundenen Client beendet. Deshalb ist der Startbefehl allein noch kein vollständiger Runner.
Diese Prüfungen decken die lokale Container-Konfiguration und das Verhalten des Überwachungsprozesses ab. Sie testen weder Kernel-Ausbrüche noch die MicroVM-Isolierung von Lizard und belegen nicht, dass jeder Zugriffsweg blockiert ist. Wiederholen Sie die Prüfungen auf der tatsächlichen Worker-Laufzeitumgebung, die Sie einsetzen.
Auch Ergebnisse und gespeicherten Zustand als nicht vertrauenswürdig behandeln
Eine Sandbox kann ein Programm isolieren, während dessen Ausgabe das nächste System angreift. Maskieren Sie stdout vor der Darstellung. Prüfen Sie zurückgegebenes JSON. Deserialisieren Sie niemals ein eingereichtes Python-Pickle und werten Sie niemals eine zurückgegebene JavaScript-Zeichenfolge im Backend aus. Python-Pickle-Warnung, OWASP-Leitfaden zur Ausgabeverarbeitung.
Stellen Sie erzeugtes HTML und Vorschauen über einen Origin bereit, der nicht die Anmeldeinformationen Ihres Dashboards teilt. Kontrollieren Sie bei heruntergeladenen Dateien Namen, Größen und Typen; folgen Sie keinen vom Gast erstellten Symlinks in Host-Pfade. Begrenzen Sie auch den Aufwand für das Entpacken von Archiven und die Dekomprimierung.
Ein frischer Zustand verhindert, dass ein Mandant die Dateien, Paket-Caches oder Werte im Arbeitsspeicher eines anderen Mandanten liest. Persistente Sitzungen brauchen ausdrückliche Regeln für Zugehörigkeit und Aufbewahrung. Eine pausierte Sandbox behält ihren Zustand; sie sollte nicht als saubere Umgebung für den nächsten Benutzer dienen. Wenden Sie dieselben Regeln auf Snapshots und gemeinsam genutzte Volumes an.
Wo Lizard zum Einsatz kommt
Lizard Sandboxes führt Code in Firecracker-MicroVMs aus, jede mit eigenem Kernel. Lizard SDK unterstützt JavaScript/TypeScript und Python, einschließlich Dateivorgängen, Befehlsausführung, Statusprüfungen und Lebenszyklussteuerung. Ihr Backend kann die Ausführungsumgebung über diese API verwalten, statt eigene Worker-Hosts zu betreiben. Dokumentation zu Lizard Sandboxes.
Bewahren Sie den API-Schlüssel für die Verwaltung im Backend auf. Vermeiden Sie bei Jobs mit bösartigem Code die Übergabe von lizardToken, das eine Projekt-Anmeldeinformation im Gast bereitstellt, sowie das Anhängen von Agenten-Anmeldeinformationen, die die Aufgabe nicht benötigt. Legen Sie eine ausdrückliche Lebensdauer fest und beenden Sie kurzlebige Jobs, nachdem Sie begrenzte Ergebnisse erfasst haben. Referenz zu Lizard SDK.
Die Isolierung durch Firecracker bedeutet nicht, dass ausgehender Netzwerkverkehr blockiert ist. Prüfen Sie, ob die eingesetzte Netzwerkrichtlinie Ihre Vorgaben zu erlaubten Zielen durchsetzt. Die aktuelle SDK-Referenz zu den Erstellungsoptionen dokumentiert keine Egress-Allowlist; erfinden Sie kein Flag und nehmen Sie nicht an, dass eine Proxy-Einstellung im Gast eine solche Allowlist durchsetzt. Wenn eine strenge Kontrolle des ausgehenden Datenverkehrs Pflicht ist, prüfen Sie eine durchsetzbare Konfiguration, bevor Sie diese Arbeitslast annehmen.
Legen Sie zuerst die erlaubten Dateien, den Netzwerkzugriff und das Ressourcenbudget des Jobs fest. Testen Sie dann eine neue Umgebung mit diesen Einschränkungen. Nutzen Sie die Lizard-Schnellstartanleitung für die Ausführungs-API und unseren Sandbox-Preisvergleich zur Kostenschätzung.
Mit KI entwickeln. Mit Lizard ausliefern.
Du brauchst kein Plattform-Team, um live zu gehen. Deine ganze Cloud ist nur einen CLI-Befehl entfernt.
- Dienste
- —
- Add-ons
- —
- Bereitstellungen
- —
- Sandbox-Starts
- —