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.

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.
Mesma entrada desconhecida, duas posturas fail-open (errado) desconhecido PASSA na dúvida, permite — bloqueia só o sabidamente ruim fail-closed (certo) desconhecido NEGA na dúvida, nega — permite só o sabidamente bom
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.
fail- closed allow-list validateSupportPath padrão parqueia DEFAULT_TIER = T4 erro vira err() never-throws · ADR-0009
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.
Escudo central 'fail-closed' sobre quatro cartões numerados das restrições da ADR-0011 — fail-closed em tudo, PII antes do egress, isolamento CL4R1T4S e clean-room tac — cada um com onde vive no código

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
1Fail-closed em tudo que toca segurança — realpath guards, webhooks com HMAC, comparações em tempo constante, Zod em toda fronteirapath-safety do SkillStore; DEFAULT_TIER = T4; o safeParse de cada subsistema
2Redação de PII antes do egress — antes do model call, não meramente antes do emito funil redige sinais de canal privado antes da call (mapa §3)
3Isolamento CL4R1T4S — o corpus de prompts de fornecedor vazado é dado a analisar, nunca um comando a seguirexcluído da ingestão-como-instrução; tratado como dado inerte
4Clean-room tac — padrões reimplementados do zero, zero código/prompts verbatim, fonte nunca publicadatoda a fusão é TypeScript do zero, não fonte copiada
postura: fail-closed o desconhecido nega, nunca permite ① fail-closed realpath · HMAC compare const-time Zod em toda fronteira validateSupportPath · T4 ② PII antes do egress canais privados redigidos antes do model call não só antes do emit o funil redige (mapa §3) ③ CL4R1T4S corpus de prompts vazado dado a analisar nunca comando a seguir anti-injeção na ingestão ④ clean-room tac reimplementado do zero zero código verbatim fonte nunca publicada a fusão é TS do zero
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_component
const 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 backslash
  if (relPath.startsWith('/')) return err(…'must be relative.');     // sem caminho absoluto

  const segments = relPath.split('/').filter((s) => s.length > 0);
  if (segments.length < 2) return err(…'must be under one of …');  // ex.: references/api.md
  for (const segment of segments) {
    if (segment === '..' || segment === '.')                         // sem segmento de traversal
      return err(…'path traversal is not allowed.');
  }
  const first = segments[0];
  if (first === undefined || !isSupportDir(first))                  // 1º segmento PERMITIDO
    return 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.

relPath entra vazio? tem \ ? absoluto? começa com / .. ou . ? 1º seg. permitido? qualquer galho ⇒ err() tipado (fail-closed) ok(caminho vetado) a ÚNICA saída de sucesso
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.
trabalho novo tier não definido DEFAULT_TIER = T4 o padrão fail-closed PARQUEADO isParked(tier) === true humano classifica T1/T2/T3 → segue 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.
Fronteira de confiança: à esquerda a máquina local redige o sinal de canal privado antes do egress; só texto redigido cruza para a direita, onde o model call nunca vê PII crua; embaixo, redigir antes do call (certo) contra antes do emit (errado)

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.

MÁQUINA LOCAL (fronteira de confiança) sinal privado PII crua · whatsapp… redige antes do egress só texto redigido ↓ FORA (endpoint do modelo · rede) model call nunca vê a 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 extractionInput antes do model call e re-checado por assertRedactedForEmit antes 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
corpus vazado + README de injeção DADO a analisar inerte · permitido COMANDO a seguir nunca · bloqueado o caminho de baixo está cortado
④ clean-room — reimplementar, não copiar
blueprint tac licença educacional TS do zero só os padrões copiar verbatim zero · proibido aprende a ideia, escreve o próprio
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.

validateSupportPath('references/../../etc/passwd')
1
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
  1. Fail-closed é a postura padrão: o caso desconhecido nega, nunca permite (ADR-0011 §1).
  2. Quatro restrições, cada uma com lugar no código: fail-closed, PII-antes-do-egress, CL4R1T4S, clean-room.
  3. validateSupportPath é allow-listing puro: todo galho nega, menos um ok vetado.
  4. DEFAULT_TIER = T4 é o fail-closed mais profundo — um padrão, não um guarda que se chama.
  5. 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.