DeepSeek Harness Sandbox y aprobación: haz que las acciones peligrosas del Agent sean controlables

El capítulo anterior trató sobre la canalización de ejecución de herramientas, y hay dos aspectos que más preocupan: cómo el sandbox restringe los comandos, y cómo la aprobación decide si "se puede hacer".

Este capítulo profundizará enctx.sandboxyctx.approvaldos servicios, aclarando sus respectivas funciones y los principios de cierre ante fallos.

En una frase: el sandbox se ocupa de "dentro de qué límites se ejecuta el comando", y la aprobación de "si esta operación concreta está permitida"; ambos están cerrados ante fallos por defecto.


Primero vistazo general: decisión del sandbox + flujo de aprobación

El siguiente diagrama muestra los dos servicios juntos: a la izquierda, cómo el sandbox envuelve argv; a la derecha, cómo la aprobación toma una decisión de una sola vez.

沙箱决策与审批流程图

Ambos responden juntos a la misma pregunta: si el Agent quiere hacer algo arriesgado, ¿cómo restringirlo?

El sandbox delimita "qué archivos puede tocar el proceso"; la aprobación deja que el respondiente decida "si se permite esta operación".


ctx.sandbox: envuelve argv según la política

El modelo del sandbox de procesos es: el consumidor entregael argv exacto, y el backend lo envuelve según la política de efectos sobre archivos.

El punto clave es "argv exacto" en lugar de una cadena de shell: un consumidor con forma de shell debe pasar['bash', '-c', command]。

ctx.sandbox.confine(argv, policy)devuelve unConfinedArgv, es decir, "el argv reemplazado más los hechos de aplicación forzosa del backend".

SandboxMode(modo sandbox) solo controla los efectos en el sistema de archivos; no incluye red ni visibilidad de procesos.

read-only: solo permite los receptores de datos necesarios (como/dev/null), y rechaza la escritura.

workspace-write: también permite escribir en el directorio raíz del espacio de trabajo y en las áreas temporales prometidas por el backend.

danger-full-access: omite el aislamiento; el consumidor ejecuta directamente el argv original sin llamar a ctx.sandbox.

Solo los dos primeros modos se envían al proveedor,danger-full-accessel tercero ni siquiera entra en el sandbox.

Esto garantiza una propiedad de seguridad: la ejecución restringida llega necesariamente actx.sandbox, la transmisión silenciosa sin aislamiento nunca es legítima.


Grado de aplicación forzosa y reglas de respaldo

El backend informa el grado de integridad de la aplicación forzosa que realmente alcanzó:enforcement: 'full' | 'partial'。

fullindica que el backend controló todos los efectos de archivos prometidos por ese modo;partialindica que solo controló un subconjunto.

Los casos actuales de aplicación forzosa parcial incluyen ABI de Landlock más antiguos, así como los límites de Everyone y de enlaces duros en el runner de ACL de Windows.

Los consumidores que exijan límites absolutos deben tratarpartialcomo "insuficiente", rechazándolo o exponiendo esta distinción hacia arriba.

El análisis de la política tiene un orden de respaldo claro; la documentación oficial lo resume en tres capas:

PrioridadOrigenDescripción
Más altaModo explícito aprobadoEl pasado en un reintento de escalada de privilegios de una sola vezmode, prevalece sobre la política de sesión
En segundo lugarLa última de la sesiónsandbox/modeeventoSe persiste en el registro de sesión y puede reconstruirse mediante reproducción
RespaldoModo predeterminado de implementaciónLas invocaciones sin agent y las sesiones sin cwd usan el directorio raíz configurado

Las llamadas normales de herramientas se derivan del cwd inmutable de la sesión de invocaciónworkspaceRoot。

root se normaliza primero según la semántica del sistema de archivos y luego léxicamente, por lo que un cwd que contengasymlink/..identifica el directorio donde el proceso realmente se ejecuta.

Cierre ante fallos: cuando no hay backend disponible,ctx.sandbox.confinese lanzaSandboxUnavailableError, código de errorSANDBOX_UNAVAILABLE。

Bajo políticas restringidas, la transmisión silenciosa sin aislamiento nunca es legal.


Ejemplo práctico: ejecutar bash en modo restringido

Veamos cómo encadenar la ruta de llamada de "bash en sandbox" con código.

Ejemplo

// Ruta del archivo: my-plugins/sandbox-demo/src/index.ts
// Demostración de consumidor sandboxizado: primero analiza la política y luego deja que ctx.sandbox envuelva argv.
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: 'Run echo inside the sandbox. The ejemplo demo command.',
    parameters: {
      text: { type: 'string', required: true, description: 'Text to echo' },
    },
    output: { schema: { type: 'string' } },
    async execute(args, exec) {
      // 1. Analiza la política completa de esta llamada: el cwd de la sesión es el límite del espacio de trabajo.
      const policy = ctx.sandboxPolicy.resolve({ session: exec.agent?.session })

      // 2. El consumidor entrega el argv exacto (programa más argumentos), no una cadena de shell.
      const argv = ['bash', '-c', `echo ${JSON.stringify(args.text)}`]

      // 3. danger-full-access ejecuta directamente; el resto se envuelve con el sandbox.
      if (policy.mode === 'danger-full-access') {
        const { spawn } = await import('node:child_process')
        // ... spawn(argv) y recopila la salida
        return `echo ${args.text}` // Demostración: retorno normal
      }

      // 4. Modo restringido: confine devuelve el argv reemplazado; si no hay backend, lanza SANDBOX_UNAVAILABLE.
      const confined = ctx.sandbox.confine(argv, { mode: policy.mode, workspaceRoot: policy.workspaceRoot })

      // 5. El consumidor luego ejecuta confined.argv y según confined.enforcement decide si exige full.
      if (confined.enforcement === 'partial' && policy.mode !== 'read-only') {
        return { isError: true, error: { message: 'partial enforcement is not acceptable for ejemplo demo' } }
      }
      return `confined echo ${args.text} (enforcement: ${confined.enforcement})`
    },
  }))
}

Nota: el código anterior es una referencia demostrativa; se omiten los detalles de spawn y de recopilación de salida.

El consumidor de nivel de producción esdsh-bash-sandbox, que también se encarga del spawn y de la atribución de resultados.

La verdadera dificultad está en la clasificación de resultados: hay que distinguir entre "fallo del runner del sandbox" y "el sandbox funciona normalmente pero rechaza".

El sandbox también trae dos clasificadores de stderr ortogonales:

denialSignatures: identifica los casos en que el sandbox funciona normalmente y el comando restringido es bloqueado (texto EROFS de bwrap, EACCES de Landlock, EPERM de Seatbelt).

runnerFailureRules: identifica los casos en que el runner rechaza o falla antes de ejecutar el comando (un fallo de Landlock exige el código de salida 125 más una línea de diagnóstico fatal).

El consumidor debe informar primero el fallo del runner como una falla de infraestructura del sandbox, no como un fallo de tarea normal.


ctx.approval: decisión de permiso de una sola vez

El servicio de aprobación responde una pregunta muy concreta:¿Puede continuar esta operación concreta?

Utilizaapproval/requestwaterfall para distribuir la pregunta a los respondientes (answerer).

El respondiente devuelve un resultado si se encarga de la solicitud; de lo contrario, llama anext()para delegar; el primer respondiente ocupa el único espacio de decisión.

El tipo de resultadoApprovalOutcomees cerrado:

ResultadoSignificadoComportamiento del llamador
allowed-onceAprobación de una sola vezÚnico resultado de aprobación; autoriza solo la operación consultada
rejectedRechazo explícitoRechazar
cancelledLa solicitud se retira (p. ej., interrupción por signal)Rechazar
unavailableSin respondiente, el respondiente lanza una excepción o devuelve un valor no conformeCierre ante fallos; se rechaza siempre

Propiedad de seguridad clave: la ausencia, la falta de responsabilidad sobre la solicitud, una excepción o un valor no conforme por parte del respondiente producenunavailable, no una aprobación.

El llamador anterejected、cancelledyunavailablesiempre ejecuta un rechazo.


Política de aprobación por sesión: ask y never

ApprovalPolicyDecide qué ocurre antes de que se ejecute el respondiente interactivo.

PolíticaComportamientoEscenario típico
ask(predeterminada)Delega en la cadena combinada de respondientes; si no hay respondiente, se respalda enunavailableUI interactiva, se necesita que una persona decida
neverRetorno deterministarejected, no distribuye ningún respondientePostura headless estricta para CI y ejecución desatendida

El valor efectivo es la última entrada en el registro de sesiónapproval/policyevento, con respaldo a la configuración del servicio.

setApprovalPolicy(session, policy)Es la única ruta de escritura, por lo que la reproducción puede reconstruir el valor sobrescrito.

neverSe aplica forzosamente dentro del servicio, antes de la distribución waterfall, por lo que incluso si más tarde se registra un respondiente conprepend, no puede eludirlo.

Diferencia entre auditoría y visibilidad del modelo: el par de eventos de auditoría de la aprobación (approval/askedyapproval/decided) solo se escribe en el registro, no entra en la transcripción del modelo.

El comportamiento visible para el modelo son los resultados de herramientas derivados por el llamador y la instantánea del contexto de ejecución actual.


Presets de permisos: dos knobs combinados en un preset con nombre

Modo de sandbox (sandbox/mode) y política de aprobación (approval/policy) son dos knob mutuamente independientes.

Capa de ajustes predefinidos de permisosctx.permissionPresetsAgruparlos en ajustes predefinidos con nombre, para que el cliente los ofrezca como un selector único de «permisos».

Ajuste predefinidoModo de sandboxPolítica de aprobaciónSignificado
workspace-writeworkspace-writeaskEspacio de trabajo escribible, preguntar al usuario para operaciones sensibles
danger-full-accessdanger-full-accessneverOmite el sandbox, no pregunta, solo puede ejecutarse en un entorno desechable

La tabla de ajustes predefinidos está impulsada por configuración; la tabla predeterminada incluye los dos anteriores; el nombrecustomse reserva para «estados no predefinidos derivados».

current(events)Plegar el ajuste predefinido realmente activo a partir de los dos knob, no solo mirar los eventos del propio ajuste predefinido.

Cuando dos ajustes predefinidos comparten la misma combinación de knob,permission/presetlos eventos permiten quecurrent()se siga manteniendo cuál seleccionó realmente el usuario.

Importante: los ajustes predefinidos de permisos solo describen las capacidades que realmente administran.

El informe oficial del incidente 0002 ya advirtió: el acceso al sistema de archivos al combinarse no puede seguir de forma segura el ajuste predefinido bash-only en tiempo de ejecución.

En el entorno de producción de ejemplo,danger-full-accesssolo debe configurarse para entornos de sandbox desechables y de un solo uso.


Resumen y autoevaluación

El sandbox y la aprobación separan el «límite de capacidades» de la «autorización de decisiones»: el sandbox envuelve argv con una política de efectos de archivos, y la aprobación utiliza respondedores en cascada para un permiso de una sola vez.

Preguntas de autoevaluación:

PreguntaReferencia
El consumidor quiere ejecutardanger-full-accessmodo, ¿llamará a ctx.sandbox?No, hace spawn directamente del argv original, sin entrar al sandbox
La cadena de aprobación no tiene ningún respondedor, ¿cuál es el resultado?unavailable, cierre por fallo, el llamante es rechazado
En CI, si se quiere «nunca preguntar, rechazar determinantemente todas las aprobaciones», ¿qué estrategia se usa?approval/policy: never
Otras extensiones