Arcane.Seguranca
Escape por destino (HTML, atributo, JS, URL, shell, SQL LIKE, CSV, cabeçalho, log), sanitização de HTML por lista de permitidos, política e força de senha com vazamento por k-anonimato, TOTP/HOTP e códigos de recuperação, token e URL assinados com prazo e propósito, varredura de segredos por formato, redação de PII, defesa de SSRF e de travessia de caminho, limitador de taxa, bloqueio progressivo, trilha de auditoria encadeada e dez regras de análise estática.
Arcane.Crypto tem as primitivas — resumo, HMAC, senha derivada, aleatório de verdade, ChaCha20-Poly1305, JWT. Este módulo é o que se faz com a entrada que veio de fora, e ele chama o Crypto em vez de reimplementá-lo: duas contas iguais escritas duas vezes divergem, e no dia em que divergirem será a de segurança que estará errada.
Escapar é por destino, nunca “em geral”#
O que protege uma página HTML não protege uma linha de shell, e o que protege shell estraga um CSV. São doze destinos, e cada um tem a sua função — usar a errada é o mesmo que não usar nenhuma.
adopt Arcane.Seguranca as Seg
out Seg.escapar_html("<b>&'")
out Seg.escapar_js("</script>")
out Seg.escapar_csv("=HYPERLINK("http://mau")")
out Seg.escapar_cabecalho("ok
Set-Cookie: admin=1")
out Seg.escapar_log("ana
2026-01-01 INFO login ok")
out Seg.limpar_html("<b>oi</b><script>mau()</script>")O segredo que se recusa a aparecer#
Um segredo vaza em log muito mais do que em commit, e vaza pelo caminho mais inocente que existe: alguém imprime o vault inteiro para depurar. Segredo é opaco para texto, para interpolação e para serialização.
adopt Arcane.Seguranca as Seg
chave := Seg.segredo("sk_live_exemplo_de_chave")
out chave
out $"usando {chave}"
out chave.revelar()
assert chave.igual("sk_live_exemplo_de_chave")Ele não protege da memória nem de quem chama revelar(). O que ele faz é transformar um vazamento acidental numa linha explícita, que aparece na revisão de código.
Varredura de segredo, e o que a faz calar#
Ela acha por formato — ghp_, sk_live_, AKIA, bloco de chave privada, token do PyPI —, e por isso acha o que quem escreveu esqueceu, que é o único tipo que importa. O que ela não acusa custou mais que o que ela acusa:
| Cala sobre | Porque |
|---|---|
| um valor que se anuncia como exemplo | "123456:AAHexemplo" num exercício que ensina a não escrever token no arquivo. Uma varredura que acusa o material didático do próprio projeto é desligada no mesmo dia — e junto com ela vão os achados de verdade. |
JWT com papel anon | A chave anon do Supabase vai no pacote do navegador de propósito: quem protege a linha é o RLS. A service_role ignora o RLS e é comprometimento total. As duas têm o mesmo formato, e só o conteúdo as separa — então o papel é lido de dentro do próprio token. |
credencial de localhost | postgres://forge:forge@localhost num teste é a forma normal de escrever um teste. Os domínios reservados (RFC 2606) entram pela mesma porta. |
// df: permitir segredo-no-codigo | O escape do analisador, e ele vale em qualquer arquivo — um segredo de brinquedo mora tanto num teste em Python quanto num exemplo em Markdown. |
adopt Arcane.Seguranca as Seg
fonte := "chave := "ghp_abcdefghijklmnopqrstuvwxyz0123456789""
achados := Seg.procurar_segredos(fonte)
cycle a in achados:
out $"{a['tipo']} na linha {a['linha']}: {a['trecho']}"
out Seg.redigir(fonte)Entrada hostil#
Quatro perguntas que uma aplicação faz sobre todo valor que chega de fora, e que quase sempre são respondidas com uma comparação de texto que não basta.
| Função | O ataque que ela fecha |
|---|---|
url_segura | SSRF. Um campo “URL do seu webhook” com http://169.254.169.254/latest/meta-data/ faz o seu servidor buscar as credenciais da instância e devolvê-las na resposta. O pedido sai de dentro, então o firewall não vê nada de errado. |
caminho_seguro | Travessia. realpath antes de comparar: sem isso, um link simbólico dentro da pasta aponta para fora e a comparação de texto aprova. |
redirecionamento_seguro | Redirecionamento aberto. /sair?proximo=//banco-falso.exemplo sai do seu site com a barra de endereço mostrando o seu nome até o último instante. |
json_seguro | Negação de serviço com 50 KB. Um [[[[[…]]]]] de dez mil níveis estoura a pilha de quem lê, e o leitor padrão aceita. |
Segundo fator, e o token com prazo#
adopt Arcane.Seguranca as Seg
// Os vetores do RFC 4226
assert Seg.hotp("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", 0) is "755224"
assert Seg.hotp("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", 1) is "287082"
segredo := Seg.totp_segredo()
codigo := Seg.totp_agora(segredo)
assert Seg.totp_conferir(segredo, codigo)
out Seg.totp_uri(segredo, "ana@loja.com", emissor := "Loja")adopt Arcane.Seguranca as Seg
chave := Seg.chave_de_assinatura()
t := Seg.assinar({"usuario": 7}, chave, proposito := "trocar-senha")
monitor:
Seg.ler_assinado(t, chave, prazo := 900, proposito := "confirmar-email")
handle SignatureError as e:
out "recusado: o proposito e outro"
lido := Seg.ler_assinado(t, chave, prazo := 900, proposito := "trocar-senha")
out lido["valor"]["usuario"]Senha: a nota não serve, a lista serve#
“Senha fraca” não diz o que fazer. forca_da_senha devolve problemas, e politica devolve faltando — é isso que vai para a tela. exigir_politica levanta com a lista em e.nota, para quem escreve o caminho feliz.
Auditoria que não pode ser editada sem deixar marca#
Cada registro carrega o resumo do anterior. Isso não impede editar o arquivo — nada num arquivo local impede. O que ele dá é evidência: alterar ou apagar uma linha faz a próxima deixar de fechar, e conferir() diz em qual. Para que a evidência valha, a trilha tem de ser copiada para fora da máquina que a escreve.
adopt Arcane.Seguranca as Seg
livro := Seg.auditoria("auditoria.log")
livro.registrar("login", {"quem": "ana"})
livro.registrar("pagou", {"valor": 100})
r := livro.conferir()
out $"{r['registros']} registros, cadeia fecha: {r['ok']}"Os dados de cada evento passam por redigir antes de serem gravados: uma trilha de auditoria é exatamente o tipo de arquivo que acaba anexado a um chamado.
O que este módulo não faz#
| Não há | Porque |
|---|---|
| TLS e certificado | É do ssl do Python e de quem está na frente do servidor (nginx, Caddy). Uma camada própria seria um subconjunto pior de algo que já existe e já é auditado. |
| Argon2 | Em Python puro ele seria lento o bastante para ter de rodar com parâmetros fracos — o que o torna pior que o scrypt do hashlib, e não melhor. Crypto.hash_password usa o que o Python tem. |
| CSRF, cabeçalhos e limite de corpo HTTP | O Kiln já responde isso como middleware, preso ao ciclo de um pedido. Duas respostas à mesma pergunta divergem. |
| Limite de taxa entre processos | limitador é por processo e em memória. Num servidor com vários processos o limite efetivo é N vezes maior; limite compartilhado pede um banco ou um Redis, e este módulo não pretende ser nenhum dos dois. |
Guia com contexto e prática: Criptografia. As primitivas: Arcane.Crypto.
Funções (54)#
| Assinatura |
|---|
analisar(fonte, caminho='') |
assinar(valor, chave, proposito='', quando=None) |
assinar_url(url, chave, prazo=3600, quando=None) |
auditoria(caminho) |
caminho_seguro(base, pedido) |
chave_de_assinatura(bytes_=32) |
codigos_de_recuperacao(quantos=10, grupos=3, tamanho=4) |
conferir_pkce(verificador, desafio) |
conferir_url(url, chave, quando=None) |
conferir_vazamento(senha, respostas) |
e_alta_entropia(texto, minimo=3.5) |
e_segredo(v) |
entropia(texto) |
escapar_atributo(texto) |
escapar_cabecalho(texto) |
escapar_csv(valor) |
escapar_html(texto) |
escapar_js(texto) |
escapar_log(texto) |
escapar_regex(texto) |
escapar_shell(texto) |
escapar_sql_like(texto, escape='\\') |
escapar_url(texto) |
estado_de_oauth(bytes_=32) |
exigir_politica(senha, opcoes=None) |
exigir_sem_segredo(texto, onde='') |
forca_da_senha(senha) |
host_privado(host) |
hotp(segredo, contador, digitos=6, algoritmo='sha1') |
json_seguro(texto, opcoes=None) |
ler_assinado(token, chave, prazo=None, proposito='') |
limitador(limite, periodo=60.0, rajada=None) |
limpar_html(texto, opcoes=None) |
mascarar(valor, visivel=4) |
mascarar_pii(texto, tipos=None) |
nome_de_arquivo_seguro(nome, padrao='arquivo') |
numero_seguro(texto, minimo=None, maximo=None, inteiro=True) |
padroes_de_segredo() |
pkce(tamanho=64) |
politica(senha, opcoes=None) |
prefixo_vazamento(senha) |
procurar_segredos(texto, opcoes=None) |
redigir(texto) |
redirecionamento_seguro(destino, permitidos=None, padrao='/') |
regras_de_analise() |
segredo(valor, rotulo='segredo') |
sem_controle(texto, permitir_quebra=False) |
tentativas(limite=5, base=30.0, teto=3600.0) |
totp_agora(segredo, janela=30, digitos=6, algoritmo='sha1', quando=None) |
totp_conferir(segredo, codigo, janela=30, digitos=6, algoritmo='sha1', tolerancia=1, quando=None) |
totp_segredo(bytes_=20) |
totp_uri(segredo, conta, emissor='', digitos=6, janela=30, algoritmo='sha1') |
url_segura(url, opcoes=None) |
vazada(senha, tempo_limite=5.0) |