Security reference · CSS injection · accent-color · Consent manipulation
MCP server CSS accent-color consent security — native control invisibility attacks
CSS accent-color controls the fill color of native browser form controls rendered by the user agent stylesheet — checkboxes, radio buttons, range sliders, progress bars, date inputs. Setting accent-color: transparent on a consent checkbox makes the native control visually invisible while it remains in the tab order, accepts keyboard activation (Space bar), and submits its checked state with forms. No standard consent audit checks accent-color — auditors check color, background-color, and opacity, but the native checkbox rendering is controlled by a UA-stylesheet property that sits outside these checks.
accent-color attack surface overview
| Attack ID | accent-color technique | Effect on consent | Audit blind spot |
|---|---|---|---|
| SA-CSS-ACC-001 | accent-color: transparent on consent checkbox |
Native checkbox check mark and border rendered at zero opacity — invisible but clickable and submittable | color, background-color, opacity all unset or default; accent-color is not inspected by standard auditors |
| SA-CSS-ACC-002 | accent-color: var(--install-bg) matching background |
Checkbox fill matches the dialog background — no visible tick mark even when checked; appears unchecked at all times | accent-color value is correct hex or resolved var() but matches background — requires background-color correlation check |
| SA-CSS-ACC-003 | accent-color SVG data URI with mispositioned checkmark glyph |
A checkmark SVG renders outside the visible checkbox area or on the "I agree" label, implying pre-agreement | Custom SVG accent-color rendering is browser-specific; glyph position is not detectable via computed style |
| SA-CSS-ACC-004 | accent-color: transparent on <input type="range"> agreement slider |
Range slider track fill is invisible — user cannot see whether they have moved the slider to the required agreement position | Range sliders as consent controls are uncommon; their accent-color fill is not audited by standard consent scanners |
Native control gap: accent-color was added to CSS in 2021 specifically to give authors control over native browser control colors without requiring appearance:none and full custom reimplementation. The property is designed to work with the UA stylesheet's rendering of form controls — it intentionally overrides the browser's default focus-ring and checkbox colors. This means that accent-color bypasses the same detection paths that detect custom-appearance controls, while also bypassing the detection paths that check native controls for visibility issues.
Background: what accent-color controls
Prior to accent-color, authors who wanted to customize checkbox colors had to use appearance: none to strip the native rendering entirely, then rebuild the control from scratch with CSS backgrounds and pseudo-elements. This is the pattern audited by SA-CSS-AP-001. accent-color is different: it leaves the native UA-stylesheet rendering intact but overrides the accent color used for the filled/checked state of the control. The checkbox still looks and behaves like a native checkbox — with system-appropriate focus rings, ARIA states, keyboard behavior, and form submission — but its fill color is whatever the author specified via accent-color.
The CSS spec defines accent-color as inheritable. This means that setting accent-color on a form container or even the :root element will cascade to all form controls within it — including consent checkboxes the attacker does not directly target.
Attack 1: accent-color: transparent invisible checkbox (SA-CSS-ACC-001)
Setting accent-color: transparent on a consent checkbox or its container makes the native rendered checkbox completely invisible in its checked state. The unchecked state rendering varies by browser — some browsers render the checkbox border using a system outline color that may still be visible; others make the entire control invisible. In both cases, the check mark that appears when the checkbox is activated is rendered transparent and invisible to the user.
/* SA-CSS-ACC-001: transparent accent-color on consent checkbox */
/* Attack on the checkbox element directly: */
input[type="checkbox"].consent-checkbox {
accent-color: transparent;
/* The checkbox is:
- Rendered by the UA stylesheet (not display:none, not visibility:hidden)
- In the tab order
- Activatable by Space key
- Submittable with the parent form
- The check mark when checked is transparent/invisible */
}
/* Subtler: set on a container so it's not directly on the checkbox: */
.consent-section {
accent-color: transparent;
/* Inherits to all checkbox and radio children */
}
/* Detection: getComputedStyle(checkbox).accentColor
Returns "transparent" for the direct case.
For the inherited case from a container, the computed value on the
checkbox also resolves to "transparent" due to inheritance. */
SA-CSS-ACC-001 (High). SkillAudit detects this by calling getComputedStyle(el).accentColor on all <input type="checkbox"> and <input type="radio"> elements within consent containers. Any computed value of "transparent" or "rgba(0, 0, 0, 0)" is flagged. The inherited case is caught because getComputedStyle() returns the resolved computed value, including inheritance.
Attack 2: background-matching accent-color (SA-CSS-ACC-002)
Instead of transparent, the attacker sets accent-color to the same color as the dialog background. When the checkbox is checked, the tick mark appears in the background color — invisible against the background. Unlike transparent (which is a keyword and easy to detect), a background-matching hex color requires correlation between the accent-color value and the effective background color at the checkbox's rendered position.
/* SA-CSS-ACC-002: accent-color matching background color */
/* The install dialog has a dark background: */
.install-dialog {
background: #0f0f0f;
accent-color: #0f0f0f; /* Exact background match — tick is invisible when checked */
}
/* Via CSS variable — harder to detect statically: */
:root {
--dialog-bg: #1a1a2e;
}
.consent-form {
background: var(--dialog-bg);
accent-color: var(--dialog-bg); /* Same variable — background and accent match */
}
/* Detection requires:
1. Resolve accent-color to an RGB value
2. Find the effective background color at the checkbox position
3. Compare: if luminance difference < threshold, flag as matching */
Attack 3: SVG data URI glyph misdirection (SA-CSS-ACC-003)
Some browsers allow accent-color to accept a url() value referencing an SVG — this is a newer part of the specification and browser support is partial. Where supported, the SVG replaces the native check mark glyph. An attacker can provide an SVG where the check mark glyph is positioned outside the checkbox bounds (rendered outside the checkbox's own box) or overlaid on the "I agree" label text — implying that the label itself is pre-confirmed. This pattern is currently exploitable in WebKit-based browsers.
/* SA-CSS-ACC-003: SVG data URI with mispositioned glyph */
/* An SVG that renders a checkmark at a position offset from the control center —
appears over the consent label text instead of inside the checkbox */
input.consent-checkbox {
accent-color: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'
viewBox='-40 0 20 20'><polyline points='1,9 5,13 11,5'
stroke='%2334d399' stroke-width='2' fill='none'/></svg>");
/* The viewBox offset -40 positions the glyph 40 units to the left
of the checkbox — over the preceding label text */
}
/* When the checkbox is checked, the green tick appears over the label,
suggesting the label text is the confirmed/accepted item.
The checkbox itself shows no visible check mark within its border. */
Attack 4: range slider fill invisibility (SA-CSS-ACC-004)
Agreement sliders — <input type="range"> elements used to require users to drag to an "agree" position — are used in some MCP install flows as a more deliberate consent gesture. accent-color controls the filled portion of the range track (the portion from the left edge to the thumb). Setting accent-color: transparent makes the filled portion invisible, so the user cannot see whether they have moved the slider to the required minimum value.
/* SA-CSS-ACC-004: range slider track fill invisible via accent-color */
.consent-slider-container {
accent-color: transparent;
/* The range slider:
- Renders normally (native appearance)
- Has a visible track
- Has a visible thumb
- The fill from track-start to thumb position is transparent
A user who moved the slider to 60% sees the same visual as 0% —
no filled portion, no visual confirmation of their drag position.
The form submits with the slider's .value attribute (e.g., "60").
An MCP install that requires slider >= 50 to "confirm agreement"
will accept the submission, but the user received no visual
confirmation that they had made the required acknowledgment. */
}
/* Detection: check accent-color on all input[type="range"] elements
within consent containers. Transparent or background-matching
accent-color on a range consent element = SA-CSS-ACC-004. */
Detection algorithm
/* Detection for SA-CSS-ACC patterns */
function detectAccentColorConsentAttacks() {
const CONSENT_KEYWORDS = ['authorize', 'agree', 'terms', 'consent', 'permission', 'install'];
const findings = [];
document.querySelectorAll('input[type="checkbox"], input[type="radio"], input[type="range"]')
.forEach(input => {
// Find enclosing consent context
let ancestor = input.parentElement;
let isConsentContext = false;
while (ancestor) {
const text = (ancestor.textContent || '').toLowerCase();
if (CONSENT_KEYWORDS.some(k => text.includes(k))) {
isConsentContext = true;
break;
}
ancestor = ancestor.parentElement;
}
if (!isConsentContext) return;
const cs = getComputedStyle(input);
const accentColor = cs.accentColor;
if (!accentColor || accentColor === 'auto') return;
// Check for transparent
if (accentColor === 'transparent' || accentColor === 'rgba(0, 0, 0, 0)') {
findings.push({ id: 'SA-CSS-ACC-001', severity: 'HIGH', element: input,
value: accentColor, desc: 'accent-color:transparent on consent form control' });
return;
}
// Check for background-matching
const rect = input.getBoundingClientRect();
const bgElements = document.elementsFromPoint(
rect.left + rect.width / 2, rect.top + rect.height / 2
);
bgElements.forEach(bgEl => {
if (bgEl === input) return;
const bgCs = getComputedStyle(bgEl);
if (bgCs.backgroundColor !== 'rgba(0, 0, 0, 0)' &&
bgCs.backgroundColor === accentColor) {
findings.push({ id: 'SA-CSS-ACC-002', severity: 'HIGH', element: input,
value: accentColor, desc: 'accent-color matches background-color' });
}
});
// Check for data URI (SVG misdirection)
if (accentColor.startsWith('url(') || accentColor.includes('data:')) {
findings.push({ id: 'SA-CSS-ACC-003', severity: 'MEDIUM', element: input,
value: accentColor, desc: 'accent-color uses url()/data: URI — potential glyph misdirection' });
}
});
return findings;
}
SkillAudit findings for SA-CSS-ACC
accent-color: transparent (or resolved transparent) on consent checkbox or radio — native control invisible but functionally active.
accent-color matches the effective background color at the control's rendered position — tick mark invisible against background when checked.
accent-color references a url() or data URI SVG — potential glyph repositioning or misdirection attack. Requires manual review of the SVG viewBox and glyph position.
accent-color: transparent on <input type="range"> in a consent context — slider fill position invisible, user cannot confirm agreement level.
For related CSS form control attacks, see SA-CSS-AP (appearance:none) which strips native rendering entirely, and SA-CSS-CAR (caret-color) for cursor invisibility attacks on consent input fields. To audit your MCP server for all SA-CSS-ACC patterns, run a free SkillAudit scan.