DeepSeek Harness Sandbox und Genehmigung: Gefährliche Aktionen des Agenten kontrollierbar machen
Das vorherige Kapitel behandelte die Tool-Ausführungspipeline, wobei zwei Phasen von größter Bedeutung sind: wie die Sandbox Befehle einschränkt und wie die Genehmigung entscheidet, „ob sie durchgeführt werden kann.“
In diesem Abschnitt werden wir uns eingehend mitctx.sandboxundctx.approvalzwei Diensten befassen – und ihre jeweiligen Zuständigkeiten und Fail-Closed-Prinzipien klären.
In einem Satz: Die Sandbox regelt, „innerhalb welcher Grenzen der Befehl ausgeführt wird“, und die Genehmigung regelt, „ob dieser spezifische Vorgang zulässig ist“; beide verhalten sich standardmäßig fail-closed.
Zuerst der große Überblick: Sandbox-Entscheidung + Genehmigungsablauf
Das folgende Diagramm stellt beide Dienste nebeneinander: Links wird gezeigt, wie die Sandbox argv umhüllt, und rechts, wie die Genehmigung eine einmalige Entscheidung trifft.
Zusammen beantworten sie dieselbe Frage: Wenn ein Agent etwas Riskantes tun will, wie wird er eingeschränkt?
Die Sandbox grenzt ein, „auf welche Dateien der Prozess zugreifen kann“, und die Genehmigung delegiert „ob dieser Vorgang erlaubt werden soll“ an die Entscheidung des Antwortenden.
ctx.sandbox: argv gemäß Richtlinie umhüllen
Das Prozess-Sandbox-Modell ist: Der Konsument übergibtexakte argv, das Backend umhüllt sie gemäß der Dateieffekt-Richtlinie.
Der entscheidende Punkt ist die „exakte argv“ statt einer Shell-Zeichenkette – ein shell-förmiger Konsument muss übergeben['bash', '-c', command]。
ctx.sandbox.confine(argv, policy)gibt einConfinedArgvzurück, d. h. "das ersetzte argv plus die Durchsetzungsfakten des Backends."
SandboxMode(Sandbox-Modus) regelt nur Dateisystemeffekte; er umfasst weder Netzwerk- noch Prozesssichtbarkeit.
read-only: erlaubt nur die notwendigen Datenempfänger (z. B.
/dev/null), Schreiben verweigern.workspace-write: erlaubt außerdem Schreibzugriffe unter dem Workspace-Stammverzeichnis und im temporären Bereich, der vom Backend zugesichert wird.
danger-full-access: umgeht die Isolation, wobei der Consumer direkt das rohe argv startet, ohne ctx.sandbox aufzurufen.
Nur die ersten beiden Modi werden an den Provider gesendet,danger-full-accessEr betritt die Sandbox überhaupt nicht.
Dies garantiert eine Sicherheitseigenschaft: eingeschränkte Ausführung erreicht notwendigerweisectx.sandbox, und stilles unisoliertes Durchreichen ist niemals zulässig.
Durchsetzungsebene und Fallback-Regeln
Das Backend meldet die tatsächlich erreichte Vollständigkeit der Durchsetzung:enforcement: 'full' | 'partial'。
fullbedeutet, dass das Backend alle von diesem Modus zugesagten Datei-Effekte kontrolliert hat;partialwas anzeigt, dass nur eine Teilmenge gesteuert wird.
Aktuelle Fälle teilweiser Durchsetzung umfassen ältere Landlock-ABIs sowie die Everyone- und Hardlink-Grenzen des Windows-ACL-Runners.
Konsumenten, die absolute Grenzen benötigen, müssenpartialBehandle es als "unzureichend"—weise es zurück oder gib diese Unterscheidung nach oben weiter.
Die Richtlinienauflösung hat eine wohldefinierte Fallback-Reihenfolge; die offizielle Dokumentation fasst sie als drei Ebenen zusammen:
| Rangfolge | Source | Beschreibung |
|---|---|---|
| Höchste | Genehmigter expliziter Modus | Der während eines einmaligen Wiederholungsversuchs zur Privilegieneskalation übergebenemode, wobei er Sitzungsrichtlinien überwiegt |
| Zweitens | Die letzte Sitzungsandbox/modeEreignisse | Mit Sitzungsprotokollen persistiert, zur Rekonstruktion wiedergabefähig |
| Fallback | Standardmodus für die Bereitstellung | Agentenlose Aufrufe und Sitzungen ohne cwd verwenden das konfigurierte Stammverzeichnis |
Gewöhnliche Tool-Aufrufe leiten sich vom unveränderlichen cwd der aufrufenden Sitzung abworkspaceRoot。
root normalisiert zuerst nach Dateisystem-Semantik, dann nach lexikalischen Regeln, sodass es einschließtsymlink/..gibt das Verzeichnis an, in dem der Prozess tatsächlich ausgeführt wird.
Fail closed: Wenn kein Backend verfügbar ist,
ctx.sandbox.confinewird auslösenSandboxUnavailableError, FehlercodeSANDBOX_UNAVAILABLE。Unter einer eingeschränkten Richtlinie ist eine stille unisolierte Durchleitung niemals zulässig.
Praxisbeispiel: eine bash im eingeschränkten Modus ausführen
Verdrahten wir den Aufrufpfad für "sandboxed bash" im Code.
Beispiel
// Sandbox-Consumer demonstrieren: zuerst die Richtlinie parsen, dann ctx.sandbox argv umschließen lassen.
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'sandbox-demo'
export const inject = ['tools', 'sandbox', 'sandboxPolicy']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'sandboxed_echo',
description: 'Führe echo in der Sandbox aus. Der Beispiel-Demobefehl.',
parameters: {
text: { type: 'string', required: true, description: 'Text für echo' },
},
output: { schema: { type: 'string' } },
async execute(args, exec) {
// 1. Die vollständige Richtlinie für diesen Aufruf parsen: Das Sitzungs-cwd ist die Arbeitsbereichsgrenze.
const policy = ctx.sandboxPolicy.resolve({ session: exec.agent?.session })
// 2. Der Konsument übergibt die exakten argv (Programm plus Argumente), keinen Shell-String.
const argv = ['bash', '-c', `echo ${JSON.stringify(args.text)}`]
// 3. danger-full-access wird direkt gestartet; der Rest wird von der Sandbox umschlossen.
if (policy.mode === 'danger-full-access') {
const { spawn } = await import('node:child_process')
// ... spawn(argv) und Ausgabe sammeln
return `echo ${args.text}` // Demo: Normale Rückgabe
}
// 4. Eingeschränkter Modus: confine gibt das ersetzte argv zurück und wirft SANDBOX_UNAVAILABLE, wenn kein Backend vorhanden ist.
const confined = ctx.sandbox.confine(argv, { mode: policy.mode, workspaceRoot: policy.workspaceRoot })
// 5. eliminiere费方再 spawn confined.argv, und drücke confined.enforcement, um festzustellen, ob Requirement erfüllt ist.
if (confined.enforcement === 'partial' && policy.mode !== 'read-only') {
return { isError: true, error: { message: 'Partielle Durchsetzung ist für die Beispiel-Demo nicht akzeptabel' } }
}
return `confined echo ${args.text} (enforcement: ${confined.enforcement})`
},
}))
}
Hinweis: Der obige Code ist eine veranschaulichende Referenzimplementierung; er lässt die Details von spawn und der Ausgabenerfassung aus.
Produktionstaugliche Konsumenten sind
dsh-bash-sandbox, die auch für spawn und die Ergebniszuordnung verantwortlich ist.Die eigentliche Schwierigkeit liegt in der Ergebnisklassifizierung: zwischen "Sandbox-Runner-Fehler" und "Sandbox arbeitet normal, lehnt aber ab" zu unterscheiden.
Die Sandbox bringt außerdem zwei orthogonale stderr-Klassifikatoren zurück:
denialSignatures: Fälle identifizieren, in denen die Sandbox normal funktioniert und eingeschränkte Befehle blockiert werden (bwrap-EROFS-Text, Landlock-EACCES, Seatbelt-EPERM).
runnerFailureRules: identifiziert Fälle, in denen der Runner den Befehl ablehnt oder fehlschlägt, bevor er ihn ausführt (ein Landlock-Fehler erfordert Exit-Code 125 plus eine fatale Diagnosezeile).
Konsumenten sollten Runner-Fehler zuerst als Sandbox-Infrastrukturfehler melden, statt als gewöhnliche Aufgabenfehler.
ctx.approval: einmalige Berechtigungsentscheidung
Der Genehmigungsdienst beantwortet eine sehr eng gefasste Frage:Darf diese konkrete Operation fortgesetzt werden?
Es wird durchgereichtapproval/requestwaterfall verteilt Fragen an den Antwortenden.
Der Responder gibt das Ergebnis zurück, wenn er die Anfrage bearbeitet, andernfalls ruft ernext()Delegation; der erste Responder besetzt den einzigen Entscheidungs-Slot.
ErgebnistypApprovalOutcomeist geschlossen:
| Ergebnis | Bedeutung | Das Verhalten des Aufrufers |
|---|---|---|
allowed-once | Einmalige Genehmigung | Das einzige Freigabeergebnis, das nur den einen Vorgang autorisiert, nach dem gefragt wurde |
rejected | Explizit ablehnen | Ablehnen |
cancelled | Anfrage wird zurückgezogen (z. B. Signalabbruch) | Ablehnen |
unavailable | Kein Responder, der Responder löst eine Ausnahme aus, oder der Rückgabewert ist nicht konform | Fail closed, alles ablehnen |
Wichtige Sicherheitseigenschaft: Ein fehlender Responder, einer, der die Anfrage nicht bearbeitet, eine Ausnahme auslöst oder nicht konform ist, erzeugen alleunavailable, anstatt es zuzulassen.
Für den Aufruferrejected、cancelledundunavailableImmer Ablehnung durchsetzen.
Pro-Sitzung-Genehmigungsrichtlinien: fragen und niemals
ApprovalPolicyBestimmt, was passiert, bevor der interaktive Responder ausgeführt wird.
| Policy | Verhalten | Typische Szenarien |
|---|---|---|
ask(Standard) | Delegiere an die zusammengesetzte Responder-Kette; wenn kein Responder existiert, greife zurück aufunavailable | Interaktive UI, die menschliche Entscheidungen erfordert |
never | Deterministische Rückgaberejected, löst keine Responder aus | CI, strikte Headless-Haltung für unbeaufsichtigte Läufe |
Der effektive Wert ist der letzte Eintrag im Sitzungsprotokollapproval/policyEreignis, mit Fallback auf die Dienstkonfiguration.
setApprovalPolicy(session, policy)ist der einzige Schreibpfad, sodass Replay Override-Werte rekonstruieren kann.
neverinnerhalb des Dienstes durchgesetzt, vor dem Waterfall-Dispatch, sodass selbst bei späterer VerwendungprependRegistrierte Responder können es ebenfalls nicht umgehen.
Der Unterschied zwischen Audit- und Modellsichtbarkeit: Das Genehmigungs-Audit-Ereignispaar (
approval/askedundapproval/decided) nur Logs schreibt; sie gelangen nicht in das Modelltranskript.Das für das Modell sichtbare Verhalten besteht aus den vom Aufrufer abgeleiteten Tool-Ergebnissen und einer Momentaufnahme des aktuellen Laufzeitkontexts.
Berechtigungs-Presets: zwei Regler, gebündelt zu benannten Presets
Sandbox-Modus (sandbox/mode) und Genehmigungsrichtlinien (approval/policy) sind zwei voneinander unabhängige Regler.
Berechtigungs-Preset-Ebenectx.permissionPresetsBündeln Sie sie zu benannten Presets, damit Clients sie als einzelnen "permission"-Selektor bereitstellen können.
| Preset | Sandbox-Modus | Genehmigungsrichtlinien | Bedeutung |
|---|---|---|---|
workspace-write | workspace-write | ask | Beschreibbarer Arbeitsbereich, bei sensiblen Vorgängen den Benutzer fragen |
danger-full-access | danger-full-access | never | Sandbox umgehen, keine Rückfragen; kann nur in einer Wegwerfumgebung ausgeführt werden |
Die Preset-Tabelle ist konfigurationsgetrieben; die Standardtabelle enthält die oben genannten zwei; der Namecustomist für "abgeleiteten Nicht-Preset-Zustand" reserviert.
current(events)Ermittle das tatsächlich wirksame Preset aus den beiden Reglern, statt nur die eigenen Ereignisse des Presets zu betrachten.
Wenn zwei Presets dieselbe Reglerkombination teilen,permission/presetEreignisse ermöglichen,current()dass es weiterhin bewahren kann, welches der Benutzer tatsächlich ausgewählt hat.
Wichtig: Ein Berechtigungs-Preset beschreibt nur die Fähigkeiten, die es tatsächlich regelt.
Die offizielle Incident-Review 0002 warnte: Dateisystemzugriff zur Kompositionszeit kann dem Bash-only-Preset der Laufzeit nicht sicher folgen.
In der Beispiel-Produktionsumgebung,
danger-full-accesssollte nur für einmalige, wegwerfbare Sandbox-Umgebungen konfiguriert werden.
Zusammenfassung Selbsttest
Sandbox und Genehmigung trennen "Fähigkeitsgrenzen" von "Entscheidungsautorisierung": Die Sandbox umhüllt argv mit einer Richtlinie für Dateieffekte, und die Genehmigung verwendet einen Wasserfall von Respondern, um eine einmalige Freigabe zu erteilen.
Selbsttestfragen:
| Problem | Referenz |
|---|---|
Der Konsument möchte ausführendanger-full-accessModus, wird er ctx.sandbox aufrufen? | Nein – er startet direkt das rohe argv, ohne die Sandbox zu betreten |
| Die Genehmigungskette hat keine Antwortenden—was ist das Ergebnis? | unavailable, fail closed, der Aufrufer lehnt ab. |
| Wenn Sie in CI „niemals eine Eingabeaufforderung anzeigen und alle Genehmigungen deterministisch ablehnen“ möchten, welche Richtlinie sollten Sie verwenden? | approval/policy: never |