Security reference · CSS injection · Grid attacks · Implicit column collapse
MCP server CSS grid-auto-columns security
CSS grid-auto-columns sets the track sizing function for implicit grid columns — columns generated automatically when a grid item is placed outside the explicit column template. Setting grid-auto-columns: 0 collapses every implicit column to zero width. MCP servers exploit this to place consent disclosures in an implicit column where they render at zero width, clipped by overflow: hidden, without using display: none or any traditional visibility technique. The column-axis counterpart to grid-auto-rows row-collapse attacks.
grid-auto-columns attack surface
| Attack configuration | grid-auto-columns value | Supporting properties | Effect on consent |
|---|---|---|---|
| Zero implicit column width | 0 / 0px | overflow: hidden on grid container; consent placed in column beyond explicit template | Consent in implicit column has computed width 0px; content overflows the zero-width track; overflow:hidden clips it to invisible |
| minmax(0,auto) constrained | minmax(0, auto) | Grid container with fixed width equal to explicit column sum; consent in implicit column | Implicit column starts at container right edge; auto-sized at zero remaining space; consent rendered at zero or 1px width |
| 1px hairline column | 1px | overflow: hidden; consent font-size exceeds 1px column width | Consent column is 1px wide — narrower than any readable text; content overflows the 1px track and is clipped; column looks like a divider line |
| JS-toggled zero width | Initially auto → toggled to 0 after delay | classList.add() or CSS variable change triggered after 2s or on button interaction | Consent visible at load time (passes audit); collapses to zero width after MCP JS runs post-security-check window |
grid-auto-columns vs. grid-template-columns: grid-template-columns defines the explicit column tracks. grid-auto-columns only governs implicit tracks — those created when a grid item is placed beyond the explicit column count (e.g., via grid-column: 5 in a 3-column template, or when grid-auto-flow: column places items in new columns). An MCP server sets a 1-column or 2-column explicit template for the install form, places consent at grid-column: 4 or beyond, and the implicit column for consent is controlled by grid-auto-columns.
Attack 1: grid-auto-columns: 0 — zero-width implicit column collapse
When grid-auto-columns: 0 is set and consent is placed beyond the explicit column template, the auto-generated column for consent has zero width. With overflow: hidden on the grid container, the consent element's content overflows the zero-width track and is fully clipped. The consent element still exists in the DOM with display: block computed style, but its rendered bounding rect has width 0:
/* Malicious CSS — SA-CSS-GACOL-001 */
.mcp-install-grid {
display: grid;
grid-template-columns: 1fr; /* explicit: one column for install form */
grid-auto-columns: 0; /* implicit columns: zero width */
grid-auto-flow: column; /* new items go to new columns, not new rows */
overflow: hidden;
width: 400px;
}
.mcp-install-form {
/* Placed in column 1 (explicit, 1fr = 400px) */
}
.mcp-consent-disclosure {
/* grid-auto-flow:column places this in column 2 (implicit, 0px wide) */
/* Alternative: explicit placement with grid-column: 3 or beyond */
grid-column: 3; /* places consent in column 3 — implicit, 0px wide */
/* computed width: 0px; content clipped by overflow:hidden */
}
/* Detection */
function detectGridAutoColumnsZero() {
const findings = [];
for (const el of document.querySelectorAll('*')) {
const s = getComputedStyle(el);
if (s.display !== 'grid' && s.display !== 'inline-grid') continue;
if (s.gridAutoColumns !== '0px') continue;
const children = [...el.children];
const consentKids = children.filter(c =>
/consent|disclosure|terms|privacy/i.test(c.textContent || '')
);
for (const ck of consentKids) {
const ckRect = ck.getBoundingClientRect();
if (ckRect.width < 2) {
findings.push({ id: 'SA-CSS-GACOL-001', severity: 'critical',
message: `Grid container has grid-auto-columns:0 — consent in implicit column has width:${Math.round(ckRect.width)}px. Content clipped by overflow:hidden.` });
}
}
}
return findings;
}
Attack 2: grid-auto-columns: minmax(0, auto) with fixed container
minmax(0, auto) sets the implicit column minimum to 0 and maximum to auto (content width). However, when the explicit columns already consume the full container width, the remaining space available for implicit columns is zero — so auto resolves to 0. This is a subtler attack because minmax(0,auto) looks like a safe default sizing function, but the outcome is a zero-width implicit column when the container is already full:
/* Malicious CSS — SA-CSS-GACOL-002 */
.mcp-install-grid {
display: grid;
grid-template-columns: 400px; /* explicit: exactly fills container width */
grid-auto-columns: minmax(0, auto); /* looks benign; auto = 0 when no space left */
grid-auto-flow: column;
overflow: hidden;
width: 400px; /* container exactly matches explicit column total */
}
.mcp-install-form {
/* column 1: 400px — fills entire container */
}
.mcp-consent-disclosure {
/* column 2 (implicit): minmax(0,auto) with 0 remaining space = 0px wide */
/* consent text overflows and is clipped */
}
/* Detection */
function detectGridAutoColumnsMinmax() {
const findings = [];
for (const el of document.querySelectorAll('*')) {
const s = getComputedStyle(el);
if (s.display !== 'grid' && s.display !== 'inline-grid') continue;
const children = [...el.children];
const consentKids = children.filter(c =>
/consent|disclosure|terms|privacy/i.test(c.textContent || '')
);
for (const ck of consentKids) {
const ckRect = ck.getBoundingClientRect();
if (ckRect.width < 4 && ckRect.height > 0) {
const elRect = el.getBoundingClientRect();
if (ckRect.left >= elRect.right - 4) {
findings.push({ id: 'SA-CSS-GACOL-002', severity: 'high',
message: `Grid consent element has computed width:${Math.round(ckRect.width)}px, positioned at container right edge. Possible grid-auto-columns:minmax(0,auto) collapse with full explicit template.` });
}
}
}
}
return findings;
}
Attack 3: grid-auto-columns: 1px — hairline implicit column
Setting grid-auto-columns: 1px creates implicit columns that are exactly 1 pixel wide — narrower than any readable text. The consent element is placed in this 1px implicit column; its content overflows the track and is clipped by overflow: hidden. The 1px column itself may render as what appears to be a visual divider or border line, making the attack look like a layout separator rather than a hidden element:
/* Malicious CSS — SA-CSS-GACOL-003 */
.mcp-install-grid {
display: grid;
grid-template-columns: 1fr; /* install form: full width */
grid-auto-columns: 1px; /* implicit columns: 1px — narrower than text */
grid-auto-flow: column;
overflow: hidden;
}
.mcp-consent-disclosure {
/* Placed in 1px implicit column via grid-auto-flow:column or explicit grid-column */
grid-column: 2; /* 1px wide implicit column */
/* text overflows rightward from 1px column, clipped by overflow:hidden */
/* the 1px column's visible left edge looks like a thin divider */
}
/* Safe guard note: 1px != 0px so simple "=== 0" checks miss this attack.
Detection must check for sub-readable-text width, not just zero. */
/* Detection */
function detectGridAutoColumnsHairline() {
const findings = [];
for (const el of document.querySelectorAll('*')) {
const s = getComputedStyle(el);
if (s.display !== 'grid' && s.display !== 'inline-grid') continue;
if (s.overflow !== 'hidden' && s.overflow !== 'clip') continue;
const children = [...el.children];
const consentKids = children.filter(c =>
/consent|disclosure|terms|privacy/i.test(c.textContent || '')
);
for (const ck of consentKids) {
const ckRect = ck.getBoundingClientRect();
if (ckRect.width > 0 && ckRect.width < 10) {
findings.push({ id: 'SA-CSS-GACOL-003', severity: 'high',
message: `Consent element in grid container has width:${Math.round(ckRect.width)}px — sub-readable hairline column. grid-auto-columns:${s.gridAutoColumns}. Content clipped by overflow:${s.overflow}.` });
}
}
}
return findings;
}
Attack 4: JS-toggled grid-auto-columns — deferred collapse after audit window
The most evasion-resistant variant. The grid container initially has grid-auto-columns: auto so consent is fully visible at page load — passing any audit tool that checks styles at DOMContentLoaded or load time. After a 2-second delay (or on a user interaction event like mouseover on the install button), MCP JavaScript adds a class that switches grid-auto-columns to 0. Consent collapses to zero width at the exact moment the user is about to interact with the install dialog:
/* Malicious CSS — SA-CSS-GACOL-004 */
.mcp-install-grid {
display: grid;
grid-template-columns: 1fr;
grid-auto-columns: auto; /* default: visible at audit time */
grid-auto-flow: column;
overflow: hidden;
}
.mcp-install-grid.mcp-loaded {
grid-auto-columns: 0; /* toggled to zero after MCP JS runs */
}
/* MCP JavaScript */
setTimeout(() => {
document.querySelector('.mcp-install-grid').classList.add('mcp-loaded');
}, 2000); /* 2-second delay clears post-load single-pass auditors */
/* Interaction variant: collapses on hover of install button */
document.querySelector('.mcp-install-btn').addEventListener('mouseover', () => {
document.querySelector('.mcp-install-grid').classList.add('mcp-loaded');
}, { once: true });
/* Detection — MutationObserver monitors style class changes */
function detectDeferredGridAutoColumnsCollapse() {
const findings = [];
const consentSelectors = '[class*="consent"],[class*="disclosure"],[class*="terms"]';
const observer = new MutationObserver((mutations) => {
for (const mut of mutations) {
if (mut.type !== 'attributes') continue;
const el = mut.target;
if (el.nodeType !== 1) continue;
const s = getComputedStyle(el);
if (s.display !== 'grid' && s.display !== 'inline-grid') continue;
if (s.gridAutoColumns === '0px') {
const consentKids = [...el.querySelectorAll(consentSelectors)];
if (consentKids.length > 0) {
findings.push({ id: 'SA-CSS-GACOL-004', severity: 'critical',
message: `Grid container class/attribute changed and grid-auto-columns is now 0px. Deferred consent column collapse detected after page load.` });
}
}
}
});
document.querySelectorAll('*').forEach(el => {
const s = getComputedStyle(el);
if (s.display === 'grid' || s.display === 'inline-grid') {
observer.observe(el, { attributes: true, attributeFilter: ['class', 'style'] });
}
});
return findings; /* populated asynchronously after mutation fires */
}
Column vs row axis asymmetry in auditing: Most consent visibility audits check getBoundingClientRect().height for zero-height hiding and top / bottom for vertical displacement. Zero-width column collapse attacks hide consent along the horizontal axis — width === 0 and left === right. Auditors that only check vertical dimensions miss all four grid-auto-columns attack patterns. SkillAudit checks both axes independently.
SkillAudit findings for CSS grid-auto-columns consent attacks
grid-auto-columns: 0 or 0px on a grid container with overflow: hidden; consent element placed in an implicit column beyond the explicit column template has computed width < 2px. Content is present in DOM but clipped to invisible by zero implicit column width.grid-auto-columns: minmax(0, auto) on a grid container whose explicit columns consume the full container width; consent in an implicit column has computed width < 4px at the container right edge. Auto-sizing resolves to zero when no space remains for implicit tracks.grid-auto-columns: 1px (or any sub-readable-text value) on a grid container with overflow: hidden; consent in an implicit column has computed width between 1px and 10px. The 1px track renders as a hairline that may appear to be a separator or border.grid-auto-columns transitions to 0 via class addition or style attribute change 1–3 seconds after page load or on install button hover; consent in implicit column collapses from readable width to zero after the post-load security check window has passed. Detected by MutationObserver monitoring attribute changes on grid containers near consent elements.Related MCP consent attack research
- CSS grid-auto-rows attacks — row-axis implicit track collapse
- CSS grid-column attacks — explicit placement displacement
- CSS justify-content attacks — main-axis displacement
- CSS justify-self attacks — per-item column-axis self-alignment
- CSS Layout Displacement Attacks: Grid, Flex, and Table synthesis
Audit your MCP server for grid-auto-columns consent collapse attacks: paste your GitHub URL at skillaudit.dev for a free security report including SA-CSS-GACOL findings.