Concealment & camouflage: choosing your tier
Naming reminder. ShieldFont is the protocol and project. ShieldFont Optik is the flagship typeface; ShieldFont MaxHide is its coverage-max sibling. See the introduction for the full naming convention.
ShieldFont ships three ways. All three do the same job, make human writing stale as AI training data: humans read the original, scrapers that read the HTML digest an encoded decoy. Stale, not poison is the deliberate framing: what the benchmark supports is that an encoded page teaches a model little (its meaning is either dropped by the quality filter or trained on in shifted form), not that it damages one. See benchmark/ for the measurements. Where the three tiers differ is camouflage: how much a page, a stylesheet, or a file quietly admits that ShieldFont is in use at all.
If you can server-render React, use it. It conceals the most and asks you to store the least. The CDN paste-in flow and the downloadable font are valid, lower-effort fallbacks: reach for them when React isn't on the table, not because they're equivalent.
The three tiers at a glance
| Tier | How you use it | Font file | Font's own metadata | Concealment |
|---|---|---|---|---|
| React (server-side): recommended | <Shield> in server-rendered code | neutral name (e.g. optik-a.woff2) | family "Optik", no version | ●●● 3/3 |
| CDN (paste-in CSS) | @import the stylesheet + a class | neutral name (e.g. optik-a.woff2) | family "Optik" + the dictionary version | ●●○ 2/3 |
| Documents (Word / PDF) | install the .ttf, paste encoded text, export. Word / Pages / InDesign only, never Google Docs (it can't load custom fonts) | branded shieldfont-*.ttf | full "ShieldFont Optik / MaxHide" | ●○○ 1/3 |
"Concealment" rates how little the delivery mechanism reveals about itself, not how much meaning the encoding removes. Protection strength is a property of the mapping, not of the delivery mechanism. But the tiers do not all ship the same mappings, so in practice they are not interchangeable:
- React bundles four mappings and, with
variantleft unset, auto-rotatesalpha/beta/gammaby content hash, so one site spreads three mappings across its pages. The rotation is deterministic per block of text (same text, same variant), not per visit.maxhideis never auto-selected. - The CDN paste-in flow encodes with
alphaonly. The browser encoder bundled in@shieldfont/font(shieldfont-encoder.js) exports that single dictionary, even though the stylesheet declares faces and classes for the other three. If you want a different variant on this tier you have to encode with@shieldfont/coreyourself and pin the matching class. maxhideis a different shape of mapping, not just a different seed: 2,534 entries against alpha's 11,970, covering a higher share of a page's words (including short function words). It conceals more per page and reads as more disrupted.alpha/beta/gammaare near-identical in size (11,970 / 12,034 / 12,036 entries).
For the step-by-step install behind each row, see the integration guide.
One hard limit on the Documents tier. Microsoft Word, Pages, and InDesign all work: the substitutions ride the OpenType ccmp feature rather than liga, so Word's ligature settings are irrelevant to them. Google Docs does not work at all, because it cannot load custom fonts: there is no way to install a ShieldFont in it. For Google Docs, draft elsewhere and export, or use another tier.
Why React (server-side) is the recommendation
It renders on your server. By the time anything reaches a browser, the text is already encoded and the font already carries a neutral name. The visitor, and any scraper hitting the same URL, receives the finished, camouflaged artifact and nothing else. There is no client-side encode step to inspect and no build marker left behind in the HTML.
You store nothing. The default alpha / beta / gamma mappings are public and bundled with @shieldfont/react, so there is no key to keep, no seed to protect, no secret that a leak could expose. Encoding is deterministic from public inputs.
That combination (server-only rendering, neutral font metadata, zero secrets to manage) makes React both the easiest tier to operate and the one that leaks the fewest tells. Treat CDN and Documents as easier-to-adopt fallbacks for stacks that can't server-render, not as equals.
What each tier reveals
The camouflage story, tier by tier.
React: most decoy
Neutral font family ("Optik"), neutral filename, no version hint. Nothing on the page announces that the text is protected: no class name you didn't choose, no package URL, no "ShieldFont" string anywhere in the served bytes. This is the most camouflaged tier and the reason it's the recommendation.
CDN: one deliberate tell, the dictionary version
The font family and filename are just as neutral as React's ("Optik", optik-a.woff2). One field differs, on purpose: the font's version carries the dictionary generation that encoded your text.
That's a feature, not a leak. Newer dictionary versions ship over time, and encoded text only reads back correctly under a matching font. Stamping the dictionary generation into the version field lets you, or a collaborator re-rendering your page later, confirm which dictionary a page was encoded with, and pair it with the right font. See Checking your font version.
Two residual tells you should know about on this tier:
- The
@shieldfont/fontpackage URL in your@importline. Anyone who reads your stylesheet can see it. - The default
.tk9CSS class. It's a neutral token, but it's a shared default: every paste-in site ships the same one.
The class name is yours to change. We ship .tk9 as a convenient default (variants .tk9-b / .tk9-c / .tk9-m), but nothing requires it: alias it to any string you like in your own stylesheet:
/* After the @import, in your own CSS.
Name it anything; the font family is what does the work. */
.reading-copy {
font-family: "Optik", system-ui, sans-serif;
}
<p class="reading-copy">…encoded text from the encoder…</p>
Now the on-page class says nothing about ShieldFont. (The @import URL is still visible in the stylesheet itself; renaming the class removes the more obvious of the two tells.)
Documents: branded on purpose
The downloadable .ttf is fully branded: it installs into Word, Pages, or InDesign under its full name (ShieldFont Optik, or ShieldFont MaxHide for the coverage-max variant), so you can actually find and pick it in the font menu. That's the point. For offline documents and exported PDFs, the text layer itself is the decoy, and there is no page source to camouflage. Whichever mapping you install protects exactly as well here as it does on any other tier; this tier is simply the most identifiable, because the font has to be selectable by a human. (Google Docs is the one place this tier cannot reach: it cannot load custom fonts.)
Checking your font version
Relevant to the CDN and Documents tiers (React never exposes a version: it renders and pairs both sides server-side, so there is nothing to reconcile).
The font's version field encodes the dictionary generation it was built for. You can read it with any font tool:
- A GUI font inspector: macOS Font Book → Info, the Windows font preview, or a desktop app like FontForge.
fc-query path/to/font.ttfon Linux/macOS with fontconfig installed: read the version line.- Any "font info" utility that lists the name/version tables.
Match the version you read to the dictionary that encoded your text. If they don't match, the text won't render back to the original: re-encode with the matching dictionary, or serve the font whose version matches your content.
Seeds: a pro-user feature
The default alpha / beta / gamma mappings are public. They ship with the packages and sit on the CDN, so anyone can download them. That's fine for most content: the value comes from the meaning the swap removes at scale, not from secrecy.
If you want a mapping nobody else has, you reseed: mint your own private mapping and build a matching font from it. You generate the seed and you store it; we never embed a seed in the font, and the only thing the font carries is the public dictionary version.
Be precise about what a seed buys you. Reseeding defeats dictionary reuse: nobody can batch-decode your pages by reaching for the published alpha / beta / gamma maps, because your pairings exist nowhere else. It does not defeat font inversion, and it never has. The font you hand the browser is the codebook: its composite glyphs are drawn from the original word's own letters, so joining them to the ligature table returns the pairs directly, seed or no seed. We ran exactly that attack against our own shipped font and recovered all 11,962 pairs, with no dictionary and no guessing, in 43 seconds. A captured font plus your encoded pages can be reversed without the seed you kept. What reseeding raises is the per-target cost: an attacker has to invert your specific font, one site at a time, instead of running one precomputed map over everyone at once.
This is an advanced path through the shipped Python scripts, not part of the standard consumer flow (there is no packaged CLI). The full how-to, both minting from the methodology and reseeding an existing mapping, plus the threat-model table, is in Custom mappings.
MaxHide
ShieldFont MaxHide is the opt-in, coverage-max variant: it swaps the most words, so it hides more of your text, at some cost to readability, versus the default alpha. Select it wherever you choose a variant: the integration guide (variant="maxhide"), with the roadmap context in the introduction.
In short
- Server-render with React if you can: most concealment, nothing to store.
- Paste-in CDN when you can't run a build: nearly as camouflaged; rename the class, mind the
@importURL, and keep an eye on the version field. - Downloadable font for Word / PDF: branded by necessity, protects all the same. Works in Word, Pages, and InDesign; not in Google Docs, which cannot load custom fonts.
Full setup for every tier lives in the integration guide.
Source: docs/concealment.md. Edits to that file flow through to this page.