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:

RangfolgeSourceBeschreibung
HöchsteGenehmigter expliziter ModusDer während eines einmaligen Wiederholungsversuchs zur Privilegieneskalation übergebenemode, wobei er Sitzungsrichtlinien überwiegt
ZweitensDie letzte Sitzungsandbox/modeEreignisseMit Sitzungsprotokollen persistiert, zur Rekonstruktion wiedergabefähig
FallbackStandardmodus für die BereitstellungAgentenlose 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

// Dateipfad: my-plugins/sandbox-demo/src/index.ts
// 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 sinddsh-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:

ErgebnisBedeutungDas Verhalten des Aufrufers
allowed-onceEinmalige GenehmigungDas einzige Freigabeergebnis, das nur den einen Vorgang autorisiert, nach dem gefragt wurde
rejectedExplizit ablehnenAblehnen
cancelledAnfrage wird zurückgezogen (z. B. Signalabbruch)Ablehnen
unavailableKein Responder, der Responder löst eine Ausnahme aus, oder der Rückgabewert ist nicht konformFail 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.

PolicyVerhaltenTypische Szenarien
ask(Standard)Delegiere an die zusammengesetzte Responder-Kette; wenn kein Responder existiert, greife zurück aufunavailableInteraktive UI, die menschliche Entscheidungen erfordert
neverDeterministische Rückgaberejected, löst keine Responder ausCI, 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.

PresetSandbox-ModusGenehmigungsrichtlinienBedeutung
workspace-writeworkspace-writeaskBeschreibbarer Arbeitsbereich, bei sensiblen Vorgängen den Benutzer fragen
danger-full-accessdanger-full-accessneverSandbox 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:

ProblemReferenz
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
Weitere Erweiterungen