Repository navigation
fix(markdown): sanitize content_html server-side (F-009) - #8
Merged
Merged
Conversation
…009) GithubFlavoredMarkdownConverter passed raw HTML and javascript:/data: links through untouched, and content_html was returned by the API exactly as stored. Any consumer other than the sanitizing Angular viewer was exposed to stored XSS, including rows already persisted before this fix. - Harden the converter config (html_input=escape, allow_unsafe_links=false) as a first layer at parse time. - Add HtmlSanitizer, a dependency-free DOM-based allowlist filter for the tags/attributes Markdown legitimately produces (headings, code, tables, GFM task lists, links, images), and apply it to freshly parsed content. - Add a content_html accessor on Document that runs the same sanitizer on every read, so API responses are safe even for documents whose HTML was written to the database before this fix (no data migration performed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…SS payloads
Unit-tests HtmlSanitizer directly against the required payload matrix
(script, on* handlers, iframe, svg onload, object/embed, style, form,
javascript:/data:text/html links, encoded entities, case variation,
control-byte scheme bypass) plus the legitimate rendering set.
Feature-tests the full MarkdownParser pipeline against the same malicious
Markdown/HTML mix, and proves a document whose content_html was written
directly to the database (bypassing ingestion entirely) is served
sanitized through GET /api/v1/documents/{document}.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mark F-009 as mitigated, add ADR-26 for the two-layer server-side HTML sanitization, update canonical state and backlog, document the discovery cache invalidation deploy step, and leave a handoff with the audit evidence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sanitization Renumbers the sanitization ADR to ADR-28 because main took ADR-26 and ADR-27, keeps both remediation phases in the canonical state, and drops the two backlog items completed by this branch and by PR #7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fecha o achado F-009 do audit de 2026-06-13:
content_html, derivado de Markdown de terceiros, era armazenado e devolvido pela API sem sanitização no servidor. O Angular era a única barreira, então qualquer outro consumidor — export, integração, client alternativo — recebia HTML executável.Causa raiz
Dois vetores independentes:
GithubFlavoredMarkdownConverterera instanciado sem configuração, mantendo os padrõeshtml_input=allowe links inseguros permitidos — HTML bruto embutido no Markdown passava intacto.DocumentResourceeDocumentDataliamcontent_htmldireto do model, sem segunda barreira, então documentos ingeridos antes da correção seguiam expostos.Correção
Sanitização em duas camadas, sem dependência nova (ver ADR-28):
html_input=escapeeallow_unsafe_links=false, e a saída é sanitizada antes de persistir.Document::contentHtml()sanitiza a cada acesso, sem reescrever a coluna — é o que cobre os documentos antigos sem migração nem reprocessamento de dados.App\Services\Markdown\HtmlSanitizeré um allowlist de tags e atributos sobreDOMDocument.href/srcaceitam apenashttp,https,mailtoe caminhos relativos, com remoção de bytes de controle antes da checagem de esquema. Isso neutraliza a classe de bypass do CVE-2026-71478 (commonmark 2.8.2) independentemente da atualização daquele pacote.A sanitização do Angular (
MarkdownRendererService) não foi tocada e permanece como defesa em profundidade.Testes
tests/Unit/Services/Markdown/HtmlSanitizerTest.php: 19 casos maliciosos (script, handlerson*, iframe, svg onload, object/embed, style, form,javascript:,data:text/html, entidades codificadas, variação de caixa, bytes de controle) e 8 casos legítimos.tests/Feature/MarkdownSanitizationTest.php: pipeline completo de parse, e documento comcontent_htmlmalicioso gravado direto no banco servido sanitizado pela API.Auditoria adversarial
Executada em banco isolado, descartado ao final; o banco de desenvolvimento não recebeu migrate nem reset.
on*ou esquemas perigosos sobrevivendo./plugins/{slug}/versions/{version}/documents, 38 documentos) verificada contra a falha "sanitiza só numa rota": 0 falhas.Gates
Após o merge da
main(que trouxe o PR #7), com a imagem do backend reconstruída — a Dockerfile fazCOPY . .e o container não tem bind mount, então rodar os testes sem rebuild valida código velho:main.skip/only/todoembackend/testsoufrontend/src.composer audit(advisories pré-existentes) e as 2 falhas de Jest emruntime-config.spec.tssão anteriores a esta branch e estão registradas no backlog.Passo de deploy
DiscoveryCacheguarda payloads resolvidos por 300s, então entradas cacheadas antes do deploy seguem servindo HTML não sanitizado até expirar. Invalidar o cache ao subir — comando emdocs/HUMAN-ACTIONS.md.🤖 Generated with Claude Code