MCP server CSS column-rule-width security: zero-width invisibility, thick-rule consent text obscuring, and column separator overlap attacks
Published 2026-09-25 — SkillAudit Research
The CSS column-rule-width property sets the width (thickness) of the rule drawn between columns in a multi-column container. It follows the same value syntax as border-width, accepting keyword values (thin, medium, thick) and explicit length values (0px, 1px, 100px). A rule is only drawn if both column-rule-style is not none and column-rule-width is greater than zero.
This creates two distinct attack surfaces for MCP server consent manipulation. The first is the zero-width attack: set column-rule-width: 0 to remove the separator signal while leaving column-rule-style: solid apparently set (fooling scanners that check style but not width). The second is the thick-rule attack: set a very large width to create a rule that extends into adjacent column content areas, partially obscuring the consent text that lines sit adjacent to the separator boundary.
The style-without-width false-negative: A static audit checking only column-rule-style may report "separator style: solid — visible rule present." It would not check column-rule-width and would miss that the width is set to 0px by a subsequent longhand override. The shorthand may declare column-rule: 1px solid #ccc, then a separate column-rule-width: 0 overrides the width. The computed rendered separator is invisible. See also: CSS column-rule-style security and CSS column-rule-color security.
Attack findings
Setting
column-rule-width: 0 (or column-rule-width: 0px) makes the separator rule invisible regardless of the column-rule-style or column-rule-color values. A host stylesheet that sets column-rule: 1px solid #ccc can be subverted by a subsequent column-rule-width: 0 injection. The multi-column layout remains fully active with all columns present in the DOM; only the visual separator that would alert users to the multi-column structure is removed.
/* Host framework: visible separator */
.consent-dialog { column-count: 2; column-rule: 1px solid #e0e0e0; }
/* MCP injection: removes separator via width longhand override */
.consent-dialog { column-rule-width: 0; }
/* column-rule-style: solid still set (from shorthand)
column-rule-color: #e0e0e0 still set (from shorthand)
But zero width = no visible rule painted.
User sees no separator. User does not know column 2 exists.
Consent clause in column 2: invisible. */
Column rules are drawn in the
column-gap area between columns. The CSS specification does not require that a column rule stay within the gap — a rule wider than the gap will extend into the adjacent column content areas. A column-rule-width: 100px on a container with column-gap: 20px paints a 100px-wide rule whose edges extend 40px into each adjacent column. For a narrow consent column (120px wide), 40px of intrusion covers the first 40 pixels of every line — the authorization framing text at the start of each line is obscured by the rule.
.consent-modal {
column-count: 2;
column-gap: 20px;
/* Rule is 100px wide. Gap is 20px.
Rule extends 40px into column 1 and 40px into column 2.
On a 120px column, 40px of text near the separator is obscured. */
column-rule: 100px solid rgba(255,255,255,0.95); /* near-white obscures text */
}
The rule's color does not need to be fully opaque to cause damage — a near-opaque white rule on a white-text dark background, or near-opaque dark on light, still obscures the text beneath it. The consent text passes DOM-presence checks; its computed color is readable; but in the rendered view the rule physically covers it.
When
column-rule-color matches the consent text color and column-rule-width is thick enough to intrude into the column content area, the rule creates a color-matched band that visually blends with the text. Users perceive the consent text as continuing normally but certain words — those that fall within the rule's extension band — are visually merged with the rule's painted area. The words remain in the DOM, but their contrast is eliminated at the intersection.
The keyword value
thick resolves to 5px in most browsers (same as border-width: thick). On a narrow column-gap: 4px, a 5px rule extends 0.5px into each adjacent column. This is not sufficient to obscure significant text on standard screens, but at high pixel density (3x or 4x DPR) the fractional pixel extension may cause sub-pixel rendering artifacts that reduce contrast at the column boundary. This is a low-severity consistency finding, not a consent-hiding attack in isolation, but may compound with other column-manipulation techniques.
Detection
function checkColumnRuleWidth(container) {
const cs = getComputedStyle(container);
const cc = parseInt(cs.columnCount, 10);
if (cc <= 1) return null;
if (cs.columnRuleStyle === 'none' || cs.columnRuleStyle === 'hidden') return null;
const ruleWidth = parseFloat(cs.columnRuleWidth);
const gapWidth = parseFloat(cs.columnGap) || 0;
const containerW = container.getBoundingClientRect().width;
const colW = containerW / cc;
// Zero-width override
if (ruleWidth === 0) {
return {
severity: 'high',
issue: `column-rule-width:0 on ${cc}-column container — separator invisible despite style:${cs.columnRuleStyle}`
};
}
// Thick rule intruding into column content
const intrusion = Math.max(0, (ruleWidth - gapWidth) / 2);
if (intrusion > 10) {
const pct = Math.round((intrusion / colW) * 100);
return {
severity: intrusion > 30 ? 'high' : 'medium',
issue: `column-rule-width:${ruleWidth}px extends ${intrusion}px into ${Math.round(colW)}px column (${pct}% obscured near separator)`
};
}
return null;
}
Remediation
| Control | How it helps |
|---|---|
| Check computed column-rule-width (not shorthand) | Longhand-after-shorthand overrides only visible in computed style; raw CSS parsing misses cascade order |
| Flag column-rule-width:0 with non-none rule-style | The style-without-width pattern indicates deliberate separator-signal suppression |
| Compute rule intrusion into column content area | (ruleWidth − gapWidth) / 2 > 10px indicates text-obscuring potential |
| Restrict column-count to 1 in sandboxed consent UIs | Eliminates all three column-rule longhands as attack surface |
SkillAudit checks all three column-rule longhands — style, color, and width — at computed-style resolution. Run a free audit on any MCP server GitHub URL.