Skip to content

Endurece o leitor de XLSX contra DTD nos membros XML - #33

Merged
matbrgz merged 4 commits into
masterfrom
endurece-leitor-xlsx-dtd
Oct 12, 2026
Merged

matbrgz merged 4 commits into
masterfrom
endurece-leitor-xlsx-dtd

Conversation

@matbrgz

@matbrgz matbrgz commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

O que mudou

Revisão de segurança apontou que src/namescommons/spreadsheet.py lê os membros XML do XLSX com xml.etree.ElementTree. O risco já era baixo (o expat do Python 3.12 limita a expansão de entidades e o ElementTree não resolve entidades externas, ADR 0005), mas agora há defesa em profundidade, sem dependência nova:

  • _membro devolve o fluxo envolvido em _SemDtd, que lê o prólogo (no máximo 64 KiB, até o elemento raiz), ignora declaração XML, comentários e instruções, e levanta FormatError (<arquivo>: membro <membro> tem DTD ...) se achar <!DOCTYPE ou <!ENTITY. Depois repassa o prefixo ao parser e segue lendo o resto em fluxo.
  • Prólogo maior que 64 KiB sem elemento raiz também é recusado.
  • ADR 0005 (consequências) e spec 003b R1.4 (lista de guardas) atualizados. Manutenção: sem spec nova.

Critérios de aceite e verificação

  • Membro com <!DOCTYPE x [<!ENTITY a "b">]> falha com FormatError (workbook, rels, sharedStrings e aba; variações com doctype minúsculo e ENTITY após comentário).
  • Payload billion-laughs falha com FormatError, agora pela checagem de DTD.
  • DTD depois de BOM UTF-8 (com e sem comentário antes) também é recusado nos quatro membros (workbook, rels, sharedStrings, aba).
  • <!DOCTYPE em comentário do prólogo ou como texto de célula não é DTD; BOM e declaração XML continuam funcionando.
  • XLSX normal segue lendo; teste de 300 mil linhas em fluxo com tracemalloc (pico abaixo de 5 MiB) e o teste existente de linhas saindo da árvore continuam passando.
  • uv run pytest -q: 522 passed. Passos offline da CI (namescommons run --offline com exemplo e com as fixtures oficiais) rodaram com sucesso.
  • Commits em TDD: testes falhando primeiro, depois a implementação.

Decisões tomadas sozinho

  • Limite de 64 KiB para o prólogo: se um XLSX legítimo tivesse prólogo maior (improvável, Excel grava poucas dezenas de bytes), seria recusado; custo: subir a constante.
  • Decodificação do prefixo: UTF-8 (BOM removido) por padrão, UTF-16 por BOM ou bytes nulos. UTF-32 e EBCDIC e outras codificações exóticas passam sem checagem de DTD e caem nas proteções do expat (custo: nenhuma regressão, só menos defesa).

🤖 Generated with Claude Code

matbrgz and others added 4 commits October 12, 2026 12:24
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Defesa em profundidade sem dependência nova: um leitor inspeciona o prólogo
(até 64 KiB, até o elemento raiz), levanta FormatError se achar DOCTYPE ou
ENTITY e devolve o prefixo ao parser, mantendo a leitura em fluxo. Atualiza
o ADR 0005 e a spec 003b (R1.4).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Remove o BOM UTF-8 antes de decodificar (a decodificação latin-1 deixava o
DTD passar), eleva o regex ao módulo, renomeia a variável sombreada e
alinha as duas mensagens de erro.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@matbrgz
matbrgz merged commit 39da005 into master Oct 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant