Pular para o conteúdo

As ferramentas

Detecção de segredos, linter de segurança, análise estática, verificações em execução e trilha de auditoria — o que cada uma pega, e o que ela não pega.

Uma ferramenta de segurança vale pelo que ela pega e pelo que ela não acusa por engano. A segunda metade decide se ela continua ligada: um alarme falso no caminho comum ensina a ignorar a saída, e uma ferramenta ignorada não protege nada.

`dataforge seguranca`#

Duas varreduras sobre cada arquivo: segredos por formato e dez regras sintáticas. Ela lê .df, .env, .json, .toml, .yml, .sh, .ts e .md — um segredo vaza do arquivo de configuração muito mais do que do código.

dataforge
// $ dataforge seguranca .
//
// ALTO  src/config.df:12:14  [segredo-no-codigo]
//         Stripe escrito no arquivo (sk_l************).
//         dica: Leia de 'OS.env' e ROTACIONE a chave que ja esteve aqui.
//
// MEDIO src/relatorio.df:88:5  [md5-ou-sha1]
//         MD5 e SHA-1 estao quebrados para assinatura.
//         dica: Use 'sha256'. Para senha, 'hash_password'.
//
// 2 achado(s) em 143 arquivo(s) — 1 de gravidade alta

// --strict        reprova a esteira de CI
// --json          vira entrada de outra ferramenta
// --so=alto       esconde os medios
RegraGravidadeO que ela acusa
segredo-no-codigoaltochave de API, token ou bloco de chave privada no arquivo
sql-concatenadoaltoSQL montado com interpolação ou concatenação
shell-com-textoaltolinha de shell montada com interpolação
senha-sem-derivacaoaltosenha guardada com resumo simples
verificacao-desligadaaltoverificar := no no caminho de produção
md5-ou-sha1médioMD5 e SHA-1 para assinatura
html-sem-escapemédioHTML montado com interpolação
aleatorio-fracomédiorandint para token, senha ou chave
comparacao-de-segredomédiocomparar segredo com is vaza tempo
caminho-de-foramédiocaminho montado com valor que veio de fora
dataforge
adopt Arcane.Seguranca as Seg

// A mesma varredura, chamada de dentro de um programa — e util num
// gancho de pre-commit ou antes de gravar um log.
fonte := "chave := \"ghp_abcdefghijklmnopqrstuvwxyz0123456789\""

achados := Seg.procurar_segredos(fonte)
assert len(achados) is 1
out $"{achados[0]['tipo']} na linha {achados[0]['linha']}: {achados[0]['trecho']}"

// O trecho ja vem MASCARADO: um relatorio de vazamento que imprime
// o segredo inteiro e mais um lugar onde ele esta.
assert "abcdefghijkl" not in achados[0]["trecho"]

// E 'redigir' devolve o texto com os segredos trocados no lugar,
// para gravar um corpo de pedido no log sem que o log vire o
// proximo vazamento.
assert "abcdefghijkl" not in Seg.redigir(fonte)

Análise estática#

ComandoO que ele pega que importa para segurança
dataforge checknome, aridade e tipo — inclusive entre arquivos; campo inexistente com sugestão
dataforge check --strictavisos viram erros; é o que vai na esteira
dataforge lintvariável escrita e nunca lida, ramo redundante
dataforge segurancaas duas varreduras acima
dataforge oopacoplamento e coesão — complexidade é onde a falha se esconde
dataforge depso grafo de imports, e o ciclo

Verificações em execução#

O que a análise estática não consegue provar continua sendo conferido quando o programa roda — e o erro tem nome, linha e dica.

ConfereO erro
tipo declarado, em toda fronteiraTypeError
índice fora do alcanceIndexError
chave que não existeKeyError
membro de voidNullReferenceError
refinamento de um type … whereo erro nomeia o tipo, não a base
invariante de um agregadoInvariantError, e o comando é desfeito
limite de bloco e ponteiro nuloBufferOverflowError, NullPointerError
entrada hostil recusadaUnsafeInputError

Trilha de auditoria#

Responsabilização exige um registro que não possa ser alterado sem deixar marca. A trilha encadeia o resumo do registro anterior em cada registro.

dataforge
adopt Arcane.Seguranca as Seg
adopt Arcane.OS as OS
adopt Arcane.IO as IO

caminho := $"{OS.temp_dir()}/df-auditoria-{randint(100000, 999999)}.log"
livro := Seg.auditoria(caminho)

livro.registrar("login", {"ip": "203.0.113.7"}, quem := "ana")
livro.registrar("apagou_pedido", {"pedido": 42}, quem := "ana")
livro.registrar("exportou_relatorio", {"linhas": 1200}, quem := "bruno")

r := livro.conferir()
assert r["ok"]
out $"{r['registros']} registros, cadeia fecha"

// Os dados passam por 'redigir' ANTES de serem gravados: uma trilha
// de auditoria e exatamente o tipo de arquivo que acaba anexado a
// um chamado.
livro.registrar("deploy", {"token": "ghp_abcdefghijklmnopqrstuvwxyz01"})
cycle linha in livro.ler():
    assert "abcdefghijkl" not in str(linha)

out "o segredo nao entra nem na propria auditoria"

IO.delete(caminho)

A esteira completa#

dataforge
// .github/workflows/ci.yml — as cinco que reprovam de verdade
//
//   dataforge check . --strict
//   dataforge lint .
//   dataforge seguranca . --strict
//   dataforge test tests/ --cobertura --minimo=80
//   dataforge devops doctor
//
// E o gancho local, que pega antes de o segredo sair da maquina:
//
//   # .git/hooks/pre-commit
//   #!/bin/sh
//   dataforge seguranca . --strict || exit 1