Lição 26 · Curso de Fusão·Parte 5 · Engenharia · Proveniência e segurança
Parte 5 · Engenharia · Lição 26
Proveniência e segurança — fail-closed por padrão
Um sistema que ingere logs de chat privados, raspa a web e escreve arquivos que um agente nomeou precisa ser paranoico por construção. A ADR-0011 fixa quatro restrições permanentes — fail-closed em tudo, redação de PII antes que um byte saia da máquina, isolamento do corpus de prompts vazado e uma regra de clean-room — e elas não são slogans: aparecem como guardas reais no código que você já conheceu. Esta lição liga a política à implementação: a fronteira Zod, a defesa contra path-traversal no SkillStore, o DEFAULT_TIER = T4 fail-closed e a redação de PII antes do model call.
Vários blocos têm uma versão Simples e uma Técnica. Abra a técnica quando quiser o código real.
1
A postura — o caso desconhecido nega, nunca permite
Ao terminar esta lição você vai saber
O que significa fail-closed — e por que ele é a postura padrão, não uma exceção.
As quatro restrições da ADR-0011 e onde cada uma vive no código.
Como validateSupportPath bloqueia path-traversal listando o que permite e negando o resto.
Por que DEFAULT_TIER = T4 é a expressão mais profunda do fail-closed.
Por que a redação de PII acontece antes do egress — antes do model call, não só antes do emit.
Por que proveniência (fonte + data + hash) e fail-closed são a mesma postura por dois ângulos.
Suposições tolas (assumimos pouco de você)
Você sabe que uma ADR é um registro de decisão de arquitetura (vimos na Lição 24).
Você lembra que o motor usa Result<T, Error> e nunca lança (Lição 5; ADR-0009).
Você não precisa ter lido a Lição 12 a fundo — o SkillStore é só citado aqui.
PII = dados pessoais identificáveis. Egress = o ponto onde um dado deixa a máquina local.
Há uma ideia que sustenta tudo nesta lição: "o caso desconhecido nega, nunca permite" (ADR-0011 §1). Quando o sistema não tem certeza, ele fecha — bloqueia, parqueia ou descarta. Nunca deixa passar "no benefício da dúvida". Isso é fail-closed, e é a postura padrão para tudo que toca segurança.
Pense numa fechadura de cofre. Uma porta comum fica aberta e você precisa lembrar de trancá-la — esquecer = exposto. A porta de um cofre fica trancada por padrão; só uma combinação certa abre. Fail-closed é a porta do cofre: o estado natural é negar. Você não fica seguro por lembrar de checar — fica seguro porque o desconhecido já está trancado do lado de fora.
A diferença é só onde fica o default. Fail-open deixa o ambíguo passar; fail-closed deixa o ambíguo trancado do lado de fora.
O mesmo princípio, três caras. Fail-closed é exatamente a mesma ideia que DEFAULT_TIER = T4 (trabalho não classificado parqueia, Lição 24) e que o contrato never-throws (uma falha não tratada vira uma negação tipada, não um sucesso silencioso — ADR-0009). Negue o desconhecido, parqueie o não classificado, transforme erro em err(): três expressões de uma só postura.
Uma postura, três rostos: lista de permissões no nível do caminho, parqueamento no nível da política, e erro tipado no nível do controle de fluxo.
Uma postura, quatro restrições: o escudo fail-closed no topo e os quatro cartões da ADR-0011, cada um com o lugar exato onde vive no código.
Primeiro, um palpite
Uma função de validação de caminho recebe uma entrada que ela não reconhece — nem claramente válida, nem claramente um ataque. Numa postura fail-closed, o que ela faz?
Nega. Fail-closed enumera o que é permitido e recusa todo o resto — então qualquer coisa fora da lista de permissões, inclusive o ambíguo, vira um err() tipado. "O caso desconhecido nega, nunca permite." O oposto (fail-open) deixaria o ambíguo passar, e é justamente isso que a ADR-0011 proíbe.
2
As quatro restrições permanentes
A ADR-0011 ("Accepted", 2026-06-02) lista quatro restrições que governam o motor inteiro. Duas são checagens de runtime (fail-closed, PII); duas são disciplina de quem constrói (não seguir dados, não copiar fonte). O que as torna fortes é que cada uma tem um lugar no código — não vivem só no documento.
#
Restrição (ADR-0011)
Onde vive no código
1
Fail-closed em tudo que toca segurança — realpath guards, webhooks com HMAC, comparações em tempo constante, Zod em toda fronteira
path-safety do SkillStore; DEFAULT_TIER = T4; o safeParse de cada subsistema
2
Redação de PII antes do egress — antes do model call, não meramente antes do emit
o funil redige sinais de canal privado antes da call (mapa §3)
3
Isolamento CL4R1T4S — o corpus de prompts de fornecedor vazado é dado a analisar, nunca um comando a seguir
excluído da ingestão-como-instrução; tratado como dado inerte
4
Clean-room tac — padrões reimplementados do zero, zero código/prompts verbatim, fonte nunca publicada
toda a fusão é TypeScript do zero, não fonte copiada
Quatro restrições descendem de uma só postura: negar o desconhecido. Cada cartão tem um lugar real no código (linha azul-clay embaixo).
Flashcard · §1
Qual é "o único princípio" da ADR-0011?
clique para virar
"O caso desconhecido nega, nunca permite." Fail-closed é a postura padrão para tudo que toca segurança — a mesma ideia de DEFAULT_TIER = T4 e do contrato never-throws.
Flashcard · §2
Por que "antes do egress" e não "antes do emit"?
clique para virar
Porque o endpoint do modelo está fora da fronteira de confiança. Redigir só antes do emit ainda mandaria PII crua pela rede até o modelo. O egress é o momento certo.
Flashcard · §3
Por que o corpus CL4R1T4S nunca é "ingerido como instrução"?
clique para virar
É um corpus de prompts vazado com um README de payload de injeção. Tratá-lo como dado a analisar (e não comandos a seguir) é defesa contra prompt-injection na camada de ingestão.
3
Restrição 1 no código — o guarda de path-traversal
Você viu o SkillStore na Lição 12. A espinha de segurança dele é validateSupportPath — uma função pura que recusa qualquer caminho relativo capaz de escapar do diretório da skill. Ela espelha o has_traversal_component + _resolve_skill_target do Hermes e é o validador fail-closed de manual: lista o que permite e nega todo o resto.
É como a portaria de um prédio com lista de moradores. O porteiro não tenta adivinhar quem é "suspeito" (isso é fail-open e sempre falha). Ele faz o contrário: só entra quem está na lista; qualquer outro — conhecido ou não — fica na rua. validateSupportPath tem uma lista de subpastas permitidas; tudo fora dela é negado.
nega
caminho vazio '' → 'file path is required.'
nega
tem barra invertida \ → 'use forward slashes.'(sem truques de backslash)
nega
começa com / → 'must be relative.'(sem caminhos absolutos)
nega
menos de 2 segmentos → 'must be under one of …'(ex.: references/api.md)
nega
algum segmento é .. ou . → 'path traversal is not allowed.'
nega
primeiro segmento fora dos permitidos → 'first segment must be one of …'
permite
ok(segments.join('/')) — só agora: um caminho relativo, confinado e vetado
Repare no formato: todo galho é uma negação, menos o último ok. Um ../../etc/passwd controlado por atacante morre na checagem de ..; um sorrateiro references/../../secret morre também. A função é pura e nunca lança, então compõe limpa no mundo Result — falhas de segurança aparecem como erros tipados e fail-closed, não como exceções nem como passagens silenciosas.
// packages/hermes/src/skills/skill-store.ts (condensado) — espelha has_traversal_componentconst validateSupportPath = (relPath: string): Result<string, Error> => {
if (relPath.length === 0) return err(new Error('file path is required.'));
if (relPath.includes('\\')) return err(…'use forward slashes.'); // sem backslashif (relPath.startsWith('/')) return err(…'must be relative.'); // sem caminho absolutoconst segments = relPath.split('/').filter((s) => s.length > 0);
if (segments.length < 2) return err(…'must be under one of …'); // ex.: references/api.mdfor (const segment of segments) {
if (segment === '..' || segment === '.') // sem segmento de traversalreturn err(…'path traversal is not allowed.');
}
const first = segments[0];
if (first === undefined || !isSupportDir(first)) // 1º segmento PERMITIDOreturn err(…'first segment must be one of …');
return ok(segments.join('/')); // só agora: caminho vetado e confinado
};
É o padrão allow-listing: a função enumera o que é permitido (um caminho relativo sob um support-dir aprovado, sem segmentos de traversal) e nega tudo o mais com valores err tipados. Pura, sem throw — exatamente a mesma postura que DEFAULT_TIER = T4, mas no nível de um caminho de arquivo.
Seis portões em série, cada um podendo negar; só quem passa por todos chega ao único ok. É o desenho do fail-closed.
DicaAllow-list, não block-list. Listar o que é permitido (e negar o resto) é robusto: um ataque novo que ninguém previu cai no "resto" e é negado. Listar o que é proibido (block-list) sempre fica para trás do próximo truque.
4
Restrição 1, de novo — fail-closed no tier padrão
A expressão mais profunda do fail-closed não é um guarda que você chama — é o padrão. DEFAULT_TIER = T4 (em packages/contracts/src/tier.ts) significa que qualquer trabalho não classificado explicitamente como autônomo fica parqueado, esperando um humano (Lição 24). A própria ADR traça a linha: "é também por isso que DEFAULT_TIER = T4" — o caso desconhecido nega. Autonomia não classificada é impossível por construção, não por lembrar de checar.
Imagine uma máquina industrial cujo botão "ligar sozinha" só funciona se alguém girou explicitamente a chave para "modo automático autorizado". Em qualquer outro estado — inclusive "ninguém configurou nada" — ela fica parada esperando um operador. O T4 é esse estado de repouso: na dúvida, não age sozinha.
O default não é "rodar"; é "parquear". Para sair do T4 e agir, alguém precisa classificar o trabalho — fail-closed no nível da própria política de execução.
Guarde istoUm guarda fail-closed é forte; um padrão fail-closed é mais forte ainda — porque protege até quando ninguém lembrou de chamar guarda nenhum.
5
Restrição 2 — PII antes do egress, não antes do emit
A palavra sutil é egress. Seria fácil redigir PII só antes de mostrar um resultado ao usuário. A ADR-0011 exige mais: um Signal derivado de um canal privado (WhatsApp, Discord, Skool, Circle) é "redigido antes de deixar a máquina local — antes do model call, não meramente antes do emit". O modelo de ameaça assume que o próprio endpoint do modelo está fora da fronteira de confiança, então PII crua nunca pode estar num payload de requisição.
É como rasurar um documento sigiloso antes de entregá-lo ao mensageiro — não depois que ele já entregou. Se você rasurasse só na recepção do destino, o conteúdo secreto já teria viajado a viagem inteira na mão de terceiros. Aqui o "mensageiro" é a rede até o modelo: a rasura tem que acontecer antes de ele sair com o envelope.
A fronteira de confiança: a redação acontece dentro da máquina, antes da call. O endpoint do modelo, à direita, nunca recebe um byte de PII crua.
A redação acontece antes do dado cruzar a borda local. O endpoint do modelo está do lado de fora — por isso "antes do call", não "antes do emit".
O elo com o funil (Lição 15). Lá vimos a invariante na prática: um sinal de canal privado é redigido por extractionInputantes do model call e re-checado por assertRedactedForEmitantes de qualquer write; um sinal não redigido é descartado e FunnelReport.t1PiiBlocked incrementa como alarme. É a ADR-0011 §2 virando código real: defesa em profundidade, duas barreiras, não uma.
Cuidado"Antes do emit" parece suficiente — não é. Entre o emit e o model call há uma viagem de rede inteira. A barra certa é o egress: o último ponto antes de o dado deixar a máquina.
6
Restrições 3 e 4 — não siga dados, não copie fonte
As duas últimas são sobre disciplina, não checagens de runtime. Isolamento CL4R1T4S: um corpus vazado de prompts de fornecedor (e seu README de payload de injeção) "é isolado e nunca ingerido como instrução; é dado a analisar, nunca um comando a seguir". Isso é defesa contra prompt-injection na camada de ingestão — o corpus é texto inerte, jamais executado.
Clean-room tac: tac é um blueprint de licença educacional, então "seus padrões são reimplementados do zero, com zero código ou prompts verbatim, e sua fonte nunca é publicada". A fusão @alembic/hermes inteira é uma reimplementação from-scratch em TypeScript justamente por causa dessa regra — que é também por que as lições citam a fonte do próprio Alembic, nunca o Python do Hermes.
③ CL4R1T4S — dado inerte, nunca instrução
④ clean-room — reimplementar, não copiar
Proveniência amarra tudo
A regra de orquestração "SEMPRE cite a fonte" e os stores content-addressed (SHA-256 sobre JSON canônico, Lição 28) fazem com que todo fato ingerido carregue uma fonte, uma data e um hash. Proveniência não é uma feature à parte — é o que permite ao sistema saber se um dado é confiável (um Learning vetado) ou suspeito (um payload CL4R1T4S). Fail-closed + proveniência são a mesma postura por dois ângulos: negue o desconhecido e sempre saiba de onde a coisa veio.
Por baixoPor que o clean-room também deixa o curso confiável: cada lição cita a fonte do Alembic porque uma cópia verbatim do Hermes seria uma violação. Reimplementar do zero é ao mesmo tempo a postura legal e a razão de o código ser de fato entendido, não colado.
7
Exemplo guiado — um caminho hostil batendo no guarda
Vamos passar uma entrada maliciosa por validateSupportPath e ver, galho a galho, onde ela morre. A entrada: references/../../etc/passwd — uma tentativa de escapar do diretório da skill via traversal.
Vazio? Não — tem conteúdo. Passa para o próximo galho.
2
Tem \? Não — só barras normais. Passa.
3
Começa com /? Não — é relativo. Passa.
4
Tem ao menos 2 segmentos? Sim (references, .., .., etc, passwd). Passa.
5
Algum segmento é .. ou .?SIM. O loop encontra .. e retorna err('path traversal is not allowed.'). Morre aqui.
6
Resultado. Um err tipado e fail-closed sobe pelo Result. Nada é lido, nada lança, nada vaza. O /etc/passwd nunca foi tocado.
Agora você. Refaça os mesmos galhos para a entrada /etc/shadow (um caminho absoluto). Em que passo exato ela morre, e com qual mensagem? Depois tente secret.md (um único segmento, sem subpasta) — esse passa pelos galhos de ..? Em qual checagem ele cai? Dica: há dois motivos diferentes de negação aqui, e nenhum dos dois chega ao ok.
8
Confusões comuns
"Fail-closed significa que o sistema trava com entrada ruim." O oposto — ele retorna um erro tipado e fail-closed (ADR-0011 §1, "não passagens silenciosas") e nunca lança (ADR-0009). O trabalho é negado ou parqueado, de forma limpa; nada trava e nada tem sucesso silencioso.
"A regra de clean-room é só cautela jurídica." É também por que este curso é confiável: cada lição cita a fonte do próprio Alembic porque uma cópia verbatim do Hermes seria uma violação. Reimplementar do zero é a postura legal e a razão de o código ser genuinamente entendido, não colado.
"Redigir PII antes de mostrar ao usuário basta." Não — entre mostrar e chamar o modelo há uma viagem de rede. A redação tem que acontecer no egress, antes do model call, porque o endpoint do modelo está fora da fronteira de confiança.
CuidadoBlock-list (listar o que é proibido) parece equivalente a allow-list, mas não é: um ataque novo cai fora da block-list e passa. validateSupportPath faz allow-list de propósito — o desconhecido é negado.
9
Recapitulando
Recap · 1 de 6
O caso desconhecido nega
Fail-closed é a postura padrão para tudo que toca segurança. Na dúvida, o sistema fecha — bloqueia, parqueia ou descarta.
01
Recap · 2 de 6
Quatro restrições
ADR-0011: ① fail-closed em tudo, ② PII antes do egress, ③ isolamento CL4R1T4S, ④ clean-room tac. Cada uma tem um lugar no código.
02
Recap · 3 de 6
O guarda de path
validateSupportPath: todo galho nega, menos um ok. Allow-list pura, sem throw. .., absoluto e \ são recusados.
03
Recap · 4 de 6
O padrão fail-closed
DEFAULT_TIER = T4: trabalho não classificado parqueia. Autonomia não classificada é impossível por construção, não por lembrar de checar.
04
Recap · 5 de 6
PII antes do egress
O endpoint do modelo está FORA da fronteira de confiança. Redija antes do call, não antes do emit — senão a PII crua viaja pela rede.
05
Recap · 6 de 6
Proveniência amarra tudo
Fonte + data + hash em todo fato. Fail-closed + proveniência = uma postura por dois ângulos: negue o desconhecido, saiba de onde a coisa veio.
06
Slide 1 / 6←→ navegam
Em uma frase: o motor é paranoico por construção — nega o desconhecido (fail-closed, allow-list, T4 padrão), redige PII no egress (antes do modelo, não da tela), trata dado vazado como dado e reimplementa em vez de copiar, e amarra tudo com proveniência (fonte + data + hash).
10
Verifique seu entendimento
Três perguntas — sua pontuação aparece embaixo
1. A ADR-0011 exige redação de PII "antes do egress … antes do model call, não meramente antes do emit". Por que a ênfase em antes do model call?
Correto: b. Redigir só antes do emit ainda mandaria PII crua pela rede até o modelo. A ADR empurra a redação para o ponto de egress — o momento em que o dado deixaria a máquina local — então o endpoint nunca recebe conteúdo privado não redigido.
2. validateSupportPath recusa .., caminhos absolutos e barras invertidas, permitindo só caminhos sob um subdir aprovado. Que padrão de design é esse?
Correto: d. A função enumera o que é permitido (um caminho relativo sob um support-dir aprovado, sem segmentos de traversal) e nega tudo o mais com valores err tipados. É allow-listing / fail-closed — a mesma postura de DEFAULT_TIER = T4.
3. Por que o corpus CL4R1T4S é "nunca ingerido como instrução"?
Correto: c. A ADR-0011 §3 o isola como dado inerte. Se o sistema o executasse como instrução, o payload de injeção poderia sequestrar o agente. A regra — "dado a analisar, nunca um comando a seguir" — é a defesa.
Acertos: 0/3
As cinco coisas para levar da proveniência e segurança
Fail-closed é a postura padrão: o caso desconhecido nega, nunca permite (ADR-0011 §1).
Quatro restrições, cada uma com lugar no código: fail-closed, PII-antes-do-egress, CL4R1T4S, clean-room.
validateSupportPath é allow-listing puro: todo galho nega, menos um ok vetado.
DEFAULT_TIER = T4 é o fail-closed mais profundo — um padrão, não um guarda que se chama.
Proveniência (fonte + data + hash) é o outro lado da mesma postura: saiba sempre de onde a coisa veio.
A fonte para ler
docs/adr/0011-security-and-provenance.md — as quatro restrições (fail-closed §1, PII-antes-do-egress §2, CL4R1T4S §3, clean-room tac §4), "o caso desconhecido nega", "por isso DEFAULT_TIER = T4" e erros tipados fail-closed, não passagens silenciosas. Veja também packages/hermes/src/skills/skill-store.ts (validateSupportPath) e packages/contracts/src/tier.ts (DEFAULT_TIER = T4). O fundamento never-throws está na ADR-0009 (Lição 5); a PII no funil, na Lição 15.