MCP server SVG feColorMatrix hueRotate consent security
Four SA-CSS-CMH consent attack patterns using feColorMatrix type="hueRotate" — the SVG per-pixel hue rotation matrix that shifts all color hues by a fixed angle, exploited to rotate saturated consent text to a hue with lower WCAG contrast, neutralize color-coded warning signals, and evade CSSOM-based detection via the getComputedStyle().color blind spot.
The feColorMatrix SVG filter primitive with type="hueRotate" and values="θ" applies a 3×3 hue-rotation matrix to the R, G, B channels of every pixel (SVG §15.10). The matrix is defined by the SVG specification as a combination of a luminance-preserving component (the "desaturated" portion) and a hue-rotating component (the cos(θ) and sin(θ) terms). Unlike CSS filter: hue-rotate(θ) — which is a CSS property reflected in computed styles — the SVG feColorMatrix hue rotation is applied in the SVG filter effect graph, not in the CSS property model. This makes it invisible to getComputedStyle().
The SVG hue-rotate matrix (per spec §15.10) for angle θ in degrees:
| R' | | a b c | | R | | G' | = | d e f | | G | | B' | | g h i | | B | a = 0.213 + cos(θ)×0.787 − sin(θ)×0.213 b = 0.715 − cos(θ)×0.715 − sin(θ)×0.715 c = 0.072 − cos(θ)×0.072 + sin(θ)×0.928 d = 0.213 − cos(θ)×0.213 + sin(θ)×0.143 e = 0.715 + cos(θ)×0.285 + sin(θ)×0.140 f = 0.072 − cos(θ)×0.072 − sin(θ)×0.283 g = 0.213 − cos(θ)×0.213 − sin(θ)×0.787 h = 0.715 − cos(θ)×0.715 + sin(θ)×0.715 i = 0.072 + cos(θ)×0.928 + sin(θ)×0.072
For achromatic pixels (R=G=B), the matrix output is R'=G'=B'=R (the matrix preserves achromatic values). For saturated pixels, the matrix redistributes channel weights, changing the hue and potentially changing the relative luminance (Y = 0.2126R + 0.7152G + 0.0722B). Because the luminance weights are not equal, hue rotation changes luminance for saturated colors even though the matrix is designed to approximate luminance-preservation.
The CSSOM blind spot: getComputedStyle(element).color returns the logical CSS color value of the element — the value before any SVG filter transformation is applied. An auditor reading this value sees the original color and computes WCAG contrast against the original. The feColorMatrix hue rotation applies at render time in the filter graph; the computed contrast value is wrong for any saturated color that changes luminance under the rotation.
SA-CSS-CMH-001: feColorMatrix type="hueRotate" values="180" on saturated blue consent text
Blue is the most common saturated color in consent UI text — brand blues, link colors, action-item highlights. The hue-rotate matrix at θ=180° has cos(180°)=−1, sin(180°)=0. This simplifies the matrix to:
<filter id="hueRotate180"> <feColorMatrix type="hueRotate" values="180"/> </filter> <!-- θ=180°: cos=-1, sin=0 --> <!-- a=0.213+(-1)×0.787=0.213-0.787=-0.574 (negative → clamp interactions) --> <!-- approximate effect: blue (#2563eb, rgb(37,99,235)) → warm amber/orange hue --> <!-- original luminance ≈ 0.162; rotated luminance changes based on R'G'B' redistribution --> <!-- getComputedStyle().color reports #2563eb; WCAG tool computes original contrast -->
For brand blue #2563eb (R=37, G=99, B=235) under θ=180°, the matrix produces significant R and B channel swaps with G remaining. The resulting color shifts into the amber/warm spectrum. The post-rotation WCAG contrast against white depends on the exact output luminance — for many common blues, the rotated luminance rises (because blue contributes only 0.0722 to luminance, while the rotated channels have higher luminance weights), which can paradoxically make the text brighter and thus potentially lower-contrast against white. The attack surface is not guaranteed contrast failure but guaranteed CSSOM evasion: every tool relying on getComputedStyle().color misses the actual rendered color.
SA-CSS-CMH-002: feColorMatrix type="hueRotate" values="90" on red warning text
Red (#dc2626) is used in consent dialogs for warning annotations, high-risk permission notes, and irreversible-action callouts. Hue-rotating red by 90° shifts it toward lime-yellow-green. The perceptual significance of "red = warning" is destroyed: a lime-green note on a white background carries no urgency signal. Even if the technical WCAG contrast ratio passes, the semantic color coding fails.
<filter id="hueRotate90"> <feColorMatrix type="hueRotate" values="90"/> </filter> <!-- Red #dc2626 (hue ~0°) → rotated to ~90° hue (yellow-green) --> <!-- Warning callout text appears as lime-yellow; red=danger signal neutralized --> <!-- Security tool reads computedStyle red color; reports red warning present --> <!-- Rendered UI shows lime-green text; warning significance lost -->
Semantic color attack: WCAG contrast is a luminance-based metric that does not account for semantic color use. A red warning that passes contrast check in a WCAG audit may be rendered as lime-green by feColorMatrix hueRotate. Screen readers are unaffected (they read text content), but sighted users lose the "this is a warning" color cue. The attack targets color-coded risk disclosure in consent dialogs specifically — the red text that says "This action cannot be undone" or "Grants access to all files".
SA-CSS-CMH-003: feColorMatrix type="hueRotate" on entire consent container (text and background rotate together)
When the filter is applied to the consent container element rather than to individual text nodes, both the text color and the background color rotate simultaneously. For achromatic backgrounds (white #ffffff, near-white #f9fafb): achromatic pixels have R=G=B and the hue-rotate matrix preserves achromatic values (R'=G'=B'=R). The background remains white — only the text (which may be a saturated brand color) shifts hue.
<filter id="hueRotateContainer"> <feColorMatrix type="hueRotate" values="180"/> </filter> <div style="filter:url(#hueRotateContainer)"> <!-- Entire consent dialog: both text and background filtered --> <!-- Achromatic white background: hueRotate preserves achromatic colors --> <!-- Saturated text (brand blue, warning red): hue and luminance shift --> <!-- getComputedStyle().color on child text elements: unchanged --> </div>
When the background is achromatic and the text is saturated, the filter selectively changes the text color while leaving the background unchanged. This means the relative contrast between text and background can change — and in the attacker's favor, it can drop below WCAG thresholds if the saturation and hue of the text color are chosen to minimize post-rotation contrast. The attack requires knowledge of the target UI's text and background color pair; an MCP server can query these at install time via getComputedStyle() before applying the filter.
SA-CSS-CMH-004: JS mousedown sets feColorMatrix values from "0" to "180" (click-time hue rotation)
The initial state uses values="0" — a rotation of 0° is the identity transformation (R'=R, G'=G, B'=B). The consent element renders with original colors. On mousedown, the script sets values to "180", activating the hue rotation at click time. All detection tools evaluating the initial render state see the original colors.
consentElement.addEventListener("mousedown", () => {
const matrix = document.getElementById("hueFilter")
.querySelector("feColorMatrix");
matrix.setAttribute("values", "180"); // 0° identity → 180° hue rotation
// colors shift; getComputedStyle().color still reports original
// visual rendering changes at instant of click
});
The values="0" initial attribute is unambiguously a no-op — any static analysis tool correctly identifies this as identity. The attack is entirely in the dynamic attribute mutation. MutationObserver on the values attribute of feColorMatrix elements with type="hueRotate" within consent ancestor filters is the required runtime defence. The observer callback should re-evaluate the post-rotation contrast of the consent element whenever values changes from 0 to any non-zero value.
Attack summary
| ID | values | Target | Effect | Severity |
|---|---|---|---|---|
| SA-CSS-CMH-001 | 180° | Brand blue text | Hue shifts to amber; CSSOM blind spot on contrast | High |
| SA-CSS-CMH-002 | 90° | Red warning text | Red→lime; semantic color-coding destroyed | High |
| SA-CSS-CMH-003 | 180° | Entire consent container | Achromatic bg preserved; saturated text shifts | Medium |
| SA-CSS-CMH-004 | 0°→180° mousedown | Any consent text | Identity at load; hue-rotated at click | High |
Defences
- Apply hue-rotate matrix to measure post-rotation contrast: For feColorMatrix type=hueRotate in a consent ancestor filter, parse the values attribute (rotation angle), apply the SVG §15.10 matrix to the expected foreground color, compute the output RGB, calculate WCAG relative luminance of the rotated color, and compare contrast against the background. Do not use getComputedStyle().color for contrast measurement when a hueRotate filter is present.
- Detect semantic color shifts: Track the hue angle of consent text colors. If a color with hue in the red range (330°–30°) is rotated by 60°–270°, its semantic significance as a warning color is destroyed regardless of WCAG contrast. Flag any hueRotate values applied to red-hue consent text.
- Compare SVG feColorMatrix vs CSS filter hue-rotate: These are distinct mechanisms. CSS
filter: hue-rotate(θ)appears in computed styles; SVGfeColorMatrix type="hueRotate"does not. A security tool checking only CSS filter properties misses the SVG path. Both must be checked independently. - MutationObserver on feColorMatrix values attribute: Any change to the
valuesattribute on a feColorMatrix with type=hueRotate within a consent ancestor filter — particularly from "0" to a non-zero value — signals SA-CSS-CMH-004. Re-evaluate consent contrast on each change.
SkillAudit findings for this attack surface
feColorMatrix type="hueRotate" values="180" on saturated blue consent text — SVG hue-rotate matrix; blue hue (~230°) rotated to warm spectrum; output luminance changes; rendered WCAG contrast differs from CSSOM-derived value; getComputedStyle().color blind spot; detection: apply SVG §15.10 matrix to expected text RGB; recompute contrast vs background.feColorMatrix type="hueRotate" values="90" on red warning text — red (hue ~0°) → lime-yellow-green (hue ~90°); warning color significance lost; WCAG contrast check may pass numerically while semantic red=danger signal is destroyed; detect hue-range shifts on red-hue consent text: flag hueRotate on colors with hue in 330°–30° range.feColorMatrix type="hueRotate" on entire consent container — achromatic backgrounds (white, near-white) preserved by hue rotation (achromatic invariant); saturated text color changes; differential contrast shift; getComputedStyle() on child text elements still reports original; apply matrix to each distinct text color in the consent subtree.