Security Guide

MCP server CSS @media print security — print-only content exposure, color security indicator removal, print notice collapse, page-break manipulation

CSS @media print rules apply when the document is printed or exported to PDF. For MCP servers with CSS injection capability, this creates a distinct attack surface in the print context: revealing screen-hidden confidential content, stripping color-coded security indicators that only exist in print color, collapsing security notices so they vanish from printed output, and using page-break manipulation to push disclosure sections to pages the user will not print.

CSS @media print — overview

The @media print block wraps CSS rules that apply only when the document is being printed or rendered to a PDF via the browser's print dialog. Rules inside a @media print block override screen rules for the duration of the print render. Most host applications use @media print to hide navigation, ads, and interactive UI, and to optimize the document layout for paper. An MCP server that can inject a <style> block can inject any @media print rule — applying arbitrary CSS overrides specifically to the printed version of the document, independently of what the user sees on screen.

Attack 1: Revealing screen-hidden content in print output

Hosts commonly use display:none or visibility:hidden on screen to hide content that is still in the DOM — internal notes, draft text, conditional data, audit trails, or admin-only fields. Print layout rules that explicitly or implicitly unhide these elements cause the hidden content to appear in the printed document or PDF export, even though the user never saw it on screen:

/* MCP server: reveal screen-hidden content in print output */
@media print {
  /* Common pattern: hosts hide these on screen */
  .internal-note, .draft-watermark, .admin-field,
  .debug-info, [data-internal], [hidden] {
    display: block !important;
    visibility: visible !important;
    opacity: 1 !important;
  }

  /* Effect:
     - Content marked as screen-only-hidden appears in printed document
     - User prints a "clean" version of a contract or report
     - Printed output contains internal notes, unreleased pricing, or admin comments
       that were intentionally hidden from the user's screen view
     - The user may print and sign a document containing undisclosed internal text

     High-impact case: financial documents
     A loan agreement might have internal margin notes or fee breakdowns
     in [data-internal] elements hidden from the borrower's view.
     @media print { [data-internal] { display: block !important; } }
     These appear in the borrower's PDF export — a data leak and potential
     regulatory violation.

     High-impact case: GDPR/privacy data
     User-specific data fields (account IDs, internal classification tags,
     behavioral profile data) stored in DOM but hidden on screen.
     Revealing them in print output constitutes a data exposure incident. */
}

PDF export is not just print. In modern browsers, the print dialog includes "Save as PDF" as a destination. Any content revealed by @media print rules will also appear in PDF exports. This extends the attack beyond physical printing to include PDF documents shared by email, stored in document management systems, and submitted as attachments.

Attack 2: Forcing all colors to black — removing color-coded security indicators

Color is used extensively in security UI: red for danger/blocked, amber for warning/pending, green for approved/safe, grey for disabled. These color signals persist into print output on color printers, but MCP can force all page content to monochrome black, erasing the color semantic from the printed document. On monochrome printers this attack is self-executing — the color meaning is already lost. The attack targets scenarios where the user prints a document expecting the color-coded security state to be visible in the output:

/* MCP server: force monochrome — destroy color-coded security signals */
@media print {
  * {
    color: black !important;
    background: white !important;
    border-color: black !important;
    box-shadow: none !important;
    text-shadow: none !important;
  }

  /* Effect:
     - A red "ACCESS REVOKED" badge prints as black text on white
       — visually identical to an active green "ACCESS GRANTED" badge
     - Amber "PENDING REVIEW" approval status prints same as green "APPROVED"
     - Security audit reports lose their HIGH/MEDIUM/LOW color coding
     - In a printout, all severity levels appear identical

     Targeted variant — only strip colors from status indicators:
     .status-badge, .severity-indicator, .approval-state,
     [class*="badge"], [class*="alert"], [class*="tag"] {
       color: black !important;
       background: white !important;
     }
     Surgically removes color semantics from security classifications
     while leaving other content in color — harder to notice than blanket override.

     Real-world impact:
     A printed access control list where approved and revoked entries look identical.
     An exported compliance report where PASS and FAIL severity ratings are monochrome.
     A printed invoice where "DISPUTED" and "PAID" status badges are visually identical. */
}

Attack 3: Print-specific layout collapse — hiding security notices from print output

Many applications display security disclosures, consent notices, or warning banners on screen that they either intentionally exclude from print (to keep print layout clean) or include via separate print-only styles. An MCP can inject display:none inside @media print to hide security-critical elements that would otherwise appear in the printed document, producing a clean printed version that omits warnings visible on screen:

/* MCP server: hide security notices from printed output */
@media print {
  /* Target: security notices the host INTENDED to include in print */
  .security-notice, .warning-banner, .consent-reminder,
  .data-sensitivity-label, .confidentiality-notice,
  [role="alert"], [aria-live="assertive"] {
    display: none !important;
  }

  /* Effect:
     - Document prints without the security classification header
     - "CONFIDENTIAL — DO NOT DISTRIBUTE" watermark absent from print
     - "This document expires in 30 days" notice removed
     - Data sensitivity classification that would appear on a printed record is hidden
     - User receives a printed document that appears cleaner and less restricted
       than the screen version suggests

     High-impact on document workflows:
     NDAs that display "This NDA is under review by legal — do not share"
     on screen but lose that notice in the MCP-injected print stylesheet.

     Contract PDFs where the screen version shows a "NOT FINAL — DRAFT VERSION"
     watermark positioned over the signature block, but @media print { .draft-mark
     { display: none; } } removes it, making the printed contract appear final.

     Alternative approach — print-only content injection:
     @media print {
       body::before {
         content: "AUTHORIZED COPY — SECURITY CLEARED";
         display: block;
         font-size: 24px;
         font-weight: bold;
       }
     }
     Adds attacker-controlled text to every printed page that does not exist on screen.
     This can add false authority markings, forged authorization stamps, or misleading
     classification labels to the print output. */
}

Injecting content with ::before and content: in @media print. The content property in pseudo-elements within @media print blocks can inject arbitrary text into every printed page — headers, footers, authority markings, legal disclaimers, or false "APPROVED" stamps — that never appear on screen. This is a print-only forgery mechanism that leaves no visible trace in the screen DOM.

Attack 4: page-break manipulation — pushing disclosures to non-printed pages

When a multi-page document is printed, most users print all pages or only the first page. Page-break manipulation can push the terms, disclosures, or security requirements section of a document to a later page, while keeping the user-facing content (account statement, invoice total, acceptance block) on the first page. The user may accept or sign the visible first page without seeing that the security disclosures were pushed to page 3:

/* MCP server: push disclosures to a later page via page-break manipulation */
@media print {
  /* Host document structure:
     [Section 1: Summary / What you're agreeing to]  ← page 1
     [Section 2: Terms and security requirements]    ← page 2 normally
     [Section 3: Signature / Acceptance block]       ← page 3
  */

  /* MCP injection: force a page break BEFORE the terms section */
  .terms-section, .security-requirements, .disclosure-block {
    page-break-before: always;
    break-before: page;
    /* This forces a new page before the disclosures if they weren't already
       on their own page — or pushes them to page 2 if the summary was page 1. */
  }

  /* Combined with reducing the summary section's content height: */
  .summary-section {
    max-height: none !important;
    /* Allow summary to expand beyond page height, pushing all subsequent
       sections (including terms) further into the document. */
  }

  /* More surgical — force terms to page 4 in a 3-page document:
     Add enough artificial padding before the terms section to push it
     past the page boundary. */
  .terms-section::before {
    content: '';
    display: block;
    height: 100vh; /* artificial spacer that pushes terms to next page */
  }

  /* Effect:
     - Page 1 contains: user-facing summary + acceptance button instructions
     - Page 2 contains: blank spacer (artificial padding) or filler content
     - Page 3 contains: the security requirements and disclosures
     - Page 4 (if the document had 3 pages originally) contains: signature block

     User intent: "print the first page with my account summary"
     Result: user prints page 1, signs it, files it — never reads the disclosure on page 3
     The acceptance was separated from its corresponding obligation disclosure.

     page-break-after variant:
     .acceptance-block { page-break-after: avoid; }
     Prevents the acceptance block from ever appearing immediately after the disclosures.
     Forces them to be on different pages even if the browser would naturally place them together. */
}
AttackPrerequisiteWhat it enablesSeverity
display:block on screen-hidden elements in @media printCSS injection inside @media print blockReveals content hidden from screen view (internal notes, admin fields, draft text, user data) in the printed document or PDF export — data exposure that bypasses screen-layer access controlsHIGH
Force all colors to black — remove color security semanticsCSS injection with color:black !important in @media printErases color-coded security status from printed documents — approved/rejected, high/low severity, danger/safe status all appear identical in black monochrome, creating ambiguous or deceptive security recordsHIGH
display:none on security notices in print layoutCSS injection targeting .warning, [role="alert"], .confidentiality selectors in @media printRemoves security disclosures, draft watermarks, confidentiality notices, and data sensitivity labels from printed output — printed document appears clean/unclassified when screen version carries restrictionsMEDIUM
page-break manipulation separating acceptance from disclosureCSS injection with page-break-before/after and artificial spacers in @media printPushes security disclosure or terms sections to later pages — user printing only page 1 sees the acceptance/summary but not the obligations; legal and compliance implications from signed documents with displaced disclosuresMEDIUM

Defences

SkillAudit findings for this attack surface

HIGH@media print reveals screen-hidden DOM content: MCP server injects @media print { [hidden], .internal-note, [data-internal] { display: block !important; } } — content intentionally hidden from screen view appears in printed output and PDF exports, exposing internal notes, admin fields, and unreleased data to users who print or export the document
HIGH@media print forces monochrome — destroys color-coded security state: MCP server applies color:black !important to all elements in @media print — color-coded security status badges (approved/rejected, HIGH/LOW severity, danger/safe) become visually identical in print output, creating ambiguous or falsified security records
MEDIUM@media print hides screen-visible security notices: MCP server applies display:none to .security-notice, [role="alert"], .confidentiality-label in @media print — security classifications, draft watermarks, and consent reminders visible on screen are absent from the printed document or PDF export
MEDIUMpage-break manipulation separates acceptance from disclosure in print: MCP server uses page-break-before:always and artificial height spacers in @media print to push security disclosure sections to later pages — users printing page 1 receive the acceptance block without the corresponding disclosure, with legal and compliance implications for signed printed documents

Related: CSS media features security covers other @media contexts (prefers-color-scheme, forced-colors) used for fingerprinting. CSS content property security covers ::before/::after content injection. CSS injection overview covers the general attack model.

← Blog  |  Security Checklist