Files
fn_registry/functions/infra/shell_exec_whitelist.md
egutierrez 621e8895c9 feat(infra): auto-commit con 86 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:38:15 +02:00

4.9 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, params, output
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path params output
shell_exec_whitelist function go infra 1.0.0 impure func ShellExecWhitelist(opts ShellExecOpts) (ShellExecResult, error) Ejecuta argv shell con whitelist obligatoria de binarios, SIN shell expansion, timeout context-cancellable con SIGTERM+SIGKILL, stdout/stderr separados con truncate opcional. Para device_agent y otros sandboxes que reciben requests externos.
shell
exec
security
sandbox
device-agent
infra
agents
docker
shell_exec_result_go_infra
error_go_core
shell_exec_result_go_infra
false error_go_core
bytes
context
fmt
os
os/exec
os/user
path/filepath
strconv
strings
syscall
time
true
echo whitelisted returns stdout
binary not in whitelist rejected without spawn
timeout kills process and sets TimedOut
empty whitelist returns error
stdin payload passes to process
output exceeding MaxOutputBytes is truncated
absolute path in whitelist matches resolved binary
functions/infra/shell_exec_whitelist_test.go functions/infra/shell_exec_whitelist.go
name desc
opts.Cmd argv completo. Cmd[0] es el binario (path absoluto o nombre en PATH). Obligatorio, no puede estar vacío.
name desc
opts.BinariesAllowed Whitelist de binarios permitidos. EMPTY = rechaza todo sin spawn (defense in depth). Entry con / se compara con path resolvido; entry bare name se compara con basename del resolvido.
name desc
opts.Env Variables de entorno KEY=VAL. Si vacío, se aplica entorno mínimo: PATH=/usr/bin:/bin, HOME, USER, LANG.
name desc
opts.WorkingDir Directorio de trabajo. Si vacío usa HOME del usuario actual.
name desc
opts.TimeoutSeconds Timeout máximo en segundos. Default 30. Al expirar: SIGTERM → espera 1s → SIGKILL.
name desc
opts.StdinPayload Bytes a pasar como stdin al proceso. Nil/vacío = sin stdin.
name desc
opts.MaxOutputBytes Límite de bytes por stream (stdout y stderr por separado). Default 1 MB. Activa Truncated=true si se supera.
name desc
opts.User Usuario para ejecutar el proceso (nombre o 'uid:gid'). Requiere uid=0. Vacío = usuario actual.
ShellExecResult con ExitCode, Stdout, Stderr, Duration (ms), Truncated y TimedOut.

Ejemplo

result, err := infra.ShellExecWhitelist(infra.ShellExecOpts{
    Cmd:             []string{"ls", "-la", "/tmp"},
    BinariesAllowed: []string{"ls", "cat", "echo", "id"},
    TimeoutSeconds:  10,
    MaxOutputBytes:  64 * 1024,
})
if err != nil {
    log.Fatalf("exec rejected: %v", err)
}
fmt.Printf("exit=%d duration=%dms truncated=%v timedOut=%v\n",
    result.ExitCode, result.Duration, result.Truncated, result.TimedOut)
fmt.Println(result.Stdout)

Con stdin:

result, err := infra.ShellExecWhitelist(infra.ShellExecOpts{
    Cmd:             []string{"cat"},
    BinariesAllowed: []string{"cat"},
    StdinPayload:    []byte("payload from device_agent"),
})

Con path absoluto en whitelist:

result, err := infra.ShellExecWhitelist(infra.ShellExecOpts{
    Cmd:             []string{"/usr/bin/id"},
    BinariesAllowed: []string{"/usr/bin/id"},
})

Cuando usarla

Cuando recibes requests externos (Element Matrix, webhook, agente) que especifican un comando a ejecutar en el host, y necesitas garantizar que solo binarios pre-aprobados corren, sin posibilidad de shell injection. Reemplaza exec.Command directa en device_agent o cualquier sandbox que acepte comandos de fuentes no confiables.

Gotchas

  • Empty whitelist rechaza por diseño: BinariesAllowed: []string{} devuelve error inmediato. NUNCA construyas la whitelist dinámicamente desde input externo.
  • PATH default mínimo (/usr/bin:/bin): si tu binario está en /usr/local/bin u otro directorio, añádelo explícitamente a Env o usa el path absoluto en Cmd[0] y en BinariesAllowed.
  • SIGTERM+1s+SIGKILL: algunos procesos pueden ignorar SIGTERM. SIGKILL es forzoso pero puede dejar recursos abiertos (ficheros, sockets). Diseña el proceso objetivo para manejar SIGTERM limpiamente.
  • Truncate aplica POST-exec: no es streaming. Si el proceso produce 10 GB de output, el buffer crece hasta ese tamaño en RAM antes de truncar. Para procesos con output gigante usa pipes propios o un wrapper de streaming.
  • User switch requiere uid=0: en un entorno sin root (contenedor sin privilegios, proceso normal), pasar User != "" siempre devuelve error. Verificar con os.Getuid() == 0 antes si el campo es opcional.
  • Cmd[0] es el nombre del binario en PATH pero la whitelist puede tener paths absolutos o bare names. Precedencia: entry con / compara contra el path resolvido por LookPath; entry sin / compara contra filepath.Base del path resolvido. Ambas formas son válidas y pueden coexistir en la misma whitelist.