Pular para conteúdo

AUR-582 — regra embutida de segredo lê a base pública do gitleaks como dado pinado

Medido antes

  • internal/analysis/analysis.go fixava em Go, por regexp.MustCompile, uma única regex de palavra-chave para analysis/hardcoded-secret (password|token|secret... = "<8+ chars>"): sem formatos de fornecedor, sem entropia, sem allowlist. Uma chave AWS ou um token GitHub atribuídos a um identificador neutro (awsID := "AKIA...") não produziam achado.
  • O lock .board/bootstrap/locks/scanners.yml já pina a base: secrets_rulebase_id: gitleaks-default-config-v8.30.1 e secrets_rulebase_sha256: sha256:e163e53b…caf (97731 bytes, https://raw.githubusercontent.com/gitleaks/gitleaks/v8.30.1/config/gitleaks.toml). O download conferiu exatamente esse digest.

O que mudou (v2)

  • internal/analysis/rules/secrets.json: derivação JSON da configuração padrão do gitleaks v8.30.1 (id, descrição, regex, path, secretGroup, entropy, keywords, allowlists por regra e allowlist global), com source.sha256 = secrets_rulebase_sha256 do lock. O TOML de origem NÃO é commitado: o repositório é público e as allowlists da base trazem chaves de exemplo com forma de credencial.
  • Literais de allowlist com forma de credencial — sem metacaractere de regex e casados pela regex da própria regra (hoje: os 16 exemplos AIza… de gcp-api-key) — vão para o JSON só como sha256 (literal_sha256). Os demais regexes de allowlist e as stopwords ficam em claro. Diferença de semântica registrada: na fonte, o regex de allowlist casava qualquer alvo que CONTIVESSE o literal (substring); aqui o alvo (regexTarget, por padrão o segredo capturado) é suprimido só por IGUALDADE exata de hash.
  • internal/analysis/rules/secrets.json.sha256: digest dos bytes do JSON, checado pelo loader (pânico no init se divergir).
  • scripts/analysis/update-secret-rules.sh (host com rede): lê versão e digest do lock, baixa o TOML na tag, recusa digest divergente, deriva o JSON com python3/tomllib (stdlib — nenhum parser TOML no go.mod), converte os literais com forma de credencial em hash (só se a regex da regra os casa), confere que cada regra derivada mantém id e regex da fonte, grava JSON e sidecar. AURUMCODE_GITLEAKS_TOML_OUT=<arquivo> guarda uma cópia do TOML verificado fora do repositório. Reexecutar o script deixa internal/analysis/rules/ sem diff.
  • internal/analysis/rules/secrets-local.json: a regra de palavra-chave anterior, agora como dado (aurumcode-keyword-assignment, contexto code-outside-string-literals). Justificativa: a base pública não cobre os casos existentes password = "xxxxxxxx", token = "zzzzzzzz" etc. — o valor bate na allowlist global (^(?i:a+|…|x+|…|z+)$) e na entropia mínima. Manter a regra local preserva o comportamento publicado (mensagem fixa Hardcoded secret or credential assigned inline, usada por testes de cmd/, tests/unit/AUR-505.go, o tutorial gate e docs), e a supressão de exemplos dentro de string literal continua só nela.
  • internal/analysis/secretcatalog.go: go:embed do JSON, do sidecar e da regra local; loader recusa schema desconhecido, digest de artefato divergente e source.sha256 malformado. Regras sem regex (só path) e regex que o RE2 recuse viram skipped, nunca somem.
  • internal/analysis/secretscan.go: semântica do gitleaks — pré-filtro por keywords, regex, segredo = secretGroup ou primeiro grupo não vazio ou o match, entropia de Shannon > mínimo, allowlists por regra e global (regexes e hashes com regexTarget secret/match/line, paths, stopwords, condição OR/AND). Regras locais primeiro, depois a base; no máximo um achado analysis/hardcoded-secret por linha; só linhas adicionadas, após remover comentários.
  • Mensagem: regra local mantém a mensagem fixa; regra da base usa Hardcoded secret matched rule <id> (<descrição>); o valor capturado nunca entra na mensagem. O id analysis/hardcoded-secret e as demais regras (injeção de comando/SQL, permissões) não mudam.

Cadeia de digests (sem campo novo no lock)

lock (secrets_rulebase_sha256) → TOML (o script recusa outro digest) → JSON (source.sha256, comparado ao lock pelo teste com AURUMCODE_SCANNERS_LOCK) → bytes do JSON (secrets.json.sha256, checado no loader). Um campo secrets_rulebase_artifact_sha256 mudaria a identidade em locks.yml, o bloco citado em docs/tutorials/operacao.md e entradas de tutoriais e testes fora dos paths, sem ganho de prova: o sidecar já amarra cada byte do artefato.

Fidelidade à fonte (teste condicionado)

Como o TOML não está no repositório, TestDerivedSecretCatalogFaithfulToSource só roda com AURUMCODE_GITLEAKS_TOML=<cópia verificada>; sem ela, pula com o motivo explícito. Com ela, confere sha256 da fonte, número e ordem das regras, id, regex e entropia, e que cada literal_sha256 é o hash de um literal da allowlist da regra de origem que a regex da própria regra casa. O script faz a mesma conferência de id/regex em cada atualização. Rodado localmente com a cópia baixada: --- PASS: TestDerivedSecretCatalogFaithfulToSource.

Cobertura da base

222 regras; 221 compilam no regexp do Go. A única pulada é pkcs12-file (regra só de path), fixada em TestSkippedSecretRulesAreDeclared. A heurística do gitleaks de exigir dígito em segredos de regras generic* não faz parte da configuração e não foi reproduzida.

Prova

bash tests/acceptance/AUR-582.sh all (container go-shared), últimas linhas:

    secretcatalog_test.go:120: lock sha256:f163e5…, catalog records sha256:e163e5…
panic: secret catalog: artifact digest sha256:2f8f84… does not match the recorded sha256:3ed493…
ok      github.com/Mpaape/AurumCode/internal/analysis   0.018s
    secretcatalog_test.go:279: planted.go:5:42: secret-shaped regex compiled in Go
AC-005: no credential-shaped literal
    --- FAIL: TestSecretAllowlistAndEntropy/low-entropy_aws-shaped_key_is_below_the_minimum (0.00s)
    secretcatalog_test.go:98: a derived catalog one byte off its recorded digest was accepted
AUR-582/all/pass
  • AC-001 — TestSecretVendorFormatsReportRuleIDWithoutValue: awsID := "AKIA…" e gh := "ghp_…" (vetores por concatenação) produzem dois achados analysis/hardcoded-secret com aws-access-token e github-pat na mensagem e sem o valor.
  • AC-002 — TestSecretAllowlistAndEntropy: password: <alta entropia> em config.yml é achado da base (generic-api-key); o mesmo valor com sufixo -example (stopword) não; chave AWS terminada em EXAMPLE (allowlist regex) não; baixa entropia não; go.sum (allowlist global de path) não. TestHashedAllowlistSuppressesExampleKey: a chave de exemplo GCP allowlistada só por hash não gera achado, outra chave do mesmo formato gera, e a mesma regra sem allowlists flagra o exemplo (o hash é a causa). Os casos antigos (TestAnalyzeTable, TestAUR4*, TestNewRunnerZeroConfig) seguem verdes sem edição.
  • AC-003 — source.sha256 do artefato = secrets_rulebase_sha256 do lock (teste executado, não pulado); um lock com outro digest reprova (catalog records); um byte trocado no JSON derruba o init (artifact digest … does not match).
  • AC-004 — grep sem regexp.(Must)Compile de padrão de segredo em Go de produção e TestNoSecretRegexCompiledInGo (go/ast) verde; plantar um regexp.MustCompile de password deixa o teste vermelho.
  • AC-005 — git ls-files -z | xargs -0 grep -lE '<AIza|ghp_|xox|AKIA|sk_live_|glpat-|PEM>' vazio (sem git, no selado, o mesmo grep sobre a árvore materializada).
  • MUT-001 — entropia ignorada reprova AC-002.
  • MUT-002 — loader que aceita digest divergente reprova AC-003 (was accepted).

Regressão (container go-shared):

  • go vet ./... && go test -p 1 ./...: todos os pacotes ok.
  • tests/acceptance/AUR-574.sh all → AUR-574/all/pass.
  • Tutorial gate (único expected/ que cita o achado), com imagem do worktree: run.sh all e --check → CHECK OK (medido na v1; a v2 não muda o comportamento sobre esse repositório).
  • tests/acceptance/AUR-435.sh não roda em árvore limpa: a linha 122 copia pacotes enumerados (internal/documentation/extractors, .../incremental) que não são rastreados pelo git — falha de infraestrutura anterior a este card.
  • tests/acceptance/AUR-541.sh all → AUR-541/all/pass, rc 0, 302 s, executando AC-001, AC-002 e AC-001-MUT-001 em sequência (o all falha no primeiro seletor vermelho); o rastro bash -x cita 52 scripts de tests/e2e/ (AUR-461 sete vezes, pela mutação). A medida anterior de "~1 min" foi erro de leitura do relógio: a primeira execução também levou cerca de 5 min.