actualbudget/actual · error
Invalid font src: only data: URIs are allowed in @font-face.
Error message
Invalid font src: only data: URIs are allowed in @font-face. Remote URLs (http/https) are not permitted to protect user privacy. Font files are automatically embedded when installing from GitHub.
What it means
validateFontFaceBlocks scans @font-face blocks in theme CSS and requires every src url() to be a data: URI. Remote http/https font URLs are blocked to protect user privacy (they would leak requests to third parties); themes installed from GitHub are expected to have fonts embedded as base64 data URIs automatically.
Source
Thrown at packages/desktop-client/src/style/customThemes.ts:272
}
/**
* Validate @font-face blocks: only data: URIs allowed (no remote URLs).
* Enforces size limits to prevent DoS.
*/
function validateFontFaceBlocks(fontFaceBlocks: string[]): void {
let totalSize = 0;
// Match url() with quoted or unquoted content. Quoted URLs use a non-greedy
// match up to the closing quote; unquoted URLs match non-whitespace/non-paren.
const urlRegex = /url\(\s*(?:'([^']*)'|"([^"]*)"|([^'")\s]+))\s*\)/g;
for (const block of fontFaceBlocks) {
urlRegex.lastIndex = 0;
let match;
while ((match = urlRegex.exec(block)) !== null) {
const uri = (match[1] ?? match[2] ?? match[3]).trim();
if (!uri.startsWith('data:')) {
throw new Error(
'Invalid font src: only data: URIs are allowed in @font-face. ' +
'Remote URLs (http/https) are not permitted to protect user privacy. ' +
'Font files are automatically embedded when installing from GitHub.',
);
}
// Estimate decoded size from base64 content
const base64Match = uri.match(/;base64,(.+)$/);
if (base64Match) {
const size = Math.ceil((base64Match[1].length * 3) / 4);
if (size > MAX_FONT_FILE_SIZE) {
throw new Error(
`Font file exceeds maximum size of ${MAX_FONT_FILE_SIZE / 1024 / 1024}MB.`,
);
}
totalSize += size;
}
}
}View on GitHub (pinned to d4334cb6e6)
Solutions
- Embed the font as a base64 data URI: `src: url(data:font/woff2;base64,<...>) format('woff2');`.
- Re-install the theme through the GitHub flow so fonts are automatically embedded at install time.
- Drop the @font-face block and rely on locally installed fonts named in --font-* variables.
- Use a build step (e.g. a script that inlines woff2 files as data URIs) before publishing actual.css.
Example fix
// before
@font-face { font-family: 'Inter'; src: url('https://cdn.example.com/inter.woff2'); }
// after
@font-face { font-family: 'Inter'; src: url('data:font/woff2;base64,d09GMgAB...') format('woff2'); } Defensive patterns
Strategy: validation
Validate before calling
function fontSrcsAreDataUris(css) {
const blocks = css.match(/@font-face\s*{[^}]*}/g) ?? [];
return blocks.every(b => (b.match(/url\((['"]?)([^)"']+)(\1)\)/g) ?? [])
.every(u => u.includes('url(data:')));
}
if (!fontSrcsAreDataUrs(css)) throw new Error('remote font URL found'); Type guard
function isDataUri(uri: string): boolean {
return uri.startsWith('data:');
} Try / catch
try {
await installTheme(css);
} catch (err) {
if ((err as Error).message.includes('only data: URIs are allowed')) {
// instruct author to embed fonts as base64 or reinstall via the GitHub flow
} else throw err;
} Prevention
- Always inline fonts as base64 data URIs in theme CSS.
- Use the GitHub install flow, which embeds fonts automatically.
- Grep published actual.css for 'url(http' before release.
When it happens
Trigger: A theme's actual.css contains `@font-face { src: url(https://fonts.example.com/x.woff2); }` or a relative/protocol-relative URL — anything not starting with 'data:' inside @font-face.
Common situations: Theme author linked Google Fonts or a CDN font directly instead of embedding it; a font-embedding script failed to inline the font file; hand-written @font-face pointing at a local file path.
Related errors
- Invalid font-family value for "${property}": function calls
- Font file exceeds maximum size of ${MAX_FONT_FILE_SIZE / 102
- Total embedded font data exceeds maximum of ${MAX_TOTAL_FONT
- Theme CSS contains forbidden at-rules (@import, @media, @key
- Invalid font-family value for "${property}": value must not
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/d0d477cae94c6eb8.
Report an issue: GitHub.