Entregue um sandbox, não um emaranhado de scripts: um guia de CTO para VMs embutidas em 2026

Por Diogo Hudson Dias
Platform engineer examining a dashboard of sandboxed code executions and resource usage on a large monitor in a modern São Paulo office.

Seu produto será programado — pelos seus clientes ou pelos agentes de IA deles. Se você não oferecer um lugar seguro e de baixa latência para executar lógica, eles farão isso de qualquer jeito com webhooks frágeis, “cola” em planilhas ou RPA. É assim que você herda indisponibilidades que não causou e problemas de segurança que não enxerga. A solução não é “mais endpoints”. A solução é uma máquina virtual embutida: uma camada de execução em sandbox, com governança de recursos, sob o seu controle.

VMs de bytecode estão em todos os lugares — bancos de dados, CDNs, roteadores. Há um motivo: elas permitem expor poder sem ampliar demais o raio de impacto. Com agentes de IA agora gerando código “bom o suficiente” sob demanda, ou você projeta uma superfície de extensão contida ou assiste ao seu caminho de requisições virar uma plataforma sombra.

Por que isso importa agora

Dois movimentos convergiram em 2024–2026:

  • O código de cola gerado por agentes explodiu. Times estão conectando suas APIs com scripts escritos por LLMs. Funciona — até que timeouts, retries e “data races” batem em produção. O paper “Constraint Decay” circulou por um motivo: guardrails se degradam a menos que o ambiente os imponha.
  • As VMs de bytecode ficaram “sem graça” — no bom sentido. A indústria percebeu (de novo) que pequenos interpretadores e motores WASM são confiáveis, rápidos e portáveis. Veja o ressurgimento de VMs leves em lugares improváveis: proxies, engines de storage e até frameworks de UI. Não é sobre novidade; é sobre controle previsível.

Se “vamos só adicionar mais um webhook” é sua resposta para customização, você está terceirizando sua confiabilidade para o que quer que rode do outro lado do fio. Tudo bem para eventos de baixo valor. É imprudente para autorização, billing, roteamento e transformações de dados no hot path (caminho crítico).

Você realmente precisa de uma VM embutida?

Use esta matriz de decisão. Se dois ou mais forem verdadeiros, você precisa de um sandbox dentro do produto:

  • Lógica específica por tenant está se acumulando como feature flags e condicionais (≥10 ramificações exclusivas por cliente em fluxos centrais).
  • Suporte ou solutions engineers estão entregando “scripts pontuais” várias vezes por mês.
  • Seu p95 compatível com SLO é ≤100 ms e você ainda precisa de decisões por requisição (ex.: pricing dinâmico, roteamento ou política ABAC) sem ida e volta de rede.
  • Você lida com dados regulados e não pode confiar que código do cliente fora do seu perímetro os tratará corretamente.
  • Você espera que agentes de IA escrevam ou modifiquem lógica e quer reforço (timeouts, limites de memória, checagem de capacidades) que o agente não consiga contornar.

O que não fazer de cara: não embuta um runtime completo de Python ou Node e dê o assunto por encerrado, não suba um container por requisição e não dependa de webhooks para correção no hot path. Os três parecem fáceis; os três são armadilhas de SLO e segurança.

Escolhendo o engine: Lua, JavaScript ou WASM?

Você tem quatro famílias realistas para escolher. Aqui vai um recorte pragmático com base em footprint, segurança e ergonomia.

Lua (PUC Lua ou LuaJIT)

  • Por que é bom: Minúsculo (interpretador <300 KB), provado em Nginx/OpenResty e jogos, API de embed simples, fácil de medir (contagem de instruções) e limitar memória.
  • Cuidado com: LuaJIT é rápido, mas mais difícil de isolar e menos determinístico. PUC Lua “vanilla” é mais lento, porém mais seguro e previsível.
  • Encaixe: Ótimo para política, roteamento, templating e pequenas transformações de dados em microsegundos a poucos milissegundos.

JavaScript embutível (QuickJS, Duktape)

  • Por que é bom: Sintaxe familiar para desenvolvedores, sem JIT (determinístico), pequeno (~200–500 KB), desempenho sólido para lógica de negócio. O QuickJS em particular tem uma API C limpa.
  • Cuidado com: Mais lento que o V8 por alguns múltiplos. Você precisa fornecer e restringir fortemente quaisquer APIs do host (fetch, crypto, time).
  • Encaixe: Quando seus clientes vivem em JS e você quer latência in-process sem os 50–100 MB de overhead de isolates V8/Node.

WebAssembly (Wasmtime, WasmEdge, WAMR, Wasmer)

  • Por que é bom: Isolamento forte por padrão, chamadas ao host baseadas em capacidades, multi-linguagem via Rust/Go/TinyGo/C, compilação AOT para velocidade e boa medição (fuel) e limites de memória.
  • Números esperados: Instanciação fria ~1–5 ms para módulos pequenos com AOT; invocações em regime podem ficar sub-millisecond. Memória por instância normalmente 1–10 MB, a depender da linear memory e stacks.
  • Encaixe: Quando você precisa de um boundary de segurança mais forte, quer múltiplas linguagens ou planeja escalar para milhares de sandboxes concorrentes com isolamento previsível.

V8/Node e CPython

  • Por que são tentadores: Força do ecossistema. Seus clientes pedem por eles.
  • Por que raramente recomendamos in-process: Memória pesada (Node frequentemente ≥50–80 MB por isolate), cold starts imprevisíveis (100+ ms) e superfície de ataque maior. O CPython traz dores de empacotamento e extensões nativas, além do GIL.
  • Encaixe: Apenas out-of-process, em workers em pool — se você puder isolá-los atrás de um boundary RPC estrito e aceitar latência maior.

Segurança e SLOs são o produto. Projete o sandbox primeiro.

Escolha o engine só depois de especificar os guardrails. Esses limites são a diferença entre “programável” e “roleta do pager”.

Limites de recursos que realmente pegam

  • CPU: Timeouts rígidos (ex.: orçamento de 10–20 ms no hot path; 250 ms no assíncrono). Para WASM, use fuel metering. Para Lua/JS, interrupções a cada N bytecodes.
  • Memória: Limite por execução (ex.: 16–64 MB) e totais por tenant. Rejeite ou faça throttle quando os pools aquecerem; não deixe o kernel OOM decidir seu destino.
  • I/O: Negação por padrão para rede e filesystem. Exponha apenas chamadas explícitas do host: getSecret, kv.get/put, http.request(allowlist), emitMetric, log, now, uuid, e nada além disso.
  • Determinismo: Forneça um clock monotônico e RNG com seed via chamadas do host. Proíba Date.now()/random() “ambientais” quando possível para você poder reexecutar.

Conter o raio de impacto

  • Manifesto de capacidades: Todo script/módulo declara as APIs de que precisa. Reforce na carga. Sem poderes ocultos.
  • Multi-tenant: Pools separados por tenant. Se um se comportar mal, os outros continuam rápidos. Considere cgroups para pools out-of-process.
  • Fail-open vs fail-closed: Autorização e billing devem ser fail-closed (falha fechada). Transformações podem ser fail-open (falha aberta) com alertas, se você optar. Decida por rota e documente.

Observabilidade que torna o código “de produção”

  • Logs estruturados com IDs de correlação para cada invocação. Registre uso de capacidades e eventos de truncamento.
  • Métricas: Contagem, duração, taxa de erro por função, por tenant. Acompanhe p50/p95/p99 e consumo de orçamento. Exporte como painéis estilo RED/USE.
  • Perfis e amostragem: Amostre execuções periodicamente para verificar hotspots sem violar limites de dados (redija ou use replay sintético).

A matemática do SLO (para você não chutar)

Você consegue estimar o impacto de CPU da lógica embutida com uma linha:

CPU cores ≈ RPS × avg_ms / 1000

Exemplo: você processa 2.000 RPS por um sandbox QuickJS ou Lua que leva em média 2 ms por invocação. Isso dá ≈4 cores de CPU constante. Se seu p95 é 100 ms e você aloca 10 ms para lógica em sandbox, pode rodar cinco checagens por requisição e consumir ≈10 cores a 2.000 RPS. O ponto é: dá para bancar muita lógica se você mantiver in-process e sob poucos milissegundos.

Memória é onde o WASM pesa. Se você pré-aquece 500 instâncias WASM com limite de 8 MB para evitar cold starts no cluster, isso reserva 4 GB — ainda mais barato do que depurar timeouts de webhook pela internet pública.

Empacotamento e supply chain: trate código como produto

Não aceite “copiar/colar um script em uma textarea” como estado final. Você precisa de proveniência, rollbacks e checagens de compatibilidade.

  • Formato de pacote: Para WASM, armazene módulos como artefatos OCI em um registry privado com manifesto WIT/component. Para Lua/JS, empacote como tarballs assinados com manifesto de capacidades.
  • Assine tudo: Use Sigstore/cosign. Verifique assinaturas na carga. Registre digest + assinante no audit log.
  • Versionamento: Fixe por tenant. Faça rollouts em estágios (1%, 10%, 50%, 100%). Proporcione rollback instantâneo via digest anterior. Mantenha retenção de 30–90 dias.
  • SBOM: Mantenha uma SBOM leve para módulos (linguagem de origem, versão do compilador/SDK, dependências) para forense e compliance.

Padrões de arquitetura que funcionam

Padrão A: micro-scripts in-process (menor latência)

  • Engine: PUC Lua ou QuickJS embutidos no seu serviço de API.
  • Casos de uso: Decisões de política (ABAC), transformações de request/response, roteamento, validações E2E leves.
  • Mecânica: Pré-compile e faça cache dos scripts. Ejetar por LRU o código frio. Interrompa ao estourar o orçamento. Zero chamadas de rede no hot path a menos que explicitamente permitido.
  • Prós: Latência sub-millisecond a poucos milissegundos, infraestrutura mínima, operação mais simples.
  • Contras: Isolamento mais fraco que WASM; cuidado com as APIs do host.

Padrão B: workers WASM out-of-process (isolamento mais forte)

  • Engine: Wasmtime/WasmEdge em um serviço dedicado. Pools de instâncias pré-aquecidas por tenant/conjunto de capacidades.
  • Casos de uso: Transformações mais pesadas, módulos de terceiros não confiáveis, kits multi-linguagem.
  • Mecânica: RPC sobre HTTP/2 ou gRPC. Na sua própria rede, h2c (HTTP/2 sem TLS) reduz overhead de TLS; o Go 1.24 melhorou a ergonomia do h2c, mas use apenas em links confiáveis (cripte na camada de transporte se necessário: mTLS ou service mesh).
  • Prós: Isolamento por padrão, contabilidade de recursos mais fácil, mais seguro para código desconhecido.
  • Contras: +1 salto de rede e custo de serialização; mais partes móveis de infraestrutura.

Padrão C: pipelines assíncronos (duráveis, porém mais lentos)

  • Engine: Igual ao B, mas na frente um queue/stream (Kafka, NATS JetStream, SQS).
  • Casos de uso: Enriquecimentos em lote, transformações de payloads grandes, jobs de fan-out onde latência sub-segundo não é requisito.
  • Prós: Backpressure natural, retries, dead-letter queues.
  • Contras: Fora do caminho de requisição; consistência eventual se aplica.

Interop com engines de política e workflows

Nem tudo precisa de uma VM de propósito geral. Use Open Policy Agent (OPA) para lógica de autorização pura, onde você quer decisões de microssegundos a milissegundos e uma linguagem declarativa (Rego). Trate o OPA como uma “VM” especializada que brilha em políticas e filtragem de dados. Para orquestrações de longa duração, um engine de workflow (Temporal, Camunda) é adequado — só não confunda orquestração com sandbox de execução. Mantenha a camada de política e a de extensões separadas para cada uma escalar de forma independente.

Checklist de segurança que você vai usar de verdade

  • Zero autoridade ambiente: Toda capacidade é injetada; nada global fica acessível por padrão.
  • Validação na carga: Faça lint e checagem estática dos módulos para padrões proibidos. Rejeite em imports desconhecidos.
  • Metering em runtime: Orçamentos de tempo, memória e contagem de chamadas ao host por invocação.
  • Proveniência de código: Assine módulos. Registre assinante, digest e cadeia de aprovação.
  • Fuzz no boundary com o host: Faça property testing dos shims de hostcall. Os bugs que você vai embarcar estão no boundary, não dentro da VM.
  • Isolamento entre tenants: Espaços de chave, pools e cotas separados por tenant. Nada de caches cruzando tenants.

Construir ou comprar?

Não existe um “motor de extensões” turnkey que sirva para todo stack, mas você não precisa começar do zero.

  • WASM: Wasmtime e WasmEdge são maduros e bem documentados. Use seu fuel metering e limites de memória. Prefira compilação AOT no deploy para velocidade.
  • JS: QuickJS oferece um caminho de embed limpo e desempenho previsível. Mantenha a standard library mínima e exponha fetch/storage como chamadas explícitas do host.
  • Lua: PUC Lua é o padrão seguro. Combine com uma standard library pequena e módulos guiados por políticas.
  • Empacotamento: Use registries OCI para módulos, Sigstore para assinatura e um serviço interno de “extensions controller” para rollout e telemetria.

Se você tem um time de plataforma pequeno, comece com o Padrão A para decisões críticas e de baixa latência e adicione o Padrão B para extensões não confiáveis ou mais pesadas à medida que a demanda crescer. Uma equipe nearshore pode cuidar do extensions controller e do runtime do sandbox como um produto de plataforma — trate-o como um PaaS interno com SLAs.

Um plano de 90 dias

Dias 0–30: comprove o control plane

  • Escolha um engine (QuickJS ou PUC Lua) e um caso de uso-alvo (por exemplo, transformação de request antes da persistência).
  • Implemente manifesto de capacidades, timeouts por invocação (10 ms) e quotas por tenant.
  • Construa o caminho mínimo de empacotamento: upload de módulo assinado, carga, habilitar/desabilitar e pinagem por tenant.
  • Entregue dashboards de contagem/duração/erro e adicione logs estruturados.

Dias 31–60: leve a produção e expanda

  • Introduza workers WASM out-of-process para uma classe de módulos não confiáveis. Pré-aqueça pools; imponha limites de memória de 16–32 MB.
  • Adicione testes reproduzíveis (replay) com tempo e RNG determinísticos para cada módulo.
  • Implemente rollouts em estágios (1%/10%/50%/100%), rollback instantâneo por digest e audit log para mudanças.
  • Endureça o boundary com o host: faça fuzz nas chamadas, adicione rate limits por capacidade.

Dias 61–90: escale e entregue as chaves aos clientes

  • Exponha um console do desenvolvedor com linting, dicas de tipo para chamadas do host e um preview em sandbox usando fixtures similares à produção.
  • Adicione replicação multi-região para módulos e pools por região. Mantenha o código onde estão os dados; nada de execução cross-border sem consentimento.
  • Codifique um processo de revisão de extensões (aprovação de segurança+SRE) e uma política de depreciação para APIs do host.
  • Documente um RACI claro: quem aprova, quem pode publicar, quem pode fazer rollback.

Custos, sem rodeios

  • Engenharia: 2–4 engenheiros sêniores por 6–12 semanas conseguem entregar um v1 crível (controller, engine in-process, workers WASM básicos, dashboards). Isso é mais barato do que colocar confiabilidade a posteriori em uma proliferação de webhooks.
  • Infra: Espere poucos cores e alguns GB de RAM para a maioria dos volumes B2B em regime. Você gastará mais no seu banco do que nessa camada se mantiver orçamentos por invocação pequenos.
  • Segurança/compliance: Assinatura de código e audit logs adicionam fricção no início, mas reduzem seu tempo de incidente pela metade quando algo dá errado. Proveniência é combustível de resposta a incidentes.

E os agentes de IA escrevendo extensões?

Esse é o ponto. Dê aos agentes uma superfície restrita e tipada da qual não possam escapar. Para WASM, defina interfaces WIT e gere stubs; para Lua/JS, publique um SDK de capacidades tipado (declarações TypeScript ajudam mesmo se você embutir Lua). Cada chamada de ferramenta é registrada e orçada. Agentes continuam podendo produzir código, mas o runtime faz cumprir a realidade.

Os trade-offs, às claras

  • Lua/QuickJS in-process oferecem latência mínima e isolamento mínimo. Ótimos para código confiável ou revisado.
  • WASM out-of-process adiciona latência e infraestrutura, mas compra um boundary mais forte para código não confiável ou necessidades multi-linguagem.
  • Node/Python parecem amigáveis ao desenvolvedor, mas são operacionalmente pesados. Coloque-os atrás de um boundary RPC ou não os embarque.
  • Apenas webhooks continuam úteis para eventos de baixo valor. Não finja que são confiáveis o suficiente para decisões centrais.

Palavra final

Seu backlog futuro não é mais funcionalidade, é mais variabilidade. A forma mais barata de entregá-la é productizar a própria variabilidade. Uma VM embutida com guardrails de verdade permite dizer “sim” à lógica específica por cliente e à cola escrita por IA sem sacrificar seus SLOs. Entregue um sandbox. Torne isso banal. E deixe seus clientes — e seus agentes — construírem em cima.

Pontos-chave

  • Não terceirize correção para webhooks no hot path; entregue uma VM embutida.
  • Lua/QuickJS são rápidos e minúsculos para decisões in-process; WASM adiciona isolamento para código não confiável.
  • Desenhe os guardrails primeiro: timeouts, limites de memória e um manifesto de capacidades estrito.
  • Estime custo com cores ≈ RPS × avg_ms / 1000; dá para bancar lógica de milissegundos.
  • Empacote como produto: módulos assinados, versões fixadas, rollouts em estágios e auditoria completa.
  • Separe padrões: in-process para baixa latência, out-of-process para isolamento, assíncrono para trabalho pesado.
  • Dê aos agentes de IA uma superfície restrita e tipada; deixe o runtime impor a realidade.

Ready to scale your engineering team?

Tell us about your project and we'll get back to you within 24 hours.

Start a conversation