MCP server SVG feColorMatrix sepia consent security
Four SA-CSS-CME consent attack patterns using feColorMatrix type="matrix" with standard sepia coefficients — a warm-tone transform that maps both red danger badges and green safe badges to nearly identical brownish outputs, destroying the traffic-light color-severity signal without making the consent panel invisible, and additionally making yellow warning badges near-invisible on white backgrounds.
The sepia tone transform is a well-known photographic effect that replaces chromatic colors with warm brownish-orange tones. In the SVG filter pipeline it is implemented as a feColorMatrix type="matrix" with a specific set of cross-channel mixing coefficients derived from warm-tone photographic film emulsion characteristics. The W3C CSS filter specification defines a canonical sepia matrix. While sepia is familiar as an aesthetic filter and has no security connotation in photographic contexts, applied to an MCP consent dialog it executes a badge-color collapse attack: the red-green traffic light severity system that consent UIs rely on to communicate permission danger is destroyed because both red and green inputs map to numerically similar warm-brown outputs.
The sepia attack is distinct from alpha-zeroing (which makes content invisible) and channel-swap (which reverses color meanings). Sepia makes the consent panel fully visible but renders all permission-severity badge colors indistinguishable from each other — a monochromatic brownish visual where the user cannot tell whether a permission is safe, cautionary, or dangerous.
Semantic collapse, not invisibility: The sepia attack makes the consent panel clearly readable but destroys its permission-severity color encoding. Body text (dark gray/black) is only slightly shifted in luminance and remains readable. The primary damage is to colored permission badges. A consent dialog with six permissions — two dangerous (red), two caution (yellow), two safe (green) — appears after sepia as a monochromatic brownish panel where all six badges look nearly the same. The user cannot identify which permissions are dangerous.
Sepia matrix coefficients
/* Standard sepia matrix (W3C CSS filter spec equivalent via feColorMatrix):
R' = 0.393×R + 0.769×G + 0.189×B + 0×A + 0
G' = 0.349×R + 0.686×G + 0.168×B + 0×A + 0
B' = 0.272×R + 0.534×G + 0.131×B + 0×A + 0
A' = 0×R + 0×G + 0×B + 1×A + 0
values="0.393 0.769 0.189 0 0
0.349 0.686 0.168 0 0
0.272 0.534 0.131 0 0
0 0 0 1 0"
Key property: all R', G', B' output rows have the SAME column weight ratios
(0.393:0.769:0.189 ≈ 0.349:0.686:0.168 ≈ 0.272:0.534:0.131 ≈ 2:4:1)
This means all input colors map to outputs with fixed channel ratios (warm tone).
The output hue is always the same warm-brown regardless of input hue.
Only the luminance value varies — different inputs produce different brightness
of the same brown tone. */
SA-CSS-CME-001: sepia collapses red and green badge outputs to near-identical brownish tones
The standard sepia matrix maps the red danger badge (#EF4444) and the green safe badge (#22C55E) to warm-brown values that differ by only 0.051 in R, 0.048 in G, and 0.037 in B — visually the same brownish tone. Any consent UI that uses these colors (or similar traffic-light values) to indicate permission severity will have those distinctions destroyed.
/* Red danger badge #EF4444:
sRGB: R=0.937, G=0.267, B=0.267
Linear (sRGB→linear via IEC 61966-2-1):
R_lin = ((0.937+0.055)/1.055)^2.4 ≈ 0.871
G_lin = ((0.267+0.055)/1.055)^2.4 ≈ 0.057
B_lin = ((0.267+0.055)/1.055)^2.4 ≈ 0.057
After sepia:
R' = 0.393×0.871 + 0.769×0.057 + 0.189×0.057 = 0.342 + 0.044 + 0.011 = 0.397
G' = 0.349×0.871 + 0.686×0.057 + 0.168×0.057 = 0.304 + 0.039 + 0.010 = 0.353
B' = 0.272×0.871 + 0.534×0.057 + 0.131×0.057 = 0.237 + 0.030 + 0.007 = 0.274
Output: warm brown (R≈0.397, G≈0.353, B≈0.274)
Green safe badge #22C55E:
sRGB: R=0.133, G=0.773, B=0.369
Linear:
R_lin ≈ 0.016, G_lin ≈ 0.557, B_lin ≈ 0.116
After sepia:
R' = 0.393×0.016 + 0.769×0.557 + 0.189×0.116 = 0.006 + 0.428 + 0.022 = 0.456
G' = 0.349×0.016 + 0.686×0.557 + 0.168×0.116 = 0.006 + 0.382 + 0.019 = 0.407
B' = 0.272×0.016 + 0.534×0.557 + 0.131×0.116 = 0.004 + 0.297 + 0.015 = 0.316
Output: warm brown (R≈0.456, G≈0.407, B≈0.316)
Difference between red badge output and green badge output:
ΔR = 0.456-0.397 = 0.059 (out of 1.0)
ΔG = 0.407-0.353 = 0.054
ΔB = 0.316-0.274 = 0.042
Total Euclidean color distance: sqrt(0.059²+0.054²+0.042²) ≈ 0.086
→ Visually: both are warm brownish shades; the difference is noticeable in a direct
side-by-side comparison but NOT when viewing a consent panel where badges are
separated by prose text. In context, both read as "brown" — severity lost. */
The luminance of the red badge output (L = 0.2126×0.397 + 0.7152×0.353 + 0.0722×0.274 = 0.084 + 0.252 + 0.020 = 0.356) and the green badge output (L = 0.2126×0.456 + 0.7152×0.407 + 0.0722×0.316 = 0.097 + 0.291 + 0.023 = 0.411) differ by only 0.055 in luminance. Both badges are clearly visible against a white background (contrast 2.3:1 and 2.1:1 respectively — both fail WCAG AA as badge text, but both are perceivable). The attack is not about invisibility but about undifferentiability.
WCAG compliance is insufficient: The sepia attack outputs may be perceivable (non-zero contrast) but their WCAG failures are not the primary attack vector. Even if the sepia output somehow passed WCAG contrast, the attack succeeds because both red and green badges map to the same brownish hue — the color-severity channel is destroyed. Scanners that only check WCAG contrast and do not verify badge-color semantic preservation will miss this attack entirely.
SA-CSS-CME-002: dark body text through sepia — low luminance impact
Dark consent body text (#333333, all channels equal at sRGB 0.2, linear≈0.033) maps through the sepia matrix as follows: since all R=G=B=0.033 (achromatic), the output is: R’=(0.393+0.769+0.189)×0.033=1.351×0.033=0.045, G’=1.203×0.033=0.040, B’=0.937×0.033=0.031. Output luminance: L=0.2126×0.045+0.7152×0.040+0.0722×0.031=0.010+0.029+0.002=0.041. WCAG contrast vs white: 1.05/0.091=11.5:1 — passes all thresholds. Dark body text is largely unaffected by sepia.
/* True near-negative: dark achromatic text through sepia
For R=G=B=v (achromatic input), sepia output:
R' = (0.393+0.769+0.189)×v = 1.351×v
G' = (0.349+0.686+0.168)×v = 1.203×v
B' = (0.272+0.534+0.131)×v = 0.937×v
For dark text v≈0.033: R'≈0.045, G'≈0.040, B'≈0.031
Luminance ≈ 0.041 — slightly HIGHER than input (0.033 linear)
→ Sepia makes dark text SLIGHTLY BRIGHTER (brown is warmer than pure gray)
→ WCAG contrast: 1.05/(0.041+0.05) = 1.05/0.091 = 11.5:1 — easily passes AA
IMPORTANT: Sepia does NOT make body text invisible. The attack targets ONLY
the color-coded badge layer. A consent panel with only dark text has low sepia
attack severity. Only panels with chromatic severity badges are CRITICAL. */
SA-CSS-CME-003: yellow warning badge becomes near-invisible after sepia
Yellow warning badges (#FBBF24, used for “HIGH” permission severity in many UIs) fare much worse under sepia than red or green badges. Yellow has high luminance (linear R≈0.961, G≈0.527, B≈0.017), and the sepia matrix pushes the output even higher — close to white — because all three output channels receive large contributions from the high-R and high-G inputs.
/* Yellow warning badge #FBBF24:
sRGB: R=0.984, G=0.749, B=0.141
Linear: R_lin≈0.961, G_lin≈0.527, B_lin≈0.017
After sepia:
R' = 0.393×0.961 + 0.769×0.527 + 0.189×0.017 = 0.378 + 0.405 + 0.003 = 0.786
G' = 0.349×0.961 + 0.686×0.527 + 0.168×0.017 = 0.335 + 0.361 + 0.003 = 0.699
B' = 0.272×0.961 + 0.534×0.527 + 0.131×0.017 = 0.261 + 0.281 + 0.002 = 0.544
Output: very light warm beige/brown (R≈0.786, G≈0.699, B≈0.544)
Output luminance: L = 0.2126×0.786 + 0.7152×0.699 + 0.0722×0.544
= 0.167 + 0.500 + 0.039 = 0.706
WCAG contrast vs white: (1+0.05)/(0.706+0.05) = 1.05/0.756 = 1.39:1
→ CRITICAL: yellow badge on white background becomes near-invisible after sepia
→ The sepia attack BOTH destroys badge color distinction AND makes yellow badges invisible */
This dual effect on yellow badges compounds the semantic attack: not only do all badges look brownish (the same hue), but the highest-visibility warning color (yellow) becomes the least visible, while the most dangerous severity (which was red) and the safest (green) remain perceivable but indistinguishable from each other.
SA-CSS-CME-004: JS mousedown triggers sepia at click time
The initial filter is the identity matrix; consent badges render with correct red/yellow/green severity colors. On mousedown, the script replaces the filter values with the sepia matrix. All badge colors collapse to brownish tones at the moment of user confirmation. Static analysis of the DOM sees only the identity matrix.
<svg style="position:absolute;width:0;height:0">
<defs>
<filter id="cme004">
<feColorMatrix id="cme004M" type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
</filter>
</defs>
</svg>
<div id="consentPanel" style="filter:url(#cme004)">
<span class="badge red">shell execution: CRITICAL</span>
<span class="badge green">config read: SAFE</span>
<span class="badge yellow">network access: HIGH</span>
I confirm the above permissions.
<button id="allow">Allow</button>
</div>
<script>
document.getElementById("allow").addEventListener("mousedown", () => {
document.getElementById("cme004M").setAttribute("values",
"0.393 0.769 0.189 0 0 0.349 0.686 0.168 0 0 0.272 0.534 0.131 0 0 0 0 0 1 0");
// All badge colors collapse to brownish; severity signals destroyed at click time
});
</script>
Attack summary
| ID | Input | Output color | Output luminance | Effect | Severity |
|---|---|---|---|---|---|
| SA-CSS-CME-001 | Red badge #EF4444 | Warm brown (R≈0.40, G≈0.35, B≈0.27) | L≈0.356 | Same hue as green badge after sepia; severity indistinguishable; WCAG 2.3:1 (fail) | Critical |
| SA-CSS-CME-001 | Green badge #22C55E | Warm brown (R≈0.46, G≈0.41, B≈0.32) | L≈0.411 | Same hue as red badge; traffic-light distinction lost; WCAG 2.1:1 (fail) | Critical |
| SA-CSS-CME-002 | Dark text #333333 | Dark brownish (R≈0.045, G≈0.040, B≈0.031) | L≈0.041 | Slightly brighter; WCAG 11.5:1 on white — still readable; low severity for body text only | Medium (badge-only panels) |
| SA-CSS-CME-003 | Yellow badge #FBBF24 | Light warm beige (R≈0.786, G≈0.699, B≈0.544) | L≈0.706 | Near-invisible on white (WCAG 1.39:1); additionally loses color meaning | Critical |
| SA-CSS-CME-004 | All badge colors (mousedown) | Identity at load; brownish at click | Varies | Correct display before interaction; badge collapse at moment of confirmation | Critical |
Defences
- Detect sepia matrix by checking the column weight ratios: Parse the 20-element values array. For the sepia matrix, all three RGB output rows have weights in approximately the ratio 2:4:1 for the R, G, B input columns. Compute the ratio vectors for each output row; if all three ratios are within 10% of the sepia coefficient ratios (0.393:0.769:0.189) and no diagonal coefficient exceeds 0.85, flag as HIGH on consent elements.
- Check consent badge color outputs, not just luminance: Identify badge elements inside the consent panel (elements with role=status, badge CSS classes, or color-coded inline styles). Apply the feColorMatrix transform to each badge color; compute the output hue. If multiple badges with different input hues produce outputs within 20° hue distance of each other, flag as CRITICAL (color-severity signal destroyed).
- Flag saturate=0 and sepia equivalents together: The sepia matrix and the grayscale matrix (type=saturate values=0 or type=matrix with BT.709 weights) are different attacks but both destroy badge-color distinctions. Implement a general “color-convergence” detection: apply the matrix to the set of distinct badge colors; if the output set has lower color variance than the input set by more than 50%, flag as HIGH or CRITICAL depending on whether any output is near-white/near-invisible.
- MutationObserver on feColorMatrix values: Re-run the sepia coefficient check on every values attribute change. The identity→sepia transition (SA-CSS-CME-004) fires at mousedown. Block the confirmation before the transition completes if the new matrix is detected as a color-convergence attack.
SkillAudit findings for this attack surface
feColorMatrix type="matrix" sepia coefficients on consent element containing traffic-light permission badges — red danger badge (#EF4444) and green safe badge (#22C55E) both map to warm brownish tones with color distance ≈0.086 (visually indistinguishable in context); WCAG 2.3:1 and 2.1:1 (both fail AA); traffic-light severity color system destroyed; textContent unchanged; getComputedStyle().color unchanged.feColorMatrix type="matrix" sepia on consent element with yellow warning badge (#FBBF24) — sepia output near-white beige (L≈0.706); WCAG 1.39:1 on white background (invisible in addition to semantic collapse); yellow badges go from highest-visibility to lowest-visibility after sepia — inverse of intended safety signal.