{"record":{"id":"442e18b09f446cd5","repo":"actualbudget/actual","slug":"invalid-font-family-value-for-property-funct","errorCode":null,"errorMessage":"Invalid font-family value for \"${property}\": function calls are not allowed. Only font names are permitted.","messagePattern":"Invalid font-family value for \"(.+?)\": function calls are not allowed\\. Only font names are permitted\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/style/customThemes.ts","lineNumber":134,"sourceCode":"      `Invalid font-family value for \"${property}\": value must not be empty.`,\n    );\n  }\n\n  // Split on commas, then validate each font name\n  const families = trimmed.split(',');\n\n  for (const raw of families) {\n    const name = stripQuotes(raw);\n\n    if (!name) {\n      throw new Error(\n        `Invalid font-family value for \"${property}\": empty font name in comma-separated list.`,\n      );\n    }\n\n    // Reject anything that looks like a function call (url(), expression(), etc.)\n    if (/\\(/.test(name)) {\n      throw new Error(\n        `Invalid font-family value for \"${property}\": function calls are not allowed. Only font names are permitted.`,\n      );\n    }\n  }\n}\n\n/** Only var(--custom-property-name) is allowed; no fallbacks. Variable name: -- then [a-zA-Z0-9_-]+ (no trailing dash). */\nconst VAR_ONLY_PATTERN = /^var\\s*\\(\\s*(--[a-zA-Z0-9_-]+)\\s*\\)$/i;\n\nfunction isValidSimpleVarValue(value: string): boolean {\n  const m = value.trim().match(VAR_ONLY_PATTERN);\n  if (!m) return false;\n  const name = m[1];\n  return name !== '--' && !name.endsWith('-');\n}\n\n/**\n * Validate that a CSS property value only contains allowed content (allowlist approach).","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/style/customThemes.ts#L116-L152","documentation":"validateFontFamilyValue rejects any font name containing an opening parenthesis, because function calls like url(), var(), or expression() in font-family could load external resources or execute expressions. Only plain font names (quoted or unquoted) and generic families are permitted.","triggerScenarios":"A --font-* declaration whose value contains a function call, e.g. `--font-ui: url(https://evil.example/font.woff2);` or `--font-mono: var(--other-font);`, anywhere in the theme's :root block.","commonSituations":"Malicious or careless theme CSS attempting to load remote fonts via url() inside font-family; theme authors misusing var() inside a font stack instead of declaring a plain list.","solutions":["Replace the function call with a plain font-name list, e.g. `--font-ui: Inter, sans-serif;`.","Remove any url(...) from font-family — remote fonts are intentionally blocked for privacy; use @font-face with data: URIs if embedding is needed.","Do not nest var() inside --font-* values; put the final font list directly in the variable."],"exampleFix":"// before\n--font-ui: url(https://cdn.example.com/fonts/inter.woff2);\n// after\n--font-ui: Inter, sans-serif;","handlingStrategy":"validation","validationCode":"function fontValueIsSafe(v) {\n  return !v.split(',').some(part => part.replace(/^[\"']|[\"']$/g, '').trim().includes('('));\n}\nif (!fontValueIsSafe(value)) throw new Error('font-family must not contain function calls');","typeGuard":"function isPlainFontList(v: string): boolean {\n  return /^[^()]+$/.test(v);\n}","tryCatchPattern":"try {\n  await installTheme(css);\n} catch (err) {\n  if ((err as Error).message.includes('function calls are not allowed')) {\n    // reject theme or ask author to supply a plain font-name list\n  } else throw err;\n}","preventionTips":["Never use url() or var() inside --font-* values.","Embed remote fonts (if at all) via data-URI @font-face, not via font-family.","Review third-party theme CSS for parentheses before installing."],"tags":["css","security","validation","font-family"],"backgroundTag":"css-injection-attempt","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}