Skip to content

fix(desktop): externalize boot <style> to prevent Tauri CSP nonce override - #5242

Merged
wesbillman merged 2 commits into
mainfrom
duncan/autocomplete-cursor-regression
Aug 7, 2026
Merged

fix(desktop): externalize boot <style> to prevent Tauri CSP nonce override#5242
wesbillman merged 2 commits into
mainfrom
duncan/autocomplete-cursor-regression

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Problem

Two v0.5.6-only regressions were introduced by #4614 (the first enforced Tauri CSP):

  1. Tab-complete caret regression — after tab-completing an @mention, #channel, or :emoji: shortcode, the cursor landed inside the inserted text instead of after the trailing space. TipTap inserts the correct text including the trailing space, but without its base stylesheet (.ProseMirror { white-space: break-spaces }) the trailing space collapses visually and the caret appears mid-name.

  2. Emoji picker unstyled — the emoji-mart picker rendered as a giant unstyled layout (oversized search SVG, collapsed grid) because emoji-mart's shadow-root stylesheet injection was also blocked.

Both symptoms have the same root cause.

Root Cause

Tauri's build-time asset processor scans index.html for inline <style> elements, injects a nonce token, and adds the corresponding 'nonce-…' source to style-src at runtime. Per the CSP spec, once a nonce is present in a directive, the browser ignores 'unsafe-inline' for that directive.

index.html contained an inline <style> with the boot background color. When Tauri nonced it and injected 'nonce-…' into style-src, the intended style-src 'self' 'unsafe-inline' became effectively style-src 'self' 'nonce-…' — blocking any runtime stylesheet injection not covered by a matching nonce:

  • TipTap's injectCSS()createStyleTag() injecting .ProseMirror { white-space: break-spaces; … }
  • emoji-mart's shadow-root document.createElement('style') injection

(Inline scripts follow a separate path — they are SHA-256 hashed, not nonced.)

This only reproduces in packaged builds (where Tauri's custom protocol serves the HTML and enforces the policy). tauri dev loads from the Vite dev server and is not affected.

Fix

Move html { background-color: #000; } from an inline <style> in index.html to desktop/public/boot.css, linked via <link rel="stylesheet">. A linked stylesheet is not subject to Tauri's nonce injection, so 'unsafe-inline' in style-src applies as declared.

The <link> is render-blocking (same as the inline style was), so boot-flash behaviour is identical.

The production CSP string is unchanged. This fix makes the policy apply as intended — no security properties are altered. Will's follow-up with the security team (Jordan Mecom / Eli Foster, authors of #4614) is noted for post-ship.

A Tauri-faithful CSP harness for the Vite dev path (so this class of regression is visible before a packaged build) is tracked as a separate follow-up.

Files Changed

  • desktop/index.html — replace inline <style> with <link rel="stylesheet" href="/boot.css" />
  • desktop/public/boot.css — new file, the extracted html { background-color: #000; } plus rationale comment
  • desktop/src-tauri/tests/csp.rs — update comment: nonce for styles, SHA-256 for the boot script

Testing

  • just desktop-typecheck
  • just desktop-test ✅ (4535/4535)
  • just desktop-tauri-test ✅ (all Rust tests including csp.rs)
  • Packaged validation: pnpm tauri build --debug completed; compiled binary bakes style-src 'self' 'unsafe-inline' with no nonce source injected ✅

Tauri's build-time asset processor finds the inline <style> in
index.html, computes its SHA256, and injects a 'sha256-…' source into
style-src at compile time. Per the CSP spec, once a hash or nonce is
present in a directive the browser ignores 'unsafe-inline' for that
directive — so TipTap's runtime createStyleTag call and emoji-mart's
shadow-root style injection were both blocked in packaged builds,
manifesting as two v0.5.6 regressions:

  • Tab-complete caret landing inside @name instead of after the
    trailing space (whitespace collapses without TipTap's
    white-space: break-spaces stylesheet).
  • Emoji picker rendering unstyled/janky.

Fix: move html { background-color: #000; } from an inline <style> to
desktop/public/boot.css and link it from <head>. A linked stylesheet
is not subject to Tauri's build-time hash injection, so 'unsafe-inline'
in style-src applies as written. The stylesheet is render-blocking, so
boot-flash behaviour is identical to before.

Also add devCsp matching the production policy with dev-appropriate
script-src allowances so future packaged-only CSP regressions are
visible in the csp.rs tests even before a release build.

The production CSP string itself is unchanged; the security policy is
unaffected. Will's follow-up with the security team (Jordan Mecom /
Eli Foster, authors of #4614) is noted for post-ship.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 requested a review from a team as a code owner August 7, 2026 21:08
Thufir pass-1 corrections:
- Replace sha256 with nonce throughout comments: Tauri nonces inline
  <style> elements (not SHA-256 hashes them); only inline scripts get
  the SHA-256 treatment.
- Remove devCsp field from tauri.conf.json: Tauri only applies devCsp to
  assets it serves via its custom protocol; the Buzz dev webview navigates
  directly to Vite's devUrl, so the field has no effect. Dev CSP detection
  is deferred to a separate Tauri-faithful Vite middleware follow-up.
- Deduplicate rationale: boot-order context in index.html (one line
  pointer to boot.css), full externalization reason in boot.css,
  script-policy invariant only in csp.rs.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wesbillman

Copy link
Copy Markdown
Collaborator

Independent release verdict by Carl on Wes’s behalf at exact head 3fdb8211e15ebdf6498932e984740324e4825e81: ship this hotfix. The root-cause diagnosis is correct, the patch fixes both reported packaged-build regressions, and I found no code blocker.

I independently traced the failure through the versions actually locked by this head:

  • Tauri codegen 2.6.3 calls inject_nonce_token; tauri-utils 2.9.3 adds __TAURI_STYLE_NONCE__ to every inline <style>; Tauri 2.11.5 replaces it per request and appends a 'nonce-…' source to style-src.
  • Under CSP, that nonce causes 'unsafe-inline' to be ignored for the directive. I reproduced the browser behavior directly in Chromium: style-src 'self' 'unsafe-inline' 'nonce-abc' accepts the nonced boot style but blocks a subsequently created unnonced <style>; removing the style nonce lets the same runtime injection apply.
  • TipTap 3.22.5 defaults injectCSS: true and dynamically creates an unnonced style containing .ProseMirror { white-space: break-spaces; }. Buzz has no equivalent static rule. Blocking it explains the visually collapsed trailing space/caret after autocomplete.
  • emoji-mart 5.6.0 dynamically creates its base shadow-root <style>, and Buzz adds a second unnonced shadow-root override in useEmojiMartStyles. Blocking both explains the giant, unstyled picker.

The fix removes the only real inline <style> from the built HTML, keeps the boot background in render-blocking same-origin /boot.css, and leaves the configured CSP string unchanged. A Vite production build contains the linked boot.css, no actual inline style element, and the existing inline boot script remains on Tauri’s separate SHA-256 path. The causal timing also matches: index.html had this style before #4614, while #4614 first enabled enforcement in v0.5.6.

Security precision: this does restore the effective style-src 'unsafe-inline' behavior that the checked-in policy explicitly requested; compared with broken v0.5.6, runtime inline styles become allowed again. That is necessary for the currently shipped TipTap/emoji-mart architecture, not an accidental broadening beyond the declared policy. The promised security-team follow-up is still appropriate if the product wants to remove 'unsafe-inline' later.

Validation at this exact head: typecheck passed; Biome passed on changed web assets; Vite production build passed and emitted /boot.css; direct Chromium CSP reproduction passed; git diff --check passed; clean worktree; remote head unchanged. My local cargo test --test csp could not reach tests because the review worktree lacks required Tauri sidecar binaries, but exact-head CI has Desktop Core/build/RC, Rust lint, Windows Rust, all smoke/integration shards, DCO, Semgrep, and zizmor green.

Non-blocking coverage gap: csp.rs only updates explanatory text; it does not assert that packaged index.html remains free of inline styles or that effective runtime styles work. The PR explicitly tracks a Tauri-faithful harness follow-up. Given the verified packaged build and severity of the regressions, do not delay the hotfix for that broader harness.

@wesbillman
wesbillman merged commit dcc1231 into main Aug 7, 2026
26 checks passed
@wesbillman
wesbillman deleted the duncan/autocomplete-cursor-regression branch August 7, 2026 22:05
wpfleger96 pushed a commit that referenced this pull request Aug 7, 2026
…format

* origin/main: (60 commits)
  feat(desktop): unify add agent flows (#5015)
  fix(buzz-agent): budget summarizer reasoning separately so it cannot starve the handoff summary (#5248)
  infra: bind development services to loopback (#4871)
  chore(release): release Buzz Desktop version 0.5.7 (#5252)
  fix(desktop): isolate relay admission tests (#5221)
  fix(desktop): externalize boot <style> to prevent Tauri CSP nonce override (#5242)
  fix(desktop): let imported and recovered identities finish onboarding (#5228)
  Recover from max-token response truncation (#5223)
  chore(release): release Buzz Desktop version 0.5.6 (#5214)
  fix(mobile): keep latest messages above composer (#4981)
  fix(sdk): preserve self-mention p tags in message and forum event builders (#4975)
  bump @tauri-apps/cli to ~2.11.4 to fix linux app icon issue (#4858)
  feat(desktop): adding rich link previews to messages (#3818)
  fix(buzz-agent): Responses reasoning summary, Anthropic display:summarized, ACP v2 messageId (#5195)
  fix(desktop): retain distinct agent instances in autocomplete (#5202)
  fix(desktop): defer channel visibility change to Save (#5203)
  feat(desktop): Projects follow-ups — access restrictions, fast loading, activity feed polish (#5073)
  refactor(cli): replace probe/decider/detail split with single typed extractor (#5191)
  fix(desktop): drop unhandled rejection from throwing window.Notification (#5143)
  fix(desktop): fence localStorage SecurityError from killing the React tree (#5142)
  ...

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96 added a commit that referenced this pull request Aug 8, 2026
* origin/main:
  fix(desktop): welcome banner overlap and missing dismiss control (#5330)
  fix(desktop): prevent horizontal clipping in Prompt Context modal (#5324)
  chore(release): release Buzz Relay version 0.2.1 (#2856)
  chore(release): release Buzz Desktop version 0.5.8 (#5326)
  fix(buzz-agent): recover from 400-shaped image rejections; unbound benchmark agent rounds (#5318)
  Revert "fix(acp): reject unattended permission requests" (#5323)
  feat(desktop): unify add agent flows (#5015)
  fix(buzz-agent): budget summarizer reasoning separately so it cannot starve the handoff summary (#5248)
  infra: bind development services to loopback (#4871)
  chore(release): release Buzz Desktop version 0.5.7 (#5252)
  fix(desktop): isolate relay admission tests (#5221)
  fix(desktop): externalize boot <style> to prevent Tauri CSP nonce override (#5242)
  fix(desktop): let imported and recovered identities finish onboarding (#5228)

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
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.

2 participants