Skip to content

Palette theme variables are interpolated into CSS without validation #8184

Description

@knsv-bot

Summary

Theme palette values are interpolated into generated CSS declarations without validation, and the config key that supplies them can be set from diagram text. A diagram can therefore terminate a generated declaration early and have arbitrary CSS rules emitted into its own stylesheet.

This is long-standing and repo-wide — not introduced by any recent change. Filing it so the fix can be made centrally rather than per diagram.

Where

Every genColor-style palette generator interpolates array entries straight into a declaration, e.g.

stroke: ${borderColor};
fill: ${bkgColorArray[i % bkgColorArray.length]};

Present in 8 style modules:

diagrams/flowchart/styles.ts, diagrams/class/styles.js, diagrams/er/styles.ts, diagrams/requirement/styles.js, diagrams/usecase/styles.ts, diagrams/timeline/styles.js, diagrams/git/styles.js, and diagrams/block/styles.ts.

Why it's reachable

  • themeVariables is not in the secure list (config.schema.yaml:359, which covers only secure, securityLevel, startOnLoad, maxTextSize, suppressErrorRendering, maxEdges), so a frontmatter block or an %%{init}%% directive can supply it.
  • calculate(overrides) in the theme modules assigns overrides directly, so a supplied palette array replaces the shipped one.
  • sanitize() in config.ts recurses into the array but only rejects strings containing <, >, or url(data:. The characters that end a declaration and open a new rule are untouched.

Note the neighbouring look value is already handled correctly — safeLook() in diagrams/common/colorThemeGate.ts allowlists it with /^[\w-]+$/ before it reaches a selector. The colour values simply never got the same treatment.

Impact — bounded, which is why this is low severity

compileCSS (mermaidAPI.ts) wraps the stylesheet in #svgId{…} and its addNamespace middleware re-prefixes selectors, so injected rules stay scoped to the diagram's own SVG. No script execution. The realistic consequences are a remote resource load from a rendered diagram (a tracking/exfil beacon fetched when someone views it) and visual spoofing of the diagram's own contents.

Deliberately not including a payload here.

Suggested fix

Central, next to safeLook in diagrams/common/colorThemeGate.ts, so every current and future palette consumer is covered by one change:

const SAFE_COLOR = /^(#[0-9a-f]{3,8}|[\w-]+|(rgb|rgba|hsl|hsla)\([\d\s.,%/]+\))$/i;
export const safeColor = (c: unknown, fallback = 'currentColor'): string =>
  typeof c === 'string' && SAFE_COLOR.test(c.trim()) ? c.trim() : fallback;

then stroke: ${safeColor(borderColor)} at each interpolation site. colorThemeGate.spec.ts and paletteCssGeneration.spec.ts already exist as homes for the tests.

Worth also considering whether themeVariables belongs in the secure default, which would let a deployment opt out of directive-supplied theming entirely — though that is a broader behaviour change and shouldn't block the escaping fix.

How this was found

Surfaced during automated security review of #8181, which adds the palette to block diagrams. That PR copies the established idiom faithfully and is not the cause — the same code is already on develop in the other seven modules.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: TriageNeeds to be verified, categorized, etc

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions