fix(web_core): validate surface theme against catalog themeSchema - #2367
fix(web_core): validate surface theme against catalog themeSchema #2367Varun-S10 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces theme validation for the basic catalog using Zod schemas, integrating it into the message processor and updating the Angular, Lit, and React renderers to support and validate themes. The review feedback suggests several key improvements: using the validated output (validationResult.data) from Zod instead of the raw input in the message processor, expanding the hexadecimal color regex to support 3-character and 8-character CSS hex codes (and updating the corresponding inline checks in Angular and Lit components), and using a stricter TypeScript type (z.ZodType<BasicCatalogTheme>) instead of z.ZodObject<any> for the theme schema.
| if (theme && catalog.themeSchema) { | ||
| const validationResult = catalog.themeSchema.safeParse(theme); | ||
| if (!validationResult.success) { | ||
| const formattedErrors = validationResult.error.errors.map(formatZodIssue).join(', '); | ||
| console.error(`[A2UI Validation Error] Surface '${surfaceId}' theme:`, { | ||
| theme, | ||
| issues: validationResult.error.issues, | ||
| }); | ||
| throw new A2uiValidationError( | ||
| `Validation failed for surface '${surfaceId}' theme: ${formattedErrors}`, | ||
| validationResult.error.issues, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| const surface = new SurfaceModel<T>(surfaceId, catalog, theme, sendDataModel ?? false); |
There was a problem hiding this comment.
When validating data using Zod's safeParse, it is important to use the parsed and validated output (validationResult.data) rather than the raw input (theme). Using the raw input completely bypasses any default values, transformations, or sanitizations defined in the schema. Additionally, ensure that Zod validation errors are formatted into a human-readable list of paths and messages instead of throwing the raw ZodError message.
let validatedTheme = theme;
if (theme && catalog.themeSchema) {
const validationResult = catalog.themeSchema.safeParse(theme);
if (!validationResult.success) {
const formattedErrors = validationResult.error.errors.map(formatZodIssue).join(', ');
console.error("[A2UI Validation Error] Surface '" + surfaceId + "' theme:", {
theme,
issues: validationResult.error.issues,
});
throw new A2uiValidationError(
"Validation failed for surface '" + surfaceId + "' theme: " + formattedErrors,
validationResult.error.issues,
);
}
validatedTheme = validationResult.data;
}
const surface = new SurfaceModel<T>(surfaceId, catalog, validatedTheme, sendDataModel ?? false);References
- Format Zod validation errors into a human-readable list of paths and messages instead of throwing the raw ZodError message, which is a raw JSON string.
There was a problem hiding this comment.
Fixed. Updated to use validationResult.data when creating the SurfaceModel and added a unit test for schema transformations and defaults.
| export const HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; | ||
|
|
||
| /** | ||
| * Zod schema defining the theme configuration for the basic catalog. | ||
| * Conforms to `specification/v0_9/catalogs/basic/catalog.json#/$defs/theme`. | ||
| */ | ||
| export const BasicCatalogThemeSchema = z | ||
| .object({ | ||
| primaryColor: z | ||
| .string() | ||
| .regex( | ||
| HEX_COLOR_REGEX, | ||
| "primaryColor must be a valid 6-character hexadecimal color code (e.g. '#00BFFF')", | ||
| ) |
There was a problem hiding this comment.
The regex strictly enforces exactly 6-character hex codes. However, valid CSS hex colors can also be 3 characters (like #17e, which is used as a fallback in button.component.ts) or 8 characters (with alpha channel). Restricting this to exactly 6 characters will reject valid CSS colors and cause validation failures for standard themes.
| export const HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; | |
| /** | |
| * Zod schema defining the theme configuration for the basic catalog. | |
| * Conforms to `specification/v0_9/catalogs/basic/catalog.json#/$defs/theme`. | |
| */ | |
| export const BasicCatalogThemeSchema = z | |
| .object({ | |
| primaryColor: z | |
| .string() | |
| .regex( | |
| HEX_COLOR_REGEX, | |
| "primaryColor must be a valid 6-character hexadecimal color code (e.g. '#00BFFF')", | |
| ) | |
| export const HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/; | |
| /** | |
| * Zod schema defining the theme configuration for the basic catalog. | |
| * Conforms to 'specification/v0_9/catalogs/basic/catalog.json#/$defs/theme'. | |
| */ | |
| export const BasicCatalogThemeSchema = z | |
| .object({ | |
| primaryColor: z | |
| .string() | |
| .regex( | |
| HEX_COLOR_REGEX, | |
| "primaryColor must be a valid hexadecimal color code (e.g. '#00BFFF' or '#17e')", | |
| ) |
There was a problem hiding this comment.
Fixed. Updated HEX_COLOR_REGEX to support 3, 6, and 8 character hex color codes (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/).
| const color = this.primaryColor(); | ||
| if (typeof color === 'string' && /^#[0-9a-fA-F]{6}$/.test(color)) { | ||
| return color; | ||
| } | ||
| return null; |
There was a problem hiding this comment.
Update the inline regex check to support 3, 6, and 8 character hex colors to match the updated validation logic.
| const color = this.primaryColor(); | |
| if (typeof color === 'string' && /^#[0-9a-fA-F]{6}$/.test(color)) { | |
| return color; | |
| } | |
| return null; | |
| const color = this.primaryColor(); | |
| if (typeof color === 'string' && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(color)) { | |
| return color; | |
| } | |
| return null; |
There was a problem hiding this comment.
Fixed. Updated the Angular component to use the shared HEX_COLOR_REGEX check for the primary color style binding.
| const primaryColor = this.context?.theme?.primaryColor; | ||
| if (primaryColor) { | ||
| if (typeof primaryColor === 'string' && /^#[0-9a-fA-F]{6}$/.test(primaryColor)) { | ||
| this.style.setProperty('--a2ui-color-primary', primaryColor); |
There was a problem hiding this comment.
Update the inline regex check to support 3, 6, and 8 character hex colors to match the updated validation logic.
| const primaryColor = this.context?.theme?.primaryColor; | |
| if (primaryColor) { | |
| if (typeof primaryColor === 'string' && /^#[0-9a-fA-F]{6}$/.test(primaryColor)) { | |
| this.style.setProperty('--a2ui-color-primary', primaryColor); | |
| const primaryColor = this.context?.theme?.primaryColor; | |
| if (typeof primaryColor === 'string' && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(primaryColor)) { | |
| this.style.setProperty('--a2ui-color-primary', primaryColor); |
There was a problem hiding this comment.
Fixed. Updated the Lit element to use the shared HEX_COLOR_REGEX to support 3, 6, and 8 character hex codes.
| /** | ||
| * Optional theme schema to override default basic catalog theme schema. | ||
| */ | ||
| themeSchema?: z.ZodObject<any>; |
There was a problem hiding this comment.
Prefer strict and tight TypeScript types over loose or lenient types (like z.ZodObject<any> or z.ZodTypeAny) to reduce cognitive burden and leverage compile-time type checking. Use z.ZodType<BasicCatalogTheme> to ensure type safety.
| /** | |
| * Optional theme schema to override default basic catalog theme schema. | |
| */ | |
| themeSchema?: z.ZodObject<any>; | |
| /** | |
| * Optional theme schema to override default basic catalog theme schema. | |
| */ | |
| themeSchema?: z.ZodType<BasicCatalogTheme>; |
References
- Prefer strict and tight TypeScript types over loose or lenient types to reduce cognitive burden and leverage compile-time type checking, even if it might introduce minor breaking changes that are easily caught and resolved during compilation.
There was a problem hiding this comment.
Fixed. Updated themeSchema to z.ZodType for strict TypeScript type checking.
4e582ef to
465f44e
Compare
Description
This PR fixes a security issue where
createSurface.themevalues were saved without validation and used directly in CSS.What is changed:
BasicCatalogThemeSchemainweb_coreto make sureprimaryColoris a valid hex color code (like#00BFFF).MessageProcessor.createSurfacenow checks the theme against the catalog schema and rejects invalid or malicious values.background:tobackground-color:in the Button component so URLs cannot be loaded as background images.Fixes #2293
Pre-launch Checklist
One time:
For this PR:
If you need help, consider asking for advice on the discussion board.