Defesa em profundidade para entrada, saída e escalada humana

English summary: LLM guardrails are not a bag of content filters. They are a governable runtime that separates model proposals from authorized effects through layered policy enforcement, narrow tool permissions, sandboxing, budgets, human approval, audit evidence, adversarial evals, and explicit fail-open or fail-closed decisions.
Published: July 2026
Reading time: 24 minutes
Keywords: #LLMOps #Guardrails #LLMEngineering #AISecurity #ToolPermissions #Sandboxing #HumanInTheLoop #RuntimeGovernance
Seu agente leu uma fatura, encontrou um e-mail no documento e decidiu enviá-la. A resposta estava em JSON válido. O tom era confiante. O destinatário estava errado.
Esse incidente não é, em primeiro lugar, uma falha de linguagem. É uma falha de arquitetura: o sistema confundiu uma proposta probabilística com uma decisão autorizada.
Tese: guardrails de produção são um runtime governável entre intenção e efeito — não uma lista de palavras proibidas ao redor do prompt.
Um filtro pode ser uma peça desse runtime. Não é o runtime inteiro. A fronteira real precisa decidir quais dados entram, qual contexto é confiável, quais ferramentas existem, com quais permissões, por quanto tempo, dentro de qual orçamento, quando uma pessoa precisa aprovar e quais evidências permitem explicar ou reverter o que aconteceu.
Vamos construir primeiro a menor versão que prova essa separação.
Crie guardrails.mjs com o código abaixo e rode node guardrails.mjs. Dependência: Node.js 20 ou superior. O exemplo não chama um modelo nem uma API externa; ele simula uma resposta do LLM para deixar a fronteira de autorização visível.
import { createHash } from 'node:crypto';
const TOOL_POLICY = Object.freeze({
lookup_invoice: {
effect: 'read',
scopes: ['billing:read'],
maxCalls: 3,
approval: false,
},
send_invoice: {
effect: 'external_write',
scopes: ['billing:write'],
maxCalls: 1,
approval: true,
},
});
const EMAIL = /\b[\w.+-]+@[\w-]+(?:\.[\w-]+)+\b/i;
const INJECTION = /ignore (previous|all) instructions|reveal .*system prompt/i;
function block(code, detail) {
return { kind: 'blocked', code, detail };
}
function parseProposal(raw) {
let value;
try {
value = JSON.parse(raw);
} catch {
return block('output.invalid_json', 'A resposta não é JSON.');
}
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return block('output.invalid_schema', 'Contrato de saída inválido.');
}
const allowedKeys = ['summary', 'tool', 'args'];
const exactKeys = Object.keys(value).every((key) => allowedKeys.includes(key));
const validArgs = value.args && typeof value.args === 'object' &&
!Array.isArray(value.args) &&
Object.values(value.args).every((item) => typeof item === 'string');
if (!exactKeys || typeof value.summary !== 'string' ||
typeof value.tool !== 'string' || !validArgs) {
return block('output.invalid_schema', 'Contrato de saída inválido.');
}
if (EMAIL.test(JSON.stringify(value))) {
return block('output.pii', 'A proposta contém dado pessoal.');
}
return { kind: 'parsed', value };
}
function decide({ input, rawReply, scopes, budget }) {
if (Date.now() > budget.deadlineMs) {
return block('budget.deadline', 'Tempo total esgotado.');
}
if (budget.modelCalls > budget.maxModelCalls) {
return block('budget.model_calls', 'Limite de chamadas esgotado.');
}
if (input.length > 12_000) {
return block('input.too_large', 'Input acima do limite.');
}
if (INJECTION.test(input)) {
return block('input.suspicious', 'Input requer revisão.');
}
const parsed = parseProposal(rawReply);
if (parsed.kind === 'blocked') return parsed;
const { tool, args } = parsed.value;
const policy = TOOL_POLICY[tool];
if (!policy) return block('tool.unknown', 'Ferramenta fora da allowlist.');
if (!policy.scopes.every((scope) => scopes.includes(scope))) {
return block('tool.scope_denied', 'Escopo insuficiente.');
}
if ((budget.toolCalls[tool] ?? 0) >= policy.maxCalls) {
return block('budget.tool_calls', 'Limite da ferramenta esgotado.');
}
const plan = { tool, args, effect: policy.effect };
if (policy.approval) {
const digest = createHash('sha256')
.update(JSON.stringify(plan))
.digest('hex');
return {
kind: 'needs_approval',
plan,
digest,
expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(),
};
}
return { kind: 'allowed', plan };
}
const candidate = JSON.stringify({
summary: 'Enviar a fatura aprovada ao cliente.',
tool: 'send_invoice',
args: { invoiceId: 'inv_123', customerId: 'cus_456' },
});
const result = decide({
input: 'Envie a fatura de agosto.',
rawReply: candidate,
scopes: ['billing:write'],
budget: {
deadlineMs: Date.now() + 2_000,
maxModelCalls: 4,
modelCalls: 1,
toolCalls: {},
},
});
console.log(result.kind, result.plan.tool);
// needs_approval send_invoice
O programa não tentou descobrir se o modelo “parecia seguro”. Ele avaliou propriedades que o modelo não pode conceder a si mesmo:
A saída é needs_approval send_invoice. Isso é sucesso: o modelo propôs; o runtime interrompeu.
O exemplo é deliberadamente incompleto. A expressão regular não detecta toda prompt injection ou PII, o parser não substitui uma biblioteca de schema, o contador não é distribuído e nenhum sandbox foi criado. Também faltam autenticação, isolamento por tenant, armazenamento durável de aprovação e um executor idempotente. O objetivo é provar uma invariante pequena antes de adicionar infraestrutura:
Nenhum texto produzido pelo modelo atravessa diretamente a fronteira de efeitos colaterais.
Imagine uma sala de controle industrial. Um estagiário pode observar painéis, interpretar sinais e sugerir “feche a válvula 7”. Ele não recebe a chave mestra da planta. Um controlador verifica se a válvula existe, se aquela pessoa pode operá-la, se a pressão permite a mudança, se outra ação já está em andamento e se a operação exige dupla confirmação.
O LLM ocupa a posição do estagiário: excelente para interpretar contexto ambíguo e propor próximos passos; inadequado para ser a própria autoridade que valida sua proposta.
O runtime é a sala de controle. Ele transforma intenção em uma máquina de estados observável:
Essa arquitetura não torna o modelo determinístico. Ela torna determinística a forma como o sistema responde à incerteza.
Antes de escolher controles, vale nomear o que costuma quebrar. Caso contrário, “adicionar guardrails” vira uma tarefa sem propriedade verificável.
| Falha observada | Diagnóstico | Por que a correção ingênua falha | Propriedade a restaurar |
|---|---|---|---|
| O prompt diz “nunca envie dados”, mas o agente envia | Política crítica existe apenas em linguagem natural | O mesmo componente interpreta a regra e decide se a obedece | Enforcement fora do modelo, com DLP e permissão no executor |
| O JSON é válido, mas cancela o pedido errado | Validação sintática foi confundida com autorização | Schema prova forma, não identidade, intenção, tenant ou estado | Policy check contextual imediatamente antes do efeito |
| Uma página recuperada faz o agente chamar uma tool | Conteúdo externo perdeu proveniência e virou instrução | Delimitadores ajudam o modelo, mas não removem a capacidade perigosa | Dados não confiáveis rotulados + tool allowlist + menor privilégio |
| Um loop consome milhares de tokens e chamadas | Não há orçamento global nem local | Timeout HTTP não limita retries, fan-out ou custo acumulado | Deadline, quotas e contadores compartilhados por execução |
| Uma pessoa “aprova”, mas os argumentos mudam depois | Aprovação não está vinculada ao plano executado | Revisão de uma descrição vaga não autoriza os bytes finais | Hash, expiração, identidade e idempotency key vinculados ao plano |
| O incidente é descoberto, mas ninguém o explica | Log é texto bruto sem versão de política ou decisão | Mais prompt em log aumenta exposição sem provar o caminho | Trilha mínima, redigida, correlacionável e com owner |
Há outras falhas — envenenamento de corpus, supply chain, indisponibilidade do provedor, alucinação factual —, mas essas seis expõem a raiz operacional: capacidade, autoridade e evidência foram misturadas.
“Conteúdo seguro” é apenas um eixo. Uma saída inofensiva pode acionar uma ferramenta poderosa; uma saída desconfortável pode não produzir efeito algum. Classifique cada rota por cinco dimensões:
Uma matriz simples já produz decisões melhores do que uma coleção uniforme de filtros:
| Classe | Exemplo | Default de runtime | Evidência mínima |
|---|---|---|---|
| R0 — informacional | resumir documentação pública | permitir com limites e citações quando necessário | request ID, modelo/versão, latência |
| R1 — leitura privada | consultar pedido do próprio tenant | autenticar, filtrar tenant, minimizar dados, controlar saída de rede | principal, escopos, fontes acessadas |
| R2 — escrita reversível | criar rascunho interno | preview, idempotência, quota e opção de desfazer | diff, policy version, resultado |
| R3 — efeito consequente | enviar e-mail, cancelar pedido, mudar permissão | fail closed, aprovação ligada ao plano, rechecagem no executor | aprovador, digest, estado antes/depois |
| R4 — crítico ou irreversível | pagamento alto, produção, segredo, dado regulado | capability dedicada, dupla aprovação ou proibição | trilha imutável, owner, runbook e revisão posterior |
Os limiares são decisões de produto, segurança e domínio. Não existe um valor universal que transforme R2 em R3. Uma transferência de R$ 10 pode ser trivial em um sandbox financeiro e proibida em uma conta real sem mandato. A política precisa receber o contexto; o modelo não deve inventá-lo.
R0–R4 é uma classificação operacional local, não uma renomeação da OWASP. Em 12 de agosto de 2026, a edição mais recente listada pela OWASP era o LLM Top 10 2026, publicada em 3 de agosto. As páginas LLM01, LLM06 e LLM10 citadas ao final permanecem identificadas como edição 2025; por isso, a versão aparece explicitamente em vez de ser tratada como taxonomia atemporal.
Defesa em profundidade não significa repetir o mesmo detector sete vezes. Significa colocar mecanismos independentes nas fronteiras onde eles têm autoridade.
Comece com invariantes executáveis, não com frases como “o agente deve ser seguro”. Para a rota de faturas:
send_invoice sem billing:write;Essas propriedades orientam código, teste e observabilidade. Se você não consegue dizer como provar a invariante, ainda não terminou a especificação.
O runtime autentica o principal antes de montar contexto. Arquivos, RAG, páginas, e-mails e resultados de tools carregam origem, tenant, classificação e nível de confiança. Conteúdo recuperado continua sendo dado não confiável mesmo quando parece uma instrução.
Minimize antes do modelo. Se a tarefa só precisa de [CUSTOMER_17], não envie nome, CPF e endereço. A tabela de reidentificação fica fora do contexto, com controle de acesso e retenção próprios. Mas não chame pseudonimização de anonimização: a LGPD define dado pessoal e anonimização pelo risco razoável de associação ou reversão. A decisão jurídica depende do caso; este texto é orientação de engenharia, não aconselhamento jurídico.
Peça um plano estreito: tool, args, justification, citations e confidence quando a rota precisar. Valide schema estrito, tamanhos, enums e encoding no consumidor. Campos desconhecidos são erro; “consertar” JSON silenciosamente pode mudar intenção.
Saída estruturada reduz ambiguidade de integração. Não concede permissão. Um objeto perfeitamente válido ainda pode apontar para o tenant errado, usar dados expirados ou propor uma ação proibida.
O motor de políticas recebe fatos verificados — principal, tenant, ferramenta, argumentos, classificação de dados, estado atual, risco, orçamento e versão da política — e retorna allow, deny, degrade ou require_approval com um código de motivo estável.
Mantenha essa função determinística sempre que possível. Um segundo LLM pode ajudar a detectar conteúdo difícil, mas não deve ser a única autoridade para escopo, limite financeiro ou acesso. Detecção probabilística informa a política; não substitui IAM.
Entre aprovação e execução, o mundo pode mudar. O executor verifica novamente identidade, tenant, escopo, digest, expiração, budget e estado do recurso. A operação usa idempotency key para que retry não duplique o efeito.
Essa rechecagem evita o clássico time-of-check/time-of-use: o pedido estava cancelável quando o plano foi criado, mas já foi enviado quando a tool tentou cancelá-lo.
Uma trilha útil responde: quem pediu, qual rota decidiu, qual versão de política rodou, qual ferramenta foi proposta, por que foi permitida ou negada, quem aprovou, qual efeito ocorreu e como o sistema recuperou.
Ela não precisa guardar o prompt bruto. Prefira IDs, hashes de conteúdo já redigido, classificações e reason codes. Criptografia, retenção, acesso e deleção da trilha fazem parte do desenho; “é só log” não elimina obrigações de privacidade.
Productos gratuitos y de pago para convertir ideas en una base que puedas lanzar.
13 productos disponiblesContinúa explorando temas similares

--- title: "Introdução ao LLMOps: O Ciclo de Vida de Aplicações de LLM em Produção" subtitle: "Como orquestrar modelos de fundação com gateway, roteamento, cache, guardrails e eval

English summary: Production LLM routing is an executable policy for choosing models under quality, cost, latency, privacy, and failure constraints. This guide builds a minimal rout

Observabilidade generativa não é log com texto bonito. É prompt completo, versão do modelo, tools usadas, latência, custo e veredito do eval no mesmo span. Fechamento da série LLMOps em Produção.
Un checklist práctico para validar seguridad, costos, evaluaciones y observabilidad antes de llevar tu app con IA a producción.
Templates probados en producción, usados por desarrolladores. Ahorra semanas de setup en tu próximo proyecto.
Ferramentas devem representar operações de domínio, não atalhos administrativos. Compare:
run_sql(query), shell(command), admin_request(method, url, body);find_invoice(customerToken, month), draft_invoice_email(invoiceId), send_approved_invoice(approvalId).Cada tool precisa declarar:
read, local_write, external_write, privilege_change;Credencial temporária e restrita é parte da permissão. Se o processo do agente recebe uma chave que pode apagar todo o bucket, nenhuma frase no system prompt reduz essa capacidade real.
Permissão responde “o que esta identidade pode fazer?”. Sandbox responde “onde e com quais recursos este processo pode operar?”. Você precisa das duas.
Para execução de código ou ferramentas não confiáveis, restrinja pelo menos:
Sandbox não corrige uma autorização errada. Se você permite payments.example e injeta uma credencial de pagamento ampla, o processo isolado ainda pode pagar ao destinatário errado. E permissão estreita sem isolamento ainda pode permitir leitura acidental do host. São controles ortogonais.
Um timeout por requisição não limita um agente que abre subtarefas, repete chamadas ou alterna provedores. Defina orçamentos em múltiplos níveis:
| Orçamento | Exemplo | Ao esgotar |
|---|---|---|
| Parede | 30 s por turno, 5 min por job | cancelar filhos e devolver estado parcial seguro |
| Modelo | 6 chamadas, 40 mil tokens | parar o loop; não trocar de modelo silenciosamente |
| Tool | 3 leituras, 1 escrita | negar novas chamadas com reason code |
| Dinheiro | R$ 0,80 por execução, teto diário por tenant | degradar rota ou bloquear conforme criticidade |
| Fan-out | 4 subtarefas concorrentes | enfileirar ou rejeitar expansão |
| Output | 1 MB por tool, 8 mil tokens ao usuário | truncar com marcador ou falhar antes do parser |
O contador deve ser compartilhado entre novas tentativas e subagentes. O cancelamento precisa se propagar. O fallback de provedor consome o mesmo orçamento e deve aparecer na trilha. Caso contrário, “resiliência” vira consumo sem limite e comportamento impossível de reproduzir.
Human-in-the-loop existe somente se o estado para antes do efeito. Uma notificação depois do envio é auditoria tardia, não aprovação.
O aprovador deve ver o plano exato: ação, alvo, diff, valor, origem dos dados, argumentos finais, risco, expiração e efeito esperado. A aprovação fica vinculada a:
Qualquer mudança invalida o aceite. A UI deve oferecer aprovar, rejeitar e editar criando novo plano; nunca “aprovar qualquer variação”.
Aprovação tem custo e pode virar teatro. Se revisores aprovam centenas de ações idênticas sob pressão, a taxa de rejeição cai sem que o risco caia. Automatize rotas comprovadamente baixas; mantenha humanos onde julgamento de domínio muda o resultado; proíba o que não pode ser revisado com segurança.
“Sempre fail closed” parece seguro até o detector de PII ficar indisponível e bloquear um canal de emergência. “Sempre fail open” parece resiliente até o serviço de autorização cair e liberar uma transferência. O comportamento padrão depende do impacto de uma decisão errada e do custo de indisponibilidade.
| Falha | Contexto | Decisão recomendada | Motivo |
|---|---|---|---|
| IAM ou tenant indeterminado | qualquer dado privado ou tool | fail closed | não existe autoridade verificável |
| Serviço de aprovação indisponível | escrita R3/R4 | fail closed e preservar o plano | indisponibilidade é menos grave que efeito não autorizado |
| Detector probabilístico indisponível | resumo de documento público R0 | fail open degradado, sem tools e com limite menor | não há efeito externo nem dado privado |
| Telemetria central indisponível | leitura privada R1 | buffer local redigido; bloquear se não houver trilha recuperável | auditabilidade pode ser requisito da rota |
| Provedor principal indisponível | rota informacional | fallback explícito dentro do mesmo budget | mantém serviço sem ampliar capacidade |
| Sandbox indisponível | execução de código | fail closed | executar no host muda o raio de impacto |
Fail open não significa ignorar política. Significa entrar em um modo degradado previamente especificado: menos dados, nenhuma escrita, menos ferramentas, budget menor e resposta marcada. Se o modo não foi testado, ele não é um fallback; é improviso.
Este YAML não é um produto pronto, mas força as decisões certas a aparecerem em review. Adapte os nomes ao seu policy engine.
route: invoice_assistant.send
owner: billing-platform
policy_version: 3.2.0
risk_class: R3
data:
allowed: [invoice_id, customer_token, billing_month]
forbidden: [raw_email, tax_id, payment_card]
tenant_isolation: required
tool:
name: send_approved_invoice
effect: external_write
required_scopes: [billing:write]
max_calls: 1
timeout_ms: 3000
egress_allowlist: [billing-api.internal]
sandbox: required
budget:
wall_time_ms: 30000
model_calls: 4
input_tokens: 20000
output_tokens: 4000
max_cost_brl: 0.80
approval:
required: true
bind: [principal, tenant, tool, args_digest, policy_version]
expires_in_seconds: 300
approver_role: billing-operator
failure:
authorization_unknown: fail_closed
detector_unavailable: fail_closed
provider_unavailable: preserve_plan
evidence:
log: [request_id, principal_id, tenant_id, decision, reason_code,
tool, args_digest, approver_id, latency_ms, outcome]
redact: [prompt, raw_tool_output, personal_data]
retention_days: 90
success:
critical_policy_violations: 0
duplicate_side_effects: 0
p95_added_latency_ms: 400
max_false_block_rate: 0.01
O valor está menos na sintaxe do que na revisão: produto aceita o modo degradado? Segurança aceita o egress? Billing assume o runbook? Privacidade aceita dados e retenção? Plataforma consegue medir os SLOs? Sem respostas, a configuração é decoração.
Um teste fraco pergunta se o modelo recusou “ignore as instruções anteriores”. Um teste útil tenta produzir o efeito proibido e inspeciona o estado final.
Monte casos em cinco famílias:
Para cada caso, registre três evidências: decisão do runtime, chamadas que chegaram ao executor e estado externo antes/depois. O texto da resposta é secundário.
Separe regressões conhecidas de um conjunto de release mantido fora do prompt de desenvolvimento. Faça red-team novamente quando mudar modelo, system prompt, tool, policy, conector, sandbox ou fluxo de aprovação. O risco é propriedade do sistema composto.
Não use “número de bloqueios” como sinônimo de segurança. Um detector que bloqueia tudo tem recall excelente e produto inútil.
| Métrica | Pergunta operacional | Segmentação necessária |
|---|---|---|
| Violações críticas executadas | Alguma invariante R3/R4 escapou? | rota, tool, policy version |
| False-block rate | Quantos casos legítimos foram negados? | idioma, tenant, classe de risco |
| Precisão de escalada | Revisores confirmam o risco sinalizado? | reason code, detector, versão |
| Taxa de rejeição humana | A aprovação encontra planos ruins ou só adiciona clique? | tool, aprovador, horário |
| Side effects duplicados | Idempotência funciona sob retry? | executor, erro anterior |
| Budget-exhaustion rate | Onde loops e fan-out consomem o teto? | modelo, workflow, tenant |
| P95/P99 de latência adicionada | Qual segurança cabe no SLO? | camada, região, modo degradado |
| Tempo para conter e recuperar | O runbook funciona durante incidente? | severidade, owner, dependência |
Defina numerador, denominador, janela e fonte. “False-block abaixo de 1%” não é verificável sem conjunto rotulado ou revisão amostral. “Zero incidentes” pode significar ausência de detecção. Combine métricas preventivas, de detecção, de impacto e de recuperação.
Guardrail sem responsável envelhece em silêncio.
| Responsável | Decisão que não pode ser terceirizada ao modelo |
|---|---|
| Produto/domínio | ação permitida, risco aceitável, quando humano agrega julgamento |
| Segurança | capabilities, threat model, sandbox, segredos, red-team e resposta |
| Plataforma de IA | runtime, policy engine, budgets, versões, eval harness e observabilidade |
| Dados/privacidade/jurídico | finalidade, minimização, base aplicável, retenção e direitos |
| Operações/SRE | SLO, modo degradado, alertas, kill switch, recuperação e postmortem |
| Dono da tool | contrato, autorização final, idempotência, compensação e runbook |
Uma pessoa pode acumular papéis em equipe pequena. As decisões continuam precisando de nomes, revisão e evidência.
tenantId, principalId, policyVersion e idempotencyKey ao plano do exemplo. Faça qualquer mudança invalidar o digest. Você terminou quando o teste prova que um plano aprovado não autoriza outro.maxCalls.Você aprendeu — e o runtime está começando a ficar governável — quando consegue demonstrar, em ambiente de teste:
Esses critérios não provam segurança universal. Eles provam propriedades delimitadas em um sistema e uma versão específicos. Mudou modelo, tool, política, dado ou fluxo? A evidência precisa ser regenerada.
O script inicial é uma catraca. Produção é o prédio inteiro.
| Toy | Produção |
|---|---|
| objeto local de policy | policy store versionado, review e rollout gradual |
| contadores em memória | quota atômica distribuída e propagada entre jobs |
| regex de injection/PII | sinais múltiplos, DLP de domínio, amostragem e revisão |
scopes passados pela função | IAM autenticado, capability temporária e tenant binding |
| plano com hash | aprovação durável, assinatura, expiração e idempotência |
| nenhum executor | executor isolado, transacional e com compensação |
console.log | trace redigido, acesso controlado, retenção e alertas |
Há trade-offs inevitáveis. Mais camadas aumentam latência, custo e superfície operacional. Sandbox reduz impacto, mas dificulta cache e debugging. Aprovação reduz autonomia e pode congestionar. Fail closed protege integridade, mas derruba disponibilidade. Detecção agressiva reduz escapes e aumenta falsos bloqueios. Logs detalhados ajudam investigação e elevam risco de privacidade.
Governança não elimina esses conflitos. Ela os torna explícitos, atribuídos e mensuráveis.
Também há limites que runtime guardrails não resolvem sozinhos: qualidade ou viés do modelo, segurança da supply chain, governança de treinamento, contrato com fornecedores, verdade factual universal, desenho jurídico da finalidade e falhas do sistema consumidor. O runtime reduz raio de impacto e cria decisões auditáveis; não emite certificado de “IA segura”.
Se você está saindo de um protótipo, não comece comprando cinco classificadores.
O primeiro ganho vem de retirar poder, não de adicionar inteligência.
Guardrails maduros não tentam convencer o LLM a nunca errar. Eles partem do pressuposto de que inputs serão ambíguos, modelos serão manipuláveis, dependências falharão e políticas mudarão.
O sistema governável preserva três separações: dado não confiável não vira instrução; proposta não vira autorização; autorização não vira efeito sem revalidação e evidência.
Quando essas separações existem, “dizer não” deixa de ser uma personalidade do modelo. Vira uma propriedade testável do runtime — com contexto, owner, custo, exceção, recuperação e prova.
Navegação da série “LLMOps em Produção”