Files
actual/upcoming-release-notes/7239.md
Matiss Janis Aboltins b6fbc7dd1e [AI] Custom Themes: custom font family (#7239)
* [AI] Add secure custom font support for custom themes

Implement safe font-family references in custom themes via CSS variables
(--font-body, --font-mono, --font-heading, etc.) validated against a
curated allowlist of system-installed and web-safe fonts.

Security approach: Only fonts already present on the user's OS or bundled
with the app are allowed. No @font-face, no url(), no external font
loading — this prevents third-party tracking via font requests while
still enabling meaningful font customization in themes.

Key changes:
- Add SAFE_FONT_FAMILIES allowlist (~80 fonts: generic families, bundled
  fonts, and common system fonts across platforms)
- Add validateFontFamilyValue() for comma-separated font stack validation
- Route --font-{body,mono,heading,family,ui,display,code} properties
  through the font validator instead of the color validator
- Update index.html to use var(--font-body, ...) with current Inter
  Variable stack as fallback
- Add comprehensive tests for valid/invalid font values and security
  edge cases (url injection, javascript:, expression(), etc.)

https://claude.ai/code/session_01D4ASLpcBCvWF1nzPLz9Tw5

* [AI] Add @font-face support with data: URI embedding for custom themes

Enable truly custom fonts in themes while maintaining zero runtime network
requests. Theme authors can include font files in their GitHub repos, and
fonts are automatically downloaded and embedded as data: URIs at install
time — the same approach used for theme CSS itself.

Security model:
- @font-face blocks only allow data: URIs (no http/https/relative URLs)
- Font MIME types are validated (font/woff2, font/ttf, etc.)
- Individual font files capped at 2MB, total at 10MB
- @font-face properties are allowlisted (font-family, src, font-weight,
  font-style, font-display, font-stretch, unicode-range only)
- Font-family names from @font-face are available in --font-* variables
- No runtime network requests — all fonts stored locally after install

Key additions:
- extractFontFaceBlocks(): parse @font-face from theme CSS
- validateFontFaceBlock(): validate properties and data: URIs
- splitDeclarations(): semicolon-aware parser that respects data: URIs
- embedThemeFonts(): fetch font files from GitHub, convert to data: URIs
- ThemeInstaller calls embedThemeFonts() during catalog theme installation
- 30+ new test cases for @font-face validation and security edge cases

Example theme CSS with custom fonts:
  @font-face {
    font-family: 'My Font';
    src: url('./MyFont.woff2') format('woff2');
  }
  :root { --font-body: 'My Font', sans-serif; }

https://claude.ai/code/session_01D4ASLpcBCvWF1nzPLz9Tw5

* [AI] Rename --font-body CSS variable to --font-family

https://claude.ai/code/session_01D4ASLpcBCvWF1nzPLz9Tw5

* [AI] Remove font-family allowlist and broaden --font-* regex

- Remove SAFE_FONT_FAMILIES allowlist and SAFE_FONT_FAMILIES_LOWER lookup.
  Any font name is now valid in --font-* properties. Referencing a font
  that isn't installed simply triggers the browser's normal fallback — no
  network requests, no security risk. Function calls (url(), expression(),
  etc.) are still blocked.

- Change the --font-* property regex from a specific list
  (family|mono|heading|...) to match all --font-* variables, so theme
  authors can use any --font-prefixed custom property.

https://claude.ai/code/session_01D4ASLpcBCvWF1nzPLz9Tw5

* [AI] Simplify and improve custom font validation code

Code quality improvements from review:

- Remove dead `declaredFonts` Set (was populated but never read after
  allowlist removal)
- Extract `stripQuotes()` helper to deduplicate quote-stripping logic
  between `validateFontFamilyValue` and `validateFontFaceBlock`
- Replace confusing `const searchFrom = 0` loop with `for (;;)` idiom
  in `extractFontFaceBlocks`
- Use index tracking (`content.substring(start, i)`) instead of
  character-by-character string concatenation in `splitDeclarations`
- Use `splitDeclarations` in `validateRootContent` instead of naive
  `split(';')` for consistency and correctness
- Parallelize font fetches in `embedThemeFonts` with `Promise.all`
  instead of sequential awaits
- Replace byte-by-byte base64 conversion with chunked
  `arrayBufferToBase64()` helper (8KB chunks)
- Reuse indexOf-based @font-face parsing in `embedThemeFonts` instead
  of fragile `[^}]*` regex that can't handle large data URIs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Enhance font validation in customThemes.ts

* Add custom release notes for upcoming feature: support for custom fonts in themes

* [AI] Simplify @font-face validation to only block external URLs

Remove ~210 lines of overly thorough font validation (MIME type allowlists,
base64 encoding checks, format hint validation, @font-face property allowlists,
font-family name regex) and replace with a single function that enforces the
actual security goal: rejecting non-data: URIs to prevent external resource
loading. Size limits for DoS prevention are preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update Content Security Policy to include font-src directive

Enhance the Content Security Policy in both the desktop client and sync server to allow font loading from data URIs. This change ensures that custom fonts can be embedded securely while maintaining the existing security measures for other resources.

* Enhance font-family validation to disallow empty values

Update the `validateFontFamilyValue` function to throw an error for empty font-family values, improving security and validation accuracy. Adjust tests to reflect this change, ensuring that empty values are properly handled as invalid.

* Enhance validation for CSS custom properties in customThemes.ts

Add comprehensive checks in the `validateRootContent` function to ensure CSS custom properties start with '--', contain valid characters, and do not end with a dash. This improves error handling for invalid property names, ensuring better compliance with CSS standards.

* [AI] Fix path traversal, spaces in font URLs, and add embedThemeFonts tests

Reject path-traversal (../) and root-anchored (/) font paths in
embedThemeFonts to prevent URL manipulation. Fix URL regex to handle
quoted filenames with spaces (e.g. "Inter Variable.woff2"). Add unit
tests covering both security validations and normal embedding flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Implement font size budget enforcement in embedThemeFonts function

* Add global unstubbing in afterEach for embedThemeFonts tests

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 07:58:17 +00:00

96 B

category, authors
category authors
Enhancements
MatissJanis

Custom Themes: allow using a custom font