Skip to content

Apply custom CSS to auth pages (safely) + role-specific contributor help#273

Merged
fabiodalez-dev merged 2 commits into
mainfrom
feat/auth-custom-css-and-contributor-help
Jul 21, 2026
Merged

Apply custom CSS to auth pages (safely) + role-specific contributor help#273
fabiodalez-dev merged 2 commits into
mainfrom
feat/auth-custom-css-and-contributor-help

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Closes #262. Addresses #237. Supersedes #265.

Custom CSS on auth pages (#262 / #265)

advanced.custom_header_css only applied to the app chrome, so it never reached the login/register/forgot/reset pages — a user who wants to hide the #255 registration fields via CSS couldn't (#262). This adds a shared partial app/Views/auth/partials/custom-css.php, includes it on all five auth views, and refactors frontend/layout.php to use it (removing its inline duplicate).

This reimplements the intent of #265 (by @Himura2la) safely. #265 emitted the CSS through ContentSanitizer::normalizeExternalAssets() (fonts-only), which does not strip a </style><script>… breakout — merging it would have reintroduced the stored-XSS that was fixed earlier, and this time on unauthenticated pages. The partial uses ContentSanitizer::sanitizeCustomCss() (the same render-time sanitizer frontend/layout.php already uses), which strips <style>/<script> and HTML comment markers.

Verified live: an injected </style><script> payload renders as inert CSS text (window flag undefined, page title unchanged, 0 script tags in <head>), while legitimate CSS still applies on /register and /accedi. The existing custom-css-injection.spec.js still passes.

Role-specific contributor help (#237, @HansUwe52)

Illustrator/Translator/Curator/Colorist showed the same generic "search for an existing author" hint. Each now has its own role-named help string, translated across all four locales (it/en/de/fr).

Tests

tests/auth-custom-css-and-contributor-help.unit.php (17 assertions) locks in the safe sanitizer, the partial inclusion on every view, and the role-help keys across all four locales.

Summary by CodeRabbit

  • Nuove funzionalità

    • Aggiunto il supporto a CSS personalizzati nelle pagine di accesso, registrazione e recupero password, con applicazione coerente anche nella layout principale.
  • Miglioramenti

    • Testi di aiuto aggiornati e resi specifici per ruolo per illustratori, traduttori, curatori e coloristi.
    • Aggiunte nuove traduzioni (it-IT, en-US, de-DE, fr-FR) per i suggerimenti e i prompt di ricerca/creazione dei ruoli.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6f2027e4-afeb-4f1d-a50b-697abd0517e0

📥 Commits

Reviewing files that changed from the base of the PR and between e792a81 and 1129375.

⛔ Files ignored due to path filters (45)
  • composer.lock is excluded by !**/*.lock
  • vendor/composer/autoload_classmap.php is excluded by !vendor/**
  • vendor/composer/autoload_static.php is excluded by !vendor/**
  • vendor/composer/installed.json is excluded by !vendor/**
  • vendor/composer/installed.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/README.md is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/composer.json is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/package-lock.json is excluded by !**/package-lock.json, !vendor/**
  • vendor/guzzlehttp/guzzle/src/Client.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/CurlVersion.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/Proxy.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/ProxyEnvironment.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Handler/TlsVersion.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/MessageFormatter.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Multiplexing.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Pool.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/RequestOptions.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/Utils.php is excluded by !vendor/**
  • vendor/guzzlehttp/guzzle/src/functions.php is excluded by !vendor/**
  • vendor/guzzlehttp/promises/src/Utils.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/README.md is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/composer.json is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/Header.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/Message.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/MessageTrait.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/MimeType.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/MultipartStream.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/Request.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/Rfc7230.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/ServerRequest.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/Uri.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/UriComparator.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/UriNormalizer.php is excluded by !vendor/**
  • vendor/guzzlehttp/psr7/src/Utils.php is excluded by !vendor/**
📒 Files selected for processing (13)
  • app/Views/auth/forgot-password.php
  • app/Views/auth/login.php
  • app/Views/auth/partials/custom-css.php
  • app/Views/auth/register.php
  • app/Views/auth/register_success.php
  • app/Views/auth/reset-password.php
  • app/Views/frontend/layout.php
  • app/Views/libri/partials/book_form.php
  • locale/de_DE.json
  • locale/en_US.json
  • locale/fr_FR.json
  • locale/it_IT.json
  • tests/auth-custom-css-and-contributor-help.unit.php

📝 Walkthrough

Walkthrough

Il CSS personalizzato viene centralizzato in una partial sanitizzata e incluso nelle pagine auth e frontend. Il form libri ora mostra help distinti per ruolo contributore, con traduzioni in italiano, inglese, tedesco e francese. Sono aggiunti test automatici per entrambe le integrazioni.

Changes

CSS personalizzato condiviso

Layer / File(s) Summary
Partial e integrazione del CSS
app/Views/auth/partials/custom-css.php, app/Views/auth/*.php, app/Views/frontend/layout.php
Il CSS configurato viene sanitizzato e renderizzato dalla partial condivisa, inclusa nelle pagine auth e nel layout frontend.

Help localizzati per contributore

Layer / File(s) Summary
Messaggi specifici per ruolo
app/Views/libri/partials/book_form.php, locale/*.json
Illustratori, traduttori, curatori e coloristi ricevono testi di supporto distinti nelle quattro lingue.

Verifiche

Layer / File(s) Summary
Test di integrazione statica
tests/auth-custom-css-and-contributor-help.unit.php
Il test controlla sanitizer, inclusioni, assenza di blocchi duplicati e traduzioni non vuote.

Estimated code review effort: 2 (Semplice) | ~10 minuti

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Il titolo descrive bene l’aggiunta di CSS custom sicuro sulle pagine auth e degli aiuti per i contributori.
Linked Issues check ✅ Passed La CSS custom ora viene inclusa nella /register e nelle altre pagine auth, con sanitizzazione, quindi l’obiettivo #262 è soddisfatto.
Out of Scope Changes check ✅ Passed Le modifiche a help contributori, traduzioni e test sono coerenti con gli obiettivi del PR e non mostrano cambi fuori tema evidenti.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/auth-custom-css-and-contributor-help

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Himura2la

Himura2la commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

btw, what's the real issue with the </style><script>? Next to Custom CSS feature is Custom JS, and the write access to both is only granted to instance admins, so plain users should have no ability to inject anything to other users under normal conditions with these fields....

So how this stored-XSS can even be exploited? And what's the difference of </style><script>... and a legitimate "Essential JavaScript"?

Or am I miss something?

@fabiodalez-dev

Copy link
Copy Markdown
Owner Author

Fair point, and you're mostly right. Under the current model this isn't a privilege-escalation XSS: advanced.custom_header_css is admin-only write (CSRF-gated), and the Custom JS field right next to it runs arbitrary JS by design. An admin who breaks out of <style> with </style><script> gains nothing they couldn't already do through the JS field, and a plain user can't write either field — so there's no lower-privilege attacker. "Stored XSS" was stronger wording than it deserves in the classic sense; it's really field-contract hardening.

Where it matters concretely, and specifically because of this PR:

Before #273, both custom_header_css and Custom JS lived only in frontend/layout.php. This PR now also renders the CSS on the unauthenticated auth pages (login/register/forgot/reset). The Custom JS feature does not run there — it's emitted only in frontend/layout.php, cookie-consent gated (0 occurrences in the five auth templates). So on the pre-login pages there is deliberately no JS-execution path at all.

If the partial emitted the CSS unsanitized, a </style><script>… value would be the one way to get script running on those pre-auth pages — for every anonymous visitor to the login screen — a surface the JS field never reaches. That's an actual expansion of where script can run, introduced by this PR. So the partial runs it through sanitizeCustomCss() (the same sanitizer frontend/layout.php already uses) to keep the "CSS" box doing only CSS there.

Difference from "Essential JavaScript": that field is an explicit, labeled, cookie-category-aware opt-in to run JS, scoped to the frontend layout. The CSS field is labeled CSS and now renders on more surfaces including pre-auth; sanitizing keeps enabling this feature from silently turning the CSS box into a JS channel on pages the JS box was never wired into.

So: agreed it's not cross-user XSS — but for a field that now renders pre-auth I'd rather it can only ever style. Shout if you see a hole in that.

…ies)

Five advisories were published against guzzlehttp/guzzle <7.15.1 on
2026-07-20, turning the "composer audit (known CVEs)" CI step red on main
and every open PR:

  - CVE-2026-59883 — cookie disclosure/injection via IP-address domains
  - Host-only cookie scope not preserved
  - URI fragments disclosed in redirect Referer headers
  - Unbounded response cookies (DoS risk)
  - Proxy-Authorization headers can leak to origin servers

All fixed in 7.15.1. The root constraint is already ^7.8, so only the lock
+ vendor tree change (no composer.json edit). Pulled promises 2.5.0→2.5.1
and psr7 2.12.1→2.13.0 as required transitive upgrades.

Verified: composer audit --no-dev clean, autoloader carries no phpstan
(0), platform_check still >= 80200, Guzzle client instantiates, PHPStan
level 5 full-tree clean.
…ontributor help (#237)

Custom CSS on auth pages (fixes #262, closes #265)
--------------------------------------------------
The admin-configured advanced.custom_header_css only applied to the app
chrome, so it never reached the login/register/forgot/reset pages — a user
who wanted to hide the #255 registration fields via CSS couldn't. Extract a
shared partial app/Views/auth/partials/custom-css.php, include it on all five
auth views, and refactor frontend/layout.php to use it (removing its inline
duplicate).

Reimplements the intent of PR #265 by @Himura2la SAFELY: that PR emitted the
CSS through ContentSanitizer::normalizeExternalAssets() (fonts-only), which
does NOT strip a </style><script>… breakout — merging it would have
reintroduced the stored-XSS fixed earlier, now on UNAUTHENTICATED pages. The
partial uses ContentSanitizer::sanitizeCustomCss() (the same render-time
sanitizer as frontend/layout.php), which strips <style>/<script> and HTML
comment markers. Verified live: an injected </style><script> payload renders
as inert CSS text (window flag undefined, title unchanged, 0 script tags)
while legitimate CSS still applies.

Role-specific contributor help text (#237, @HansUwe52)
------------------------------------------------------
Illustrator/Translator/Curator/Colorist fields showed the same generic
"search for an existing author" hint. Give each its own role-named help
string, translated across all four locales (it/en/de/fr).

Guard: tests/auth-custom-css-and-contributor-help.unit.php asserts the partial
uses the safe sanitizer, every view includes it, and all four locales carry
the role-help keys.
@fabiodalez-dev
fabiodalez-dev force-pushed the feat/auth-custom-css-and-contributor-help branch from e792a81 to 1129375 Compare July 21, 2026 08:43
@Himura2la

Himura2la commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

@fabiodalez-dev thanks for the detailed explanation, but what I don't understand yet is "why is it a problem to allow custom js on unauthenticated pages?" If an instance admin uses the XSS for injecting custom js into its pages bypassing cookie-category-aware consent, then it's purely their own responsibility for breaking GDPR on his instance via a dirty hack.

They also can modify source code to get the same result, it would be just a little bit harder...

Am I still missing something? Should you really take responsibility for admin's misuse of your interface?

@fabiodalez-dev

Copy link
Copy Markdown
Owner Author

You're not missing anything — you're right. For a trusted admin (the only one who can write either field, who can already run JS via the Custom JS box or by editing source), this isn't a privilege boundary being crossed. There's no untrusted actor and no victim whose trust is violated: an admin serving their own script on their own pages is exactly what the Custom JS feature does by design. So it isn't a security vulnerability, and calling it "XSS" earlier was too strong — my bad.

What actually justifies sanitizeCustomCss() in the partial is narrower: consistency. frontend/layout.php already runs custom CSS through that same sanitizer (that shipped in 0.7.38), so the partial matches the existing render path instead of diverging. If the partial used the fonts-only normalizer, custom CSS would behave differently on the auth pages than on the rest of the public site — </style> stripped in one place, not the other. Keeping it identical is cheap and lossless for any legitimate CSS.

Whether the app should sanitize custom CSS at all — "it's the admin's instance, let them shoot their own foot" — is a fair position, but that's a question about the existing frontend behavior, not this PR. #273 just keeps the two paths consistent. If you think the sanitization should be dropped as theater against a non-threat, open an issue and I'll weigh it on its own merits — I'd rather change both paths together than have them disagree.

So: agreed, it's not my job to police an admin's self-misuse, and this isn't guarding against that. It's only keeping one render path from behaving differently than the one already shipped.

@fabiodalez-dev
fabiodalez-dev merged commit d3c544e into main Jul 21, 2026
7 checks passed
@fabiodalez-dev
fabiodalez-dev deleted the feat/auth-custom-css-and-contributor-help branch July 21, 2026 11:27
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.

Custom CSS does not apply on the /register page

2 participants