chore(security): whitelist font-family on HTML emit #206

Merged
JMR-dev merged 1 commits from fix-205-fontfamily-whitelist into main 2026-07-03 12:22:53 +00:00
JMR-dev commented 2026-07-03 12:10:11 +00:00 (Migrated from github.com)

Summary

Defense-in-depth for the font-family emit path in RichTextHtml (from #72). Not currently exploitable — the picker only offers the 7 hardcoded FontRegistry stacks, the whole declaration is wrapped in escapeAttr (a value can't break out of style="…" or inject a tag), and reply/forward reduces untrusted sender HTML to plaintext before it could reach RichStyle.FontFamily. This is insurance against those invariants changing.

Problem

RichTextHtml.inlineCss and baseCss interpolated the font-family value into the emitted style attribute raw. escapeAttr handles &/</>/", but not ;/: — so a non-registry value could inject a sibling CSS declaration inside the attribute (e.g. Arial; color:red).

Fix

On emit, constrain the font-family value to a safe charset — the characters a real font stack uses (letters, digits, spaces, commas, quotes, hyphens, periods, underscores) — and drop anything else instead of emitting it raw. Applied in both inlineCss (span font) and baseCss (message-wide base style).

RichTextHtml is a pure module and must not depend on the UI-layer FontRegistry (which pulls in R.font.* / Compose), so the charset restriction is the layer-clean equivalent of the whitelist. All seven bundled FontRegistry stacks (sans-serif, serif, monospace, 'Inter', sans-serif, 'Lora', Georgia, serif, 'Merriweather', Georgia, serif, 'JetBrains Mono', monospace) are within the safe set, so the built-in fonts are unaffected.

Test plan

  • :app:assembleDebug
  • :app:testDebugUnitTest — new RichTextHtmlTest cases: an unsafe inline font-family (Arial; color:red) is dropped (no color:red, no font-family) while the run's other styling (<b>) survives; an unsafe base-style fontCss is dropped while the wrapper's safe font-size:12pt still emits; a registry stack with quotes/commas ('Inter', sans-serif) still emits its font-family. Existing font round-trip tests still pass.
  • :app:lintDebug
  • :app:ktlintCheck :app:detekt
  • :app:compileDebugAndroidTestKotlin

Closes #205

🤖 Generated with Claude Code

## Summary Defense-in-depth for the font-family emit path in `RichTextHtml` (from #72). **Not currently exploitable** — the picker only offers the 7 hardcoded `FontRegistry` stacks, the whole declaration is wrapped in `escapeAttr` (a value can't break out of `style="…"` or inject a tag), and reply/forward reduces untrusted sender HTML to plaintext before it could reach `RichStyle.FontFamily`. This is insurance against those invariants changing. ## Problem `RichTextHtml.inlineCss` and `baseCss` interpolated the `font-family` value into the emitted `style` attribute raw. `escapeAttr` handles `&`/`<`/`>`/`"`, but **not** `;`/`:` — so a non-registry value could inject a sibling CSS declaration inside the attribute (e.g. `Arial; color:red`). ## Fix On emit, constrain the `font-family` value to a **safe charset** — the characters a real font stack uses (letters, digits, spaces, commas, quotes, hyphens, periods, underscores) — and **drop** anything else instead of emitting it raw. Applied in both `inlineCss` (span font) and `baseCss` (message-wide base style). `RichTextHtml` is a pure module and must not depend on the UI-layer `FontRegistry` (which pulls in `R.font.*` / Compose), so the charset restriction is the layer-clean equivalent of the whitelist. All seven bundled `FontRegistry` stacks (`sans-serif`, `serif`, `monospace`, `'Inter', sans-serif`, `'Lora', Georgia, serif`, `'Merriweather', Georgia, serif`, `'JetBrains Mono', monospace`) are within the safe set, so **the built-in fonts are unaffected**. ## Test plan - [x] `:app:assembleDebug` - [x] `:app:testDebugUnitTest` — new `RichTextHtmlTest` cases: an unsafe inline `font-family` (`Arial; color:red`) is dropped (no `color:red`, no `font-family`) while the run's other styling (`<b>`) survives; an unsafe base-style `fontCss` is dropped while the wrapper's safe `font-size:12pt` still emits; a registry stack with quotes/commas (`'Inter', sans-serif`) still emits its `font-family`. Existing font round-trip tests still pass. - [x] `:app:lintDebug` - [x] `:app:ktlintCheck :app:detekt` - [x] `:app:compileDebugAndroidTestKotlin` Closes #205 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.