fix(security): reforça sanitização de HTML/CSS contra mXSS e injeção - #143
Merged
Conversation
Endurece as defesas de defense-in-depth nos campos de HTML/CSS controlados por admin que chegam a m.trust()/innerHTML/<style> dos visitantes. - HtmlSanitizer (PHP) e sanitizeAdminHtml (JS): re-executam o scrub até a serialização estabilizar (defesa contra mutation-XSS via diferenças de parsing), removem nós de comentário e os elementos <noscript>/<template> (vetores clássicos de mXSS por mudança de contexto de parser). - sanitizeAdminHtml (JS): passa a higienizar o corpo de <style> (mantém CSS de animação, descarta @import/expression()/behavior/-moz-binding/url(data:) /esquemas de script). Antes o <style> do rodapé chegava sem inspeção aos visitantes, permitindo injeção/exfiltração via CSS. - TeamPage: valida a cor do grupo com o novo safeCssColor() antes de interpolar no atributo style=, evitando injeção de declarações CSS. - Testes: cobre strip de comentários, <noscript>/<template> e idempotência do sanitizador; atualiza a matriz SECURITY_COVERAGE.md. - Recompila os bundles em js/dist. https://claude.ai/code/session_01THwoXq4vQ9zJphMCPBS97m
Contributor
📦 Bundle size🟡 Crescimento pequeno — gzip total +0.3 KB (+0.5%), abaixo do limiar de regressão (2% / 2 KB).
gzip nível 9. 🟢 = menor que o master · 🔴 = maior. Report-only — não bloqueia o merge. |
Contributor
🔒 Regressão de segurança (Semgrep diff vs
|
Contributor
🔬 Performance benchmarkHome (
|
| Métrica | PR | Master (baseline) | Δ |
|---|---|---|---|
| Performance (score) | 81 | 82 | 🔴 -1pp (-1.2%) |
| FCP | 1853 ms | 1707 ms | 🔴 +145 ms (8.5%) |
| LCP | 2056 ms | 1951 ms | 🔴 +105 ms (5.4%) |
| TBT | 60 ms | 100 ms | 🟢 -40 ms (-40.0%) |
| CLS | 0.000 | 0.000 | (=) |
| Speed Index | 1853 ms | 1707 ms | 🔴 +145 ms (8.5%) |
| TTI | 2056 ms | 1951 ms | 🔴 +105 ms (5.4%) |
💡 Recomendações (Lighthouse + dicas Flarum 2 / Avocado)
- Sem compressão de texto — economia ~880 ms · ~1099 KB
Habilite gzip/brotli no servidor (nginx:gzip on; gzip_types text/css application/javascript;oubrotli on; brotli_types ...). Esse é um setting de host, não da extensão, mas reportar aqui ajuda. - JavaScript não utilizado — economia ~640 ms · ~516 KB
Useimport()dinâmico para componentes só usados em rotas específicas (TeamPage, AvocadoSearchPage). O webpack faz split automático se você usarimport("./components/TeamPage")dentro doroutesao invés de import estático no topo doindex.tsx. - CSS não utilizado — economia ~480 ms · ~538 KB
O bundleforum.csscarrega tudo deless/forum/**. Divida por rota (Home/Discussion/Tag) viaExtend\Frontend->css()condicional no JS, ou use PurgeCSS no webpack para o build de produção. - Recursos bloqueando o render — economia ~400 ms
Mova CSS não-crítico para<link rel="preload">ou injete inline o CSS above-the-fold doforum.less. Em Flarum 2, oforum.cssé servido como bloqueante por padrão — consideremedia="print" onloadpara folhas não-críticas (ex.: dark theme). - Cache HTTP curto — 8 resources found
Os assets versionados em/assets/forum-<hash>.jsdeveriam terCache-Control: public, max-age=31536000, immutable. Configure no nginx/apache, não no Flarum.
/discussions
| Métrica | PR | Master (baseline) | Δ |
|---|---|---|---|
| Performance (score) | 87 | 87 | (=) |
| FCP | 1445 ms | 1403 ms | 🔴 +42 ms (3.0%) |
| LCP | 1790 ms | 1788 ms | 🔴 +2 ms (0.1%) |
| TBT | 6 ms | 7 ms | 🟢 -1 ms (-14.3%) |
| CLS | 0.000 | 0.000 | (=) |
| Speed Index | 1445 ms | 1403 ms | 🔴 +42 ms (3.0%) |
| TTI | 1790 ms | 1788 ms | 🔴 +2 ms (0.1%) |
💡 Recomendações (Lighthouse + dicas Flarum 2 / Avocado)
- Sem compressão de texto — economia ~920 ms · ~1105 KB
Habilite gzip/brotli no servidor (nginx:gzip on; gzip_types text/css application/javascript;oubrotli on; brotli_types ...). Esse é um setting de host, não da extensão, mas reportar aqui ajuda. - JavaScript não utilizado — economia ~550 ms · ~538 KB
Useimport()dinâmico para componentes só usados em rotas específicas (TeamPage, AvocadoSearchPage). O webpack faz split automático se você usarimport("./components/TeamPage")dentro doroutesao invés de import estático no topo doindex.tsx. - Recursos bloqueando o render — economia ~520 ms
Mova CSS não-crítico para<link rel="preload">ou injete inline o CSS above-the-fold doforum.less. Em Flarum 2, oforum.cssé servido como bloqueante por padrão — consideremedia="print" onloadpara folhas não-críticas (ex.: dark theme). - CSS não utilizado — economia ~480 ms · ~544 KB
O bundleforum.csscarrega tudo deless/forum/**. Divida por rota (Home/Discussion/Tag) viaExtend\Frontend->css()condicional no JS, ou use PurgeCSS no webpack para o build de produção. - Cache HTTP curto — 9 resources found
Os assets versionados em/assets/forum-<hash>.jsdeveriam terCache-Control: public, max-age=31536000, immutable. Configure no nginx/apache, não no Flarum.
/tags
| Métrica | PR | Master (baseline) | Δ |
|---|---|---|---|
| Performance (score) | 87 | 88 | 🔴 -1pp (-1.1%) |
| FCP | 1447 ms | 1485 ms | 🟢 -37 ms (-2.5%) |
| LCP | 1747 ms | 1688 ms | 🔴 +59 ms (3.5%) |
| TBT | 0 ms | 0 ms | (=) |
| CLS | 0.000 | 0.000 | (=) |
| Speed Index | 1447 ms | 1485 ms | 🟢 -37 ms (-2.5%) |
| TTI | 1747 ms | 1688 ms | 🔴 +59 ms (3.5%) |
💡 Recomendações (Lighthouse + dicas Flarum 2 / Avocado)
- Sem compressão de texto — economia ~920 ms · ~1100 KB
Habilite gzip/brotli no servidor (nginx:gzip on; gzip_types text/css application/javascript;oubrotli on; brotli_types ...). Esse é um setting de host, não da extensão, mas reportar aqui ajuda. - Recursos bloqueando o render — economia ~520 ms
Mova CSS não-crítico para<link rel="preload">ou injete inline o CSS above-the-fold doforum.less. Em Flarum 2, oforum.cssé servido como bloqueante por padrão — consideremedia="print" onloadpara folhas não-críticas (ex.: dark theme). - JavaScript não utilizado — economia ~500 ms · ~541 KB
Useimport()dinâmico para componentes só usados em rotas específicas (TeamPage, AvocadoSearchPage). O webpack faz split automático se você usarimport("./components/TeamPage")dentro doroutesao invés de import estático no topo doindex.tsx. - CSS não utilizado — economia ~480 ms · ~543 KB
O bundleforum.csscarrega tudo deless/forum/**. Divida por rota (Home/Discussion/Tag) viaExtend\Frontend->css()condicional no JS, ou use PurgeCSS no webpack para o build de produção. - Cache HTTP curto — 8 resources found
Os assets versionados em/assets/forum-<hash>.jsdeveriam terCache-Control: public, max-age=31536000, immutable. Configure no nginx/apache, não no Flarum.
🥑 Dicas gerais para acelerar o tema (Flarum 2 + Avocado)
- Build de produção minificado — confirme que
npm run buildrodou commode: production(já é o caso nojs/package.json). Webpack tree-shakesimport { x } from "flarum/..."se o consumo for explícito. - Split por rota —
index.tsximporta todos os Components no topo. Trocar paraimport()dinâmico nas rotas raramente acessadas (TeamPage, AvocadoSearchPage, TagsPage) reduz oforum.jsinicial. - Less crítico inline — Flarum 2 serve
forum.cssbloqueando. Considere extrair o CSS above-the-fold (header + 1ª thread visível) e injetar inline viaExtend\Frontend->content(InlineCriticalCss::class). - Avatares — sirva via
<img loading="lazy">em todos os ThreadCards/PostCards que não estiverem no fold inicial. <link rel="preconnect">— se você usa CDN para fontes ou avatares S3, adicione preconnects no header.Extend\Frontend->js()rodam síncronos — todo arquivojs/dist/forum.jsé parseado no boot. Cadaextend()noindex.tsxroda antes da primeira pintura.- opcache + view cache — no host:
opcache.enable=1,opcache.validate_timestamps=0em produção, ephp flarum cache:clearno deploy. - HTTP/2 + Brotli no host — o Flarum gera bundles grandes; sem brotli você paga em transferência.
Lighthouse desktop, 1 run por URL. 🟢 = melhorou vs master · 🔴 = regrediu.
… phpunit O bump do phpunit para ^13.2.0 (Dependabot #141) quebrou a CI no master: 1. phpunit 13 exige PHP >= 8.4.1, mas flarum/core 2.x suporta ^8.3 e a matriz testa 8.3 → `composer update` falhava em todos os cells/jobs de PHP 8.3 (o --ignore-platform-req=php+ só ignora limites superiores, não inferiores). Constraint união `^11.5 || ^13.2.0`: 8.3 resolve phpunit 11.5, 8.4+ resolve 13. 2. `--filter sqlite` casava o data set no phpunit 11 mas não no 13, que então reportava "No tests executed" e saía com código 1, derrubando os steps de integração. Troca para a sintaxe de data set `--filter '#sqlite'` (idem mysql/pgsql), que funciona em ambas as versões. Validado local: composer test (80), phpstan (sem erros), integração #sqlite OK. https://claude.ai/code/session_01THwoXq4vQ9zJphMCPBS97m
…ente Com a estratégia padrão, o Dependabot não abre PR para releases do Flarum já cobertas pela faixa `^2.0.0` (só alargaria o range). `versioning-strategy: increase` faz ele bumpar o piso da constraint quando sai versão nova (incl. prereleases rc/beta, já que composer.json tem minimum-stability: dev), então novas versões do core passam a ser propostas e testadas pela CI automaticamente. - groups.flarum: consolida flarum/core + flarum/tags num único PR por ciclo. - ignore phpunit major: protege a constraint união ^11.5 || ^13.2.0 de ser revertida para 13-only (que quebraria os cells de PHP 8.3). https://claude.ai/code/session_01THwoXq4vQ9zJphMCPBS97m
ram0ng1
added a commit
that referenced
this pull request
Jun 11, 2026
fix(security): reforça sanitização de HTML/CSS contra mXSS e injeção
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.
Resumo
Endurece as defesas defense-in-depth nos campos de HTML/CSS controlados por administrador que acabam chegando a
m.trust()/innerHTML/<style>dos visitantes. Corrige os achados 1, 2 e 3 da revisão de segurança recente. Nenhuma vulnerabilidade era explorável por visitante anônimo ou usuário comum — todas dependem de conteúdo de admin — mas o objetivo declarado do projeto é "comprometimento de conta admin não deve virar XSS para guests".Mudanças
1. Defesa contra mutation-XSS (mXSS) —
HtmlSanitizer(PHP) esanitizeAdminHtml(JS)DOMDocument/DOMParser).<!-- ... -->) e os elementos<noscript>/<template>— vetores clássicos de mXSS por mudança de contexto de parser.2. Corpo de
<style>higienizado no sanitizador JS (corrige inconsistência com o PHP)sanitizeAdminHtmlnão inspecionava<style>, então o campo de rodapé (footerHtml, içado para o<head>) podia carregar@import url(//evil)/expression()/url(data:)etc. servidos a todos os visitantes (exfiltração/injeção via CSS).<style>quando ele contém um sink de CSS.3. Injeção de CSS via cor de grupo na TeamPage
TeamPage.tsxinterpolavarole.color()cru emstyle=. Agora valida via novo helpersafeCssColor()(espelha osafeColor()do PHP usado no spinner); valor inválido/forjado cai para "sem background inline" em vez de injetar declarações CSS.Testes
tests/Unit/HtmlSanitizerTest.php: novos casos para strip de comentários,<noscript>/<template>e idempotência do sanitizador (o loop anti-mXSS).composer test→ 80 testes OK ·composer analyse(phpstan) → sem erros ·tsc --noEmit→ OK ·npm run build(lint + webpack) → OK.js/distrecompilados (oextend.phpcarrega o bundle compilado).tests/SECURITY_COVERAGE.md(§9 / §9.3) atualizada.Fora de escopo
<head>); fica como observação.https://claude.ai/code/session_01THwoXq4vQ9zJphMCPBS97m
Generated by Claude Code