[AI] Allow var(--name) in custom theme CSS (no fallbacks) (#7018)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Matiss Janis Aboltins
2026-02-18 22:16:25 +00:00
committed by GitHub
parent 5179ac7c2d
commit 77b848ca84
4 changed files with 73 additions and 25 deletions

View File

@@ -457,30 +457,18 @@ describe('validateThemeCss', () => {
});
describe('invalid CSS - function calls (security)', () => {
describe('var() function - should reject all var() references', () => {
describe('var() function - should accept var(--name) only (no fallbacks)', () => {
it.each([
{
description: 'simple var() call',
css: `:root {
--color-primary: var(--other-var);
}`,
},
{
description: 'var() with fallback',
css: `:root {
--color-primary: var(--other-var, #007bff);
}`,
},
{
description: 'var() with whitespace',
css: `:root {
--color-primary: var( --other-var );
}`,
},
{
description: 'nested var() calls',
css: `:root {
--color-primary: var(--var1, var(--var2));
}`,
},
{
@@ -495,14 +483,43 @@ describe('validateThemeCss', () => {
--color-primary: VaR(--other-var);
}`,
},
])('should reject CSS with $description', ({ css }) => {
expect(() => validateThemeCss(css)).toThrow(
/Only simple CSS values are allowed/,
);
])('should accept CSS with $description', ({ css }) => {
expect(() => validateThemeCss(css)).not.toThrow();
});
});
describe('other function calls - should reject all except rgb/rgba/hsl/hsla', () => {
describe('var() function - should reject var() with fallback or invalid form', () => {
it.each([
{
description: 'var() with fallback',
css: `:root {
--color-primary: var(--other-var, #007bff);
}`,
},
{
description: 'var() with invalid variable name (no --)',
css: `:root {
--color-primary: var(not-a-custom-prop);
}`,
},
{
description: 'var() with empty variable name',
css: `:root {
--color-primary: var(--);
}`,
},
{
description: 'var() with unbalanced parentheses',
css: `:root {
--color-primary: var(--other-var;
}`,
},
])('should reject CSS with $description', ({ css }) => {
expect(() => validateThemeCss(css)).toThrow();
});
});
describe('other function calls - should reject all except rgb/rgba/hsl/hsla and var()', () => {
it.each([
{
description: 'calc() function',

View File

@@ -73,10 +73,19 @@ export async function fetchDirectCss(url: string): Promise<string> {
return response.text();
}
/** Only var(--custom-property-name) is allowed; no fallbacks. Variable name: -- then [a-zA-Z0-9_-]+ (no trailing dash). */
const VAR_ONLY_PATTERN = /^var\s*\(\s*(--[a-zA-Z0-9_-]+)\s*\)$/i;
function isValidSimpleVarValue(value: string): boolean {
const m = value.trim().match(VAR_ONLY_PATTERN);
if (!m) return false;
const name = m[1];
return name !== '--' && !name.endsWith('-');
}
/**
* Validate that a CSS property value only contains allowed content (allowlist approach).
* Only simple, safe CSS values are allowed - no functions (except rgb/rgba/hsl/hsla), no URLs, no complex constructs.
* Explicitly rejects var() and other function calls to prevent variable references and complex expressions.
* Allows: colors (hex, rgb/rgba, hsl/hsla), lengths, numbers, keywords, and var(--name) only (no fallbacks).
*/
function validatePropertyValue(value: string, property: string): void {
if (!value || value.length === 0) {
@@ -85,12 +94,15 @@ function validatePropertyValue(value: string, property: string): void {
const trimmedValue = value.trim();
if (isValidSimpleVarValue(trimmedValue)) {
return;
}
// Allowlist: Only allow specific safe CSS value patterns
// 1. Hex colors: #RGB, #RRGGBB, or #RRGGBBAA (3, 6, or 8 hex digits)
const hexColorPattern = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?([0-9a-fA-F]{2})?$/;
// 2. RGB/RGBA functions: rgb(...) or rgba(...) with simple numeric/percentage values
// Allow optional whitespace and support both integers and decimals
const rgbRgbaPattern =
/^rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(,\s*[\d.]+)?\s*\)$/;
@@ -123,7 +135,7 @@ function validatePropertyValue(value: string, property: string): void {
// If none of the allowlist patterns match, reject the value
throw new Error(
`Invalid value "${trimmedValue}" for property "${property}". Only simple CSS values are allowed (colors, lengths, numbers, or keywords). Functions (including var()), URLs, and other complex constructs are not permitted.`,
`Invalid value "${trimmedValue}" for property "${property}". Only simple CSS values are allowed (colors, lengths, numbers, keywords, or var(--name)). Other functions, URLs, and complex constructs are not permitted.`,
);
}