Skip to content

fix(web_core): validate surface theme against catalog themeSchema - #2367

Open
Varun-S10 wants to merge 2 commits into
a2ui-project:mainfrom
Varun-S10:fix/issue-2293_
Open

fix(web_core): validate surface theme against catalog themeSchema #2367
Varun-S10 wants to merge 2 commits into
a2ui-project:mainfrom
Varun-S10:fix/issue-2293_

Conversation

@Varun-S10

Copy link
Copy Markdown
Collaborator

Description

This PR fixes a security issue where createSurface.theme values were saved without validation and used directly in CSS.

What is changed:

  • Added Theme Validation: Created BasicCatalogThemeSchema in web_core to make sure primaryColor is a valid hex color code (like #00BFFF).
  • Validated in Message Processor: MessageProcessor.createSurface now checks the theme against the catalog schema and rejects invalid or malicious values.
  • Prevented CSS Injections in Renderers:
    • In Angular, changed background: to background-color: in the Button component so URLs cannot be loaded as background images.
    • Added hex color checks before setting CSS variables in Angular and Lit elements.
  • Added Tests: Added unit tests to verify valid themes work properly and invalid/malicious values are safely blocked.

Fixes #2293

Pre-launch Checklist

One time:

For this PR:

  • I have updated the relevant CHANGELOG.md file.
  • I updated/added relevant documentation.
  • My code changes (if any) have tests.
  • If my branch is on a fork, I have verified that scripts/e2e_test.sh passes.

If you need help, consider asking for advice on the discussion board.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 332 to 347
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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
  1. 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated to use validationResult.data when creating the SurfaceModel and added a unit test for schema transformations and defaults.

Comment on lines +19 to +32
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')",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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')",
)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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})$/).

Comment on lines +71 to +75
const color = this.primaryColor();
if (typeof color === 'string' && /^#[0-9a-fA-F]{6}$/.test(color)) {
return color;
}
return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the inline regex check to support 3, 6, and 8 character hex colors to match the updated validation logic.

Suggested change
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;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated the Angular component to use the shared HEX_COLOR_REGEX check for the primary color style binding.

Comment on lines 55 to 57
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the inline regex check to support 3, 6, and 8 character hex colors to match the updated validation logic.

Suggested change
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);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated the Lit element to use the shared HEX_COLOR_REGEX to support 3, 6, and 8 character hex codes.

Comment on lines +131 to +134
/**
* Optional theme schema to override default basic catalog theme schema.
*/
themeSchema?: z.ZodObject<any>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
/**
* 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
  1. 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated themeSchema to z.ZodType for strict TypeScript type checking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: createSurface.theme values stored without validation and consumed as CSS values

1 participant