Skip to content

Latest commit

 

History

History
185 lines (163 loc) · 12.3 KB

File metadata and controls

185 lines (163 loc) · 12.3 KB

Changelog

O formato segue Keep a Changelog e SemVer.

[Não lançado]

Adicionado

  • Nova checagem payload-duplicate-key (HIGH · A07:2025 · CWE-347): chave repetida no payload, simétrica à header-duplicate-key que já existia. json.loads fica com a última ocorrência; um parser first-wins fica com a primeira — a divergência é o ataque. Um role/exp/aud duplicado deixa um verificador ler user/expirado e outro admin/válido a partir do mesmo token. O achado do payload não é menos severo que o do cabeçalho porque é o payload que carrega as claims de autorização e expiração. A checagem de segmento foi generalizada para rodar idêntica nos dois segmentos (cabeçalho e payload). Coberta por caso positivo dedicado, meta-teste de catálogo e teste property-based (Hypothesis) sobre a invariante "qualquer segmento com chave repetida ⇒ pelo menos um achado".

Corrigido

  • payload-sensitive — descritor de recurso deixa de ser falso-positivo: uma chave-sensível terminada em descritor (_name/_type/_uid/_namespace/_kind/_ref, com .///- normalizados para _) descreve um metadado, não carrega o segredo. O token legado de ServiceAccount do Kubernetes carimba kubernetes.io/serviceaccount/secret.name = admin-user-token-6gl6l (o nome do objeto Secret) e disparava MÉDIA indevida. O gate por valor (assinatura forte AKIA…/ghp_…/sk-…/PEM/webhook) continua valendo sob qualquer chave, para não cegar um segredo real que apareça sob uma chave-descritor.
  • claim-long-lifetime — refresh token reconhecido por convenção de biblioteca: a supressão para refresh token não depende mais do nome do campo typ. Reconhece as convenções mainstream — typ (Keycloak), type (Flask-JWT-Extended) e token_type (djangorestframework-simplejwt) com valor refresh (case-insensitive) —, mantendo intocado o gate anti-abuso: só suprime quando não há sinais de access token (scope/scp/azp); um access token de 30 dias com scope: admin não ganha isenção só por se declarar refresh.
  • Proveniência: commit resolvido pelo diretório do pacote, não pelo CWD: o Chaveiro audita um token (não varre um repositório), então o commit do envelope é o SHA da própria ferramenta. Antes, git rev-parse HEAD rodava sem -C, herdando o diretório de trabalho — rodar chaveiro de dentro de outro repositório git carimbava o HEAD daquele repo (silenciosamente errado). Agora o git é consultado no diretório do pacote (Path(__file__).resolve().parent, via git -C), e CHAVEIRO_COMMIT só é aceito se for um SHA de 40 hex (^[0-9a-f]{40}$) — um valor malformado (HEAD, SHA truncado) é ignorado em vez de carimbado. Invariante travada por teste (revert→vermelho): rodando de dentro de um repositório git qualquer, o commit carimbado é o do pacote, nunca o do CWD.

Documentação

  • README (PT/EN) realinhado: nova linha de catálogo para a classe parser-differential (header-duplicate-key / payload-duplicate-key); a nota de falso-positivo do payload-sensitive virou calibração de baixo falso-positivo (casamento por token de nome — secretary/discard não disparam —, descritor de recurso como metadado, e o gate por valor sob qualquer chave), somada à calibração de refresh token do claim-long-lifetime; e a seção de proveniência e a linha CHAVEIRO_COMMIT passaram a descrever a resolução do commit pelo diretório do pacote e o gate de 40 hex.

[0.5.0] — 2026-08-05

Adicionado

  • -o/--output em inspect e batch (paridade com Guardião/Esteira). Grava o laudo direto em UTF-8, evitando a armadilha do operador > do PowerShell (que recodifica linha a linha, lento e sujeito a mojibake no Windows) e servindo de canal limpo para upload-sarif num passo de CI. Exige --format json.
  • Nova checagem header-zip-jws (HIGH · A06:2025 · CWE-409): zip (compressão) declarado num JWS de 3 segmentos. O parâmetro só é válido em JWE (RFC 7516); num JWS viola o RFC 7515 e é o vetor de DoS por descompressão pré-verificação — a biblioteca que descomprime o payload antes de checar a assinatura (caso Apache James) fica exposta sem o atacante conhecer chave nenhuma. Detectável offline, só lendo o cabeçalho. Coberta por caso positivo e negativo + o meta-teste de catálogo.

Documentação

  • README com prova de campo e Pro honesto: nova seção "Prova de campo" com os números medidos da bateria real (recall 22/22 vetores, 0 falso-positivo em 6/6 tokens legítimos, 24.606 tokens/s no batch, token hostil não derruba o lote) e o falso-positivo conhecido (secret como substring casa secretary) documentado em vez de escondido. A seção Pro passa a dizer com todas as letras que a engine é a mesma — o Pro é serviço (auditoria guiada, PoC autorizado, validação de referência aplicada ao stack do cliente), não código diferente atrás de paywall.

Corrigido

  • Token hostil não derruba mais o lote (batch): um JWT com payload profundamente aninhado levantava RecursionError (json.loads/json.dumps são recursivos), abortava o lote inteiro e apagava a auditoria dos demais — quebrando o contrato documentado de isolamento por token. Agora decode aplica um teto explícito de aninhamento (64 níveis, medido sem recursão sobre os bytes), o que protege decodificação, render e serialização de uma vez; audit_batch isola qualquer exceção por token (não só JWTError), registrando o tipo no campo error. Nenhum JWT legítimo passa de poucos níveis.
  • base64url estrito (RFC 7515 §2): b64url_decode rejeita segmentos com caractere fora do alfabeto A-Za-z0-9-_, com espaço/quebra de linha, ou em base64 padrão (+/). O decodificador era tolerante (herdado do urlsafe_b64decode, que descarta bytes inválidos em silêncio) — um auditor leniente laudava um token que verificador estrito nenhum aceita.
  • exp/nbf não-numérico agora falha fechado: a referência de validação e o detector passivo tratavam uma claim temporal presente-mas-não-numérica (exp: "1", Infinity) como ausente — um token com exp em string nunca expirava. A referência passa a rejeitar (RFC 7519 §2: NumericDate é número) e o detector emite o novo achado claim-malformed-time (Média).
  • claim-long-lifetime dispara sem iat: um token de 10 anos sem iat (mais perigoso, não menos) escapava porque a checagem exigia exp e iat. Sem iat, a vida útil passa a ser aproximada por exp - agora.
  • payload-sensitive varre em profundidade: segredo/PII em objeto aninhado ({"user": {"cpf": …}} — a forma mais comum de payload JWT no Brasil) escapava porque o loop só olhava o primeiro nível. Agora percorre dicts/listas aninhados (sem recursão), casa chaves compostas (user_password, db_secret) e detecta CPF de 11 dígitos crus (com validação dos dígitos verificadores, para não gerar falso positivo em telefone/id).
  • JWT aninhado real (RFC 7519 §5.2): apresentar um JWS-in-JWS legítimo (payload da casca = outro JWS compacto) fazia o decode abortar com "payload não é JSON" — a auditoria inteira parava no exato vetor que a ferramenta diz cobrir. Agora a casca é decodificada, o token interno fica em token.nested e payload-nested-jwt é emitido.
  • Injeção de marcação do Rich no relatório: dado controlado pelo emissor do token (alg, kid, claims, mensagem de erro) contendo [/]/[bold] derrubava o console com MarkupError ou — pior — saía interpretado (cor/hyperlink forjados no laudo). Todo campo externo passa por Text, saindo literal.
  • Removido arquivo de resíduo versionado por engano na raiz do repositório.

Mudado

  • Contrato JSON alinhado à suíte (schema: "suite-appsec/1"): identificador do achado em id (era check), severity_rank para ordenar entre ferramentas sem tabela de-para, by_severity sempre com as 5 chaves (inclusive zeradas) e owasp_edition como campo próprio. Quebra quem consumia a chave check.
  • OWASP Top 10 migrado para a edição 2025 (vigente): o ano vai no cabeçalho da coluna (OWASP 2025 / CWE) e no JSON; os códigos passam a A02→A04 (Cryptographic Failures), A03→A05 (Injection), A10→A01 (SSRF absorvido em Broken Access Control). Antes o dado não estava errado, mas incoerente com o resto da suíte.
  • batch: código de saída depende só de --fail-on. Linha malformada é ruído normal de token colhido de log — a contagem vai para o stderr e não derruba mais o build (era exit 2). O 2 fica reservado a erro de uso. Novo --strict para quem quiser travar em entrada malformada.
  • --fail-on passa a aceitar info.
  • Subcomando rules renomeado para regras (rules continua como alias).

Adicionado

  • Aviso de autorização em tempo de execução nos comandos ofensivos (crack, forge, forge-confusion): imprimem o enquadramento legal (Lei 12.737/2012 c/ 14.155/2021) no stderr antes de agir.
  • --wordlist do crack é lida em streaming (linha a linha): consumo de memória O(1) e o primeiro acerto encerra a leitura, em vez de materializar a lista inteira (rockyou ~140 MB carregava centenas de MB antes do 1º palpite).
  • Portão de cobertura no CI (--cov-fail-under=90), dependabot.yml e actions do CI fixadas por SHA.
  • Meta-teste de catálogo: toda checagem do CATALOG passa a exigir um caso positivo (e casos negativos que matam a inversão silenciosa de um detector) — uma checagem nova nasce vermelha até ter teste.
  • Detecção de JWT confusion por aninhamento (checagem passiva, sem rede): header-cty-nested sinaliza quando o cabeçalho declara cty: JWT (comparação case-insensitive e com o prefixo application/ opcional — RFC 7515 §4.1.10 / RFC 7519 §5.2), indicando um token aninhado cuja casca um verificador pode validar sem checar o miolo; payload-nested-jwt sinaliza quando uma claim carrega o que aparenta ser outro JWS compacto (heurística conservadora: 3 segmentos e um cabeçalho base64url que decodifica para um objeto JSON com alg). Ambas com A07 · CWE-347. Fecha o item de roadmap "jwt confusion via cty/nested tokens".
  • Modo batch (chaveiro batch): audita vários tokens — um por linha — de um arquivo ou do stdin (-), reusando o mesmo pipeline de auditoria passiva do inspect. Ignora linhas em branco e comentários (#) e remove um prefixo Bearer opcional. Reporta cada token e um resumo agregado (console ou JSON, mode: "batch"). Exit-code coerente com --fail-on: 1 se a pior severidade do lote atingir o limiar, 2 se houver token malformado (sem atingir o limiar), 0 caso contrário. Um token malformado é reportado sem interromper o lote. Fecha o item de roadmap "Modo batch". A composição decode + checagens virou audit.audit_token, fonte única usada por inspect e batch.
  • Verificação de assinatura PS256/PS384/PS512 (RSASSA-PSS, MGF1 e salt do tamanho do hash — RFC 7518 §3.5) e EdDSA/Ed25519 (RFC 8037) em core.jwt.verify_asymmetric, e ambos na allowlist do módulo de referência. Verificação real via cryptography; testes de round-trip provam que a assinatura válida passa e que a adulterada (ou verificada com a chave errada) é rejeitada. Fecha o item de roadmap "PS*/EdDSA na referência".

[0.1.0] — 2026-07-21

Adicionado

  • Decodificação de JWS compacto e primitivas de assinatura/verificação (HMAC HS*, RS*/ES* via cryptography).
  • 17 checagens passivas: alg:none, alg ausente/desconhecido, jku/x5u/jwk/x5c, kid injection, exp/iat/aud/iss, vida útil longa e dado sensível no payload.
  • Ataques: crack (dicionário HMAC com lista embutida) e forge-confusion (PoC de confusão de algoritmo RS→HS).
  • Módulo de referência de validação correta (allowlist de algoritmos, rejeita none, confere assinatura + exp/nbf + aud/iss).
  • CLI chaveiro (inspect, crack, forge-confusion, forge, rules).
  • Suíte de testes com par RSA real e PoC de confusão reproduzido; mypy strict; CI.