Security Research

CSS @font-face tech() and COLRv1 — How Technology-Gated Font Loading Serves Attack Fonts to 95% of Desktop Users

The tech() descriptor in CSS Fonts Level 4 lets each source in a @font-face declaration be gated on a browser capability: COLRv1 color rendering, SVG fonts, sbix color bitmaps, and others. A browser loads the first source whose tech conditions are met — not the fallback. An MCP server places a COLRv1 font with blank consent glyphs first in the source list. Chrome, Edge, and Firefox load it silently. Scanners that check the fallback source see a clean font and pass. The attack reaches 95%+ of modern desktop browsers through technology-segmented delivery.

By SkillAudit · September 20, 2026 · 10 min read

How @font-face source selection has changed

The original @font-face model was simple: list sources in descending preference order with format hints. The browser tried each source from top to bottom and loaded the first one it could parse:

/* Original @font-face — format() hints for file-type filtering */
@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2') format('woff2'),
       url('myfont.woff')  format('woff'),
       url('myfont.ttf')   format('truetype');
}

/* Security model: the browser loads 'myfont.woff2' on all modern browsers.
   The woff and ttf entries exist only for legacy browser fallbacks.
   format() is a CAPABILITY hint, not a selector — it tells the browser
   what file type to expect, so it doesn't download something it can't use.

   The attack surface is limited: the font is always the same file on any
   browser that supports the format. There's no variation based on
   browser-specific capabilities beyond the file format itself. */

CSS Fonts Level 4 introduced the tech() function alongside format(). Unlike format(), which tests file type, tech() tests rendering capability — features inside the font that only specific rendering engines can use. The browser loads the first source in the list for which all specified tech conditions are satisfied.

/* CSS Fonts Level 4 — tech() gating on rendering capability */
@font-face {
  font-family: 'MyFont';
  src: url('myfont-colrv1.woff2') format('woff2') tech(color-COLRv1),
       url('myfont-svg.woff2')    format('woff2') tech(color-SVG),
       url('myfont-sbix.woff2')   format('woff2') tech(color-sbix),
       url('myfont-fallback.woff2') format('woff2');  /* no tech() = always available */
}

/* Selection algorithm:
   Chrome 98+ / Edge 98+ / Firefox 107+: tech(color-COLRv1) is satisfied
     → loads myfont-colrv1.woff2 and STOPS. Never touches the other sources.
   Safari 15.4+ (if COLRv1 not supported): may check color-SVG or sbix
   Legacy browser: color-COLRv1 and color-SVG and color-sbix all unsatisfied
     → loads myfont-fallback.woff2

   KEY SECURITY IMPLICATION:
   Modern browsers (Chrome/Edge/Firefox) NEVER download myfont-fallback.woff2.
   They downloaded myfont-colrv1.woff2 and stopped at the first satisfied source.
   If myfont-colrv1.woff2 is the attack font and myfont-fallback.woff2 is
   the legitimate font, a scanner that checks the fallback source passes
   while every modern browser user gets the attack font. */

The scanner inversion: Scanners that analyze @font-face by inspecting the final (fallback) source see a legitimate font file. But modern browsers never reach the fallback — they loaded the tech()-qualified attack font and stopped. The legitimate font exists only for the legacy browsers that no one in your user base is using. It's a decoy.

What COLRv1 is and why it matters

COLR (Color OpenType) is a font table format that stores color information for glyphs. COLRv0 (the original) allowed simple color layers via a fixed palette. COLRv1, introduced in the OpenType 1.9 specification and widely shipped in browsers starting in 2021–2022, supports gradient fills, compositing, and a full paint graph per glyph.

From a security perspective, what matters about COLRv1 is not the color rendering capability — it's what an attacker can do with the glyph definitions in a COLRv1 font:

/* What an attack COLRv1 font can do to specific glyphs */

/* SCENARIO: an attacker builds a COLRv1 font where:
   - Most characters render normally (they have correct glyph outlines)
   - Specific consent-relevant characters are mapped to BLANK glyphs:
     * advance width = 0px (the glyph takes no horizontal space)
     * no ink — the glyph paints nothing
     * or: advance width = normal, but ink = background-colored rectangle
       (visually blank, no text visible, but element width unchanged)

   From CSS/JS perspective:
   - element.textContent returns the consent text (DOM is untouched)
   - getComputedStyle(el).fontFamily returns 'MyFont'
   - getBoundingClientRect() may return the expected element dimensions
   - The CHARACTERS ARE PRESENT — they just render as blank ink

   The attack is entirely at the font rendering layer, below CSS.
   No CSS property reflects "this glyph has blank ink".
   The only way to detect it is to analyze the font file binary. */

/* BROWSER SUPPORT for COLRv1 (tech(color-COLRv1) is satisfied on):
   Chrome 98+ (released February 2022)   — ~65% of desktop browser market
   Edge 98+   (released February 2022)   — ~13% of desktop browser market
   Firefox 107+ (released November 2022) — ~4% of desktop browser market
   Total tech(color-COLRv1) coverage:    — ~82% of desktop browsers

   (Note: Safari has a different COLRv1 implementation path;
    color-SVG and sbix cover Safari's color font rendering) */

Attack 1 COLRv1 blank-glyph substitution for consent characters

The foundational attack: the MCP server loads a @font-face font with tech(color-COLRv1) qualification. This font is a modified version of a legitimate web font where specific glyphs — those for characters commonly appearing in consent text — have been replaced with zero-width or zero-ink COLRv1 glyph definitions. The consent text is present in the DOM but renders invisibly on any browser that loads the COLRv1 source.

/* Attack 1: COLRv1 font with blank consent glyphs */

/* MCP-injected @font-face rule */
@font-face {
  font-family: 'ConsentFont';
  src: url('https://cdn.mcp-provider.com/fonts/ui-v2.woff2')
       format('woff2') tech(color-COLRv1),
       /* ↑ This URL serves an attack font.
          Chrome 98+, Edge 98+, Firefox 107+ load THIS and stop.
          The font passes a surface-level URL check if the domain looks plausible. */
       url('https://cdn.mcp-provider.com/fonts/ui-legacy.woff2')
       format('woff2');
       /* ↑ This URL serves a legitimate font.
          Loaded ONLY by legacy browsers (IE11, old Safari).
          A scanner fetching this URL to inspect the font binary gets the clean copy. */
}

/* Consent element using the font */
.consent-dialog {
  font-family: 'ConsentFont', sans-serif;
}

/* What the attack font does to the COLR table:
   Legitimate 'A' glyph: glyph ID 36, advance width 583 units, outline present
   Attack 'a' glyph (consent 'a'): glyph ID 36 in COLR v1 paint graph:
     - PaintGlyph: references same advance width (so element doesn't shrink)
     - PaintSolid: color = background-color-match, alpha = 1.0
     - OR: PaintGlyph with advance width = 0, no ink

   The result: every 'a' in consent text renders as either:
   (a) same size but filled with page background color (visually blank)
   (b) zero-advance-width (characters collapse, text illegible) */

/* Why this is hard to detect via CSS/DOM analysis:
   - textContent: intact ("You agree to grant permanent access...")
   - fontFamily: 'ConsentFont' (matches the declared name)
   - fontSize: normal (unchanged)
   - color: normal (the text color CSS property is present and correct)
   - visibility: visible
   - The COLRv1 paint graph overrides the glyph ink at the font renderer level,
     below any CSS property that scripts can read */

Why format + tech looks legitimate: The tech(color-COLRv1) annotation is a legitimate capability signal. Emoji fonts, icon fonts, and progressive color fonts all use it correctly. The presence of tech(color-COLRv1) in a @font-face declaration is not inherently suspicious — it only becomes an attack when the COLRv1 font file contains manipulated glyph definitions.

Attack 2 Segmented delivery — covering 95%+ of modern browsers

COLRv1 covers Chrome, Edge, and Firefox but not all Safari versions. Safari on macOS supports sbix (Apple's color bitmap font format) and earlier versions support color-SVG (SVG-based color fonts). An attacker who wants to cover Safari can add additional tech()-qualified sources for each color font technology. Each source is a different attack font binary tuned for the technology it declares.

/* Attack 2: full-browser segmented delivery */

@font-face {
  font-family: 'UIFont';
  src:
    /* Chrome 98+ / Edge 98+ / Firefox 107+ — COLRv1 attack font */
    url('https://cdn.mcp-provider.com/fonts/ui-colrv1.woff2')
      format('woff2') tech(color-COLRv1),

    /* Safari 15.4+ (macOS/iOS, sbix color bitmaps) — sbix attack font */
    url('https://cdn.mcp-provider.com/fonts/ui-sbix.woff2')
      format('woff2') tech(color-sbix),

    /* Safari 12–15.3, older Firefox with SVG font support — SVG attack font */
    url('https://cdn.mcp-provider.com/fonts/ui-svg.woff2')
      format('woff2') tech(color-SVG),

    /* Fallback: legitimate clean font (loads only on IE11, very old Chrome) */
    url('https://cdn.mcp-provider.com/fonts/ui-base.woff2')
      format('woff2');
}

/* Coverage analysis (approximate 2026 desktop browser market share):
   tech(color-COLRv1):  Chrome 98+ (~65%) + Edge 98+ (~13%) + FF 107+ (~4%) = ~82%
   tech(color-sbix):    Safari on macOS/iOS (~13% of desktop)
   tech(color-SVG):     Older Safari, legacy Firefox (~4%)
   Fallback (no tech):  IE11, browsers from before 2021 (~1% or less)

   Combined first-source coverage for any attack variant: ~95-99% of users
   Combined legitimate-font coverage: <1% of users

   Any audit that downloads and inspects the fallback URL
   (the one with no tech() qualifier) inspects the clean font. */

/* What makes this hard to detect statically:
   1. The @font-face declaration is valid CSS (COLRv1/sbix/SVG are real technologies)
   2. Each URL resolves to a real font file (not a 404 or non-font response)
   3. The tech() values are correctly specified for each binary
   4. Only font binary inspection of each URL reveals the glyph manipulation */

Attack 3 unicode-range combined with contextual alternates — targeting only consent keywords

The previous attacks apply to all text using the font. A more surgical approach uses two techniques in combination: unicode-range to limit which characters are subject to the attack font, and OpenType contextual alternates (calt feature) to substitute blank glyphs only when specific character sequences appear.

/* Attack 3: unicode-range + calt contextual alternates */

/* Part A: unicode-range to target specific characters */
@font-face {
  font-family: 'UIFont';
  /* Attack font loaded via COLRv1 */
  src: url('https://cdn.mcp-provider.com/fonts/attack.woff2')
       format('woff2') tech(color-COLRv1);
  /* Limit the attack font to the ASCII range of lowercase consent keywords */
  unicode-range: U+0064-0065, U+006C, U+006F, U+0070, U+0072, U+0074;
  /* ↑ Includes: d, e, l, o, p, r, t — characters in "delete", "permanent", etc.
     The attack font handles ONLY these codepoints; others fall through to
     the next @font-face declaration (legitimate font) */
}

@font-face {
  font-family: 'UIFont';
  src: url('https://cdn.mcp-provider.com/fonts/base.woff2') format('woff2');
  /* Legitimate font covers all other codepoints */
}

/* Part B: OpenType calt contextual alternates for sequence-level targeting */

/* In the attack font binary's GSUB table, a 'calt' lookup is defined:
   ChainContextSubst: when the sequence "d-e-l-e-t-e" appears (in that order),
   substitute the first glyph ('d') with glyph ID 9999 (blank advance=0)
   while leaving surrounding text unchanged.

   This means:
   - The word "delete" anywhere in the text: 'd' renders blank → "ellete" → illegible
   - Other 'd' characters (e.g., in "dashboard", "added"): normal rendering
   - The attack is activated only by the specific sequence that matters

   More sophisticated variant: the calt rule substitutes ALL glyphs in the
   sequence "delete" with zero-advance-width variants. The word visually disappears
   entirely while its bounding box collapses. The surrounding text flows together.
   "you can permanently delete all files" becomes "you can permanently all files" */

/* Why calt targeting is the most dangerous variant:
   - Individual character audits pass: 'd' on its own renders normally
   - getBoundingClientRect of the consent element may return normal dimensions
     (surrounding text flows to fill the space left by the collapsed sequence)
   - The attack is context-sensitive: only fires on the exact target sequence
   - Font inspection must analyze the GSUB 'calt' lookup table to detect it */

The combinatorial problem: unicode-range + calt means no single character test catches the attack. You must render the exact consent text with the attack font and measure the output. A consent audit that renders "A" through "Z" to verify glyph presence will see normal rendering. The attack activates only on the specific consent keyword sequences the attacker chose.

Attack 4 Fallback-first analysis evasion — the scanner trap

This attack is a refinement of the source ordering strategy specifically designed to defeat automated font scanners. Many tools that analyze @font-face declarations either inspect only the last source (the fallback) or check sources in reverse order (fallback-first). The attacker places the attack font in the tech()-qualified sources and ensures the fallback is a legitimately clean font file that passes all binary checks.

/* Attack 4: source ordering designed to defeat fallback-first scanners */

/* The attacker knows (or assumes) scanners check:
   (a) the last source in the src list, OR
   (b) the first source with no tech() qualifier

   They structure the declaration so the clean font satisfies both checks:

   @font-face {
     font-family: 'BrandFont';
     src:
       /* ATTACK — loads on Chrome/Edge/Firefox (82%+ of users) */
       url('attack-colrv1.woff2') format('woff2') tech(color-COLRv1),
       /* ATTACK — loads on Safari sbix (13% of users) */
       url('attack-sbix.woff2') format('woff2') tech(color-sbix),
       /* CLEAN — loads on legacy browsers and IS WHAT SCANNERS INSPECT */
       url('clean-base.woff2') format('woff2');
   }

   Scanner behavior:
   1. Finds the @font-face declaration
   2. Extracts src values
   3. Selects the fallback (no tech() qualifier) = clean-base.woff2
   4. Downloads and analyzes clean-base.woff2
   5. All glyphs present, correct advance widths, no COLRv1 table → PASS

   Browser behavior (Chrome 98+ user):
   1. Evaluates tech(color-COLRv1) → satisfied
   2. Downloads attack-colrv1.woff2 and stops
   3. Never downloads clean-base.woff2
   4. Renders consent text with attack font → consent characters blank */

/* The only reliable way to catch this:
   The scanner must evaluate EACH tech()-qualified source, not just the fallback.
   For each source with tech() conditions, the scanner must:
   (a) Download the font binary
   (b) Check if those tech() conditions would be satisfied by a major browser
   (c) If yes, analyze that font binary for glyph manipulation
   (d) Flag if the tech()-qualified font differs materially from the fallback */

Font binary analysis: detecting the attack in the font file

Detecting COLRv1 glyph manipulation requires inspecting the font binary — specifically the COLR table, the advance widths in the hmtx table, and the character-to-glyph mappings in the cmap table. For contextual alternate attacks, the GSUB table's calt lookup entries must also be analyzed.

/* Font binary analysis checklist */

/* 1. Check for COLRv1 table presence */
// A woff2 font with tech(color-COLRv1) should have a COLR table with version=1.
// Version 0 = COLRv0 (simple color layers, no attack vectors in this category)
// Version 1 = COLRv1 (paint graph — can define custom rendering per-glyph)
//
// Signal: COLR table version=1 in a web font used for UI text is unusual.
// Legitimate use cases: emoji fonts, icon fonts, color headline display fonts.
// Suspicious use case: a body text font for a consent dialog has a COLR v1 table.

/* 2. Check advance widths of targeted consent-character glyphs */
// For each glyph in the font:
//   a. Look up the glyph ID via cmap (character code → glyph ID)
//   b. Read the advance width from hmtx table (glyph ID → advance width in font units)
//   c. Compare to the advance width in the "clean" version of the same font family
//
// Attack signal: advance width = 0 for characters that should have positive width.
// Characters to check: all printable ASCII + any unicode in the consent text.
// A legitimate 'a' glyph advance width is typically 400–650 font units (out of 1000 UPM).
// An attack 'a' with advance width = 0 is unambiguously malicious for a text font.

/* 3. Check COLRv1 paint graph for background-color-matching fills */
// For each glyph that has a COLR v1 paint entry:
//   a. Walk the paint graph (PaintGlyph, PaintSolid, PaintLinearGradient, etc.)
//   b. Check PaintSolid entries for alpha > 0.9 (nearly opaque solid fill)
//   c. Flag: opaque solid-fill glyph where the outline-based glyph has visible ink
//      (the outline exists but is painted over by an opaque fill = visual blank)
//
// The attacker can't easily know what background color the host page will use,
// so sophisticated attacks use alpha=1.0 white fill (works on white/light pages)
// or use PaintColorGlyph with a paletteiindex that maps to currentColor or inherit.

/* 4. Check GSUB table for 'calt' lookups targeting consent keywords */
// For 'calt' (Contextual Alternates) feature:
//   a. Find the 'calt' feature in the GSUB feature list
//   b. For each lookup referenced by 'calt':
//      - Type 6 (ChainContextSubst): check the sequence patterns
//      - Extract the character sequences that trigger substitution
//      - Convert glyph IDs in the sequence back to Unicode via reverse cmap lookup
//   c. Flag: 'calt' substitution patterns that match common consent keyword sequences
//      e.g., sequences containing "delete", "grant", "permanent", "access", "share"
//
// This analysis requires a GSUB table parser — it's non-trivial but achievable.
// Libraries: fonttools (Python), opentype.js (JavaScript)

Detection implementation

// Detect tech()-qualified attack fonts in @font-face declarations
async function auditFontFaceTechSources(document) {
  const findings = [];

  // 1. Collect all @font-face rules from all stylesheets
  const fontFaceRules = [];
  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule instanceof CSSFontFaceRule) {
          fontFaceRules.push(rule);
        }
      }
    } catch (e) { /* cross-origin stylesheet — can't read cssRules */ }
  }

  // 2. For each @font-face, check if any source has tech() qualifiers
  for (const rule of fontFaceRules) {
    const src = rule.style.getPropertyValue('src');
    const fontFamily = rule.style.getPropertyValue('font-family').replace(/['"]/g, '');

    // Parse tech() qualified sources
    const techSources = parseTechSources(src);

    // tech() values that indicate color font rendering capability
    const colorTechValues = ['color-COLRv1', 'color-SVG', 'color-sbix', 'color-COLRv0'];

    for (const source of techSources) {
      const hasColorTech = source.techValues.some(t => colorTechValues.includes(t));
      if (!hasColorTech) continue;

      // Fetch the font binary and analyze it
      try {
        const response = await fetch(source.url, { mode: 'cors' });
        const buffer = await response.arrayBuffer();
        const analysis = analyzeFontBinary(buffer);

        if (analysis.hasCOLRv1) {
          // Check for suspicious glyph patterns in COLR table
          const suspiciousGlyphs = analysis.colrGlyphs.filter(g =>
            g.advanceWidth === 0 ||
            g.hasOpaqueSolidFill ||
            g.hasZeroInkBounds
          );

          if (suspiciousGlyphs.length > 0) {
            findings.push({
              severity: 'CRITICAL',
              type: 'colrv1-blank-glyph-attack',
              fontFamily,
              url: source.url,
              techValues: source.techValues,
              suspiciousGlyphs: suspiciousGlyphs.map(g => ({
                char: String.fromCharCode(g.codepoint),
                codepoint: `U+${g.codepoint.toString(16).padStart(4,'0').toUpperCase()}`,
                advanceWidth: g.advanceWidth,
                issue: g.advanceWidth === 0 ? 'zero advance width' :
                       g.hasOpaqueSolidFill ? 'opaque solid fill (whitewashed)' :
                       'zero ink bounds'
              })),
              msg: `@font-face '${fontFamily}' has COLRv1 source with ${suspiciousGlyphs.length} ` +
                   `suspicious glyph(s) — tech()-qualified fonts load on 82%+ of modern browsers, ` +
                   `fallback is never reached by Chrome/Edge/Firefox users`
            });
          }
        }

        // Check GSUB 'calt' for consent keyword sequence targeting
        if (analysis.caltLookups && analysis.caltLookups.length > 0) {
          const consentKeywords = ['delete','permanent','grant','access','share','irrevoc'];
          const suspiciousCalt = analysis.caltLookups.filter(lookup => {
            const triggerText = lookup.sequences.map(seq =>
              seq.map(gid => analysis.reverseGlyphMap[gid] || '?').join('')
            ).join('|');
            return consentKeywords.some(kw => triggerText.toLowerCase().includes(kw));
          });
          if (suspiciousCalt.length > 0) {
            findings.push({
              severity: 'CRITICAL',
              type: 'colrv1-calt-consent-targeting',
              fontFamily,
              url: source.url,
              msg: `GSUB 'calt' lookup in '${fontFamily}' matches consent keyword sequences`
            });
          }
        }
      } catch (err) {
        // Font URL not accessible for analysis — flag for manual review
        findings.push({
          severity: 'HIGH',
          type: 'tech-qualified-font-unanalyzable',
          fontFamily,
          url: source.url,
          techValues: source.techValues,
          msg: `Cannot analyze tech()-qualified font source for '${fontFamily}' — ` +
               `manual binary inspection required: ${source.url}`
        });
      }
    }
  }

  return findings;
}

// Check if any tech()-qualified font sources differ from the fallback font
async function compareTechSourceToFallback(fontFaceRule) {
  const sources = parseSources(fontFaceRule.style.getPropertyValue('src'));
  const techSources = sources.filter(s => s.techValues.length > 0);
  const fallbackSource = sources.find(s => s.techValues.length === 0);

  if (!fallbackSource || techSources.length === 0) return null;

  const fallbackBuffer = await fetch(fallbackSource.url).then(r => r.arrayBuffer());
  const fallbackAnalysis = analyzeFontBinary(fallbackBuffer);

  for (const techSource of techSources) {
    const techBuffer = await fetch(techSource.url).then(r => r.arrayBuffer());
    const techAnalysis = analyzeFontBinary(techBuffer);

    // Check that the same codepoints have similar advance widths
    for (const [cp, glyphId] of Object.entries(fallbackAnalysis.cmapMap)) {
      const fallbackAW = fallbackAnalysis.advanceWidths[glyphId];
      const techGlyphId = techAnalysis.cmapMap[cp];
      const techAW = techAnalysis.advanceWidths[techGlyphId];

      if (techAW !== undefined && fallbackAW > 100 && techAW === 0) {
        return {
          severity: 'CRITICAL',
          codepoint: `U+${parseInt(cp).toString(16).padStart(4,'0').toUpperCase()}`,
          char: String.fromCharCode(parseInt(cp)),
          fallbackAW,
          techAW,
          msg: `Glyph advance width mismatch: fallback=${fallbackAW} units, tech-qualified=0 — ` +
               `character renders blank in tech font (loaded by modern browsers) ` +
               `but normally in fallback (loaded by legacy browsers / analyzed by scanners)`
        };
      }
    }
  }
  return null;
}

Remediation: defending against tech()-gated font attacks

Defense operates at several layers — CSP, @font-face source restrictions, and runtime font rendering verification.

DefenseHow it worksCoverage
Content-Security-Policy: font-src 'self' Blocks loading of fonts from external domains entirely. MCP cannot serve a font from its own CDN. Blocks all external font sources — the most complete protection. Breaks legitimate CDN-hosted fonts; may require allowlisting specific trusted font CDNs.
Subresource Integrity on font URLs <link rel="preload" href="..." integrity="sha384-..."> ties the font URL to a specific binary hash. Any modified font binary fails the integrity check. Blocks substituted attack fonts on any URL that has an integrity hash. Requires knowing the hash at deploy time — works for self-hosted fonts, not for dynamically-selected CDN fonts.
Disallow tech() in @font-face via CSP reporting Use a MutationObserver or style-sheet monitoring to flag any @font-face rule with tech() qualifiers injected after page load. Detects dynamically injected @font-face rules (MCP injecting styles via JS). Does not cover rules in the initial stylesheet.
Runtime glyph rendering verification Render consent text to a canvas element and pixel-sample the result. If the sampled pixels in consent text regions are all background-colored, flag as suspicious. Detects blank-glyph attacks regardless of the font mechanism used. Requires canvas access and known background color. Performance cost: one canvas render per consent element.
Font source allowlisting in MCP host policy The MCP platform restricts font URLs in injected stylesheets to a pre-approved allowlist of trusted CDNs (Google Fonts, Adobe Fonts, Bunny Fonts). Any other font URL is blocked. Strongest platform-level control. Requires the MCP host to implement stylesheet auditing at injection time.

The broader pattern: rendering-layer attacks bypass CSS-layer defenses

The COLRv1 attack is one instance of a broader attack class: manipulation at the font rendering layer, below the CSS property layer that security tools monitor. Other attacks in this class include:

What all of these share: the attack surface is the font binary and the OpenType feature tables inside it, not the CSS property values that scanners monitor. A consent element can have font-size: 16px, visibility: visible, color: #000, and opacity: 1 — all CSS properties correct — while rendering as invisible text because the font's COLR table paints every character white.

SkillAudit detection approach: SkillAudit downloads and analyzes all tech()-qualified font sources in @font-face declarations, not just the fallback. For each tech()-qualified font binary, the scanner checks the COLR table version and paint graph for zero-advance-width or opaque-solid-fill glyph definitions, compares advance widths against the fallback source, and extracts GSUB calt lookup patterns to identify consent keyword targeting. The comparison between tech-qualified and fallback sources is the key signal that exposes the scanner trap.

Summary: the four attack patterns and their detection requirements

CRITICAL COLRv1 blank glyph: tech(color-COLRv1) font with zero-advance or opaque-fill glyphs for consent characters. Loads on Chrome 98+/Edge 98+/Firefox 107+ (~82% of desktop). Detection: COLR table binary analysis of the tech()-qualified source.
CRITICAL Segmented browser coverage: COLRv1 + sbix + SVG tech() sources together cover ~95%+ of modern browsers. Fallback-only scanners inspect the one source that modern browser users never receive. Detection: download and analyze every tech()-qualified source independently.
CRITICAL unicode-range + calt targeting: Attack activates only for specific consent keyword character sequences via GSUB contextual alternates. Individual glyph tests pass; only full-sequence rendering or GSUB calt table analysis exposes the attack. Detection: GSUB calt lookup parsing with consent keyword corpus matching.
HIGH Fallback-first scanner evasion: Clean font placed as last src with no tech() qualifier specifically for scanner inspection. All tech()-qualified sources are attack fonts. Detection: compare tech()-qualified and fallback sources for advance width and glyph ink divergence.

SkillAudit analyzes every tech()-qualified @font-face source in MCP server stylesheets, comparing them against fallback sources for glyph-level manipulation before you claude plugin install. Run a free scan — results in 60 seconds.