Web font copy protection

I have been exploring ways to protect WOFF2 fonts from being harvested and stolen by agents and scrapers. 

The concept is simple, use a simple xor based encryption to scramble the bytes offline with a key. Then in the browser use a simple javascript to decrypt the encrypt font and registers it through the FontFace API via a Blob URL.

here is the code if anyone is interested: 
https://github.com/ianshk/font-scrambler

I will get around to exploring it a but more when I have some free time.

Comments

  • So, does this scheme work ok with screen readers and the like?

    And is that blob URL inaccessible to agents and scrapers?
  • ian
    ian Posts: 3
    edited September 15
    It would work with screen readers but for the decryption to work it depends on javascript.

    A Blob URL is created at runtime. It is not in the HTML, not in @font-face CSS. So will prevent a bot that wget’s the page, parses stylesheets for .woff2

  • Way Back Machine / Internet Archive?
    Other folks who want to archive a page for later offline reading/reference?
  • I know there can never be a solution where browsers can use the font but scrapers can't, but I love thinking about it as an excercise :-)

    There's always two levels to this: preventing "dumb" scraping, where bots simply parse CSS for links to fonts in @font-face rules, or watch network requests for font files. And the next level, "smart" scraping where you deliberately circumvent a specific "protection". Your experiment guards to the first, but not the second. A dev (or, these days, AI) will see what's going on, and request the font by jumping through the hoops you put in place.

    If your approach is to serve a broken font (signature okay, data scrambled) and you're processing the binary in JavaScript anyway, wouldn't it suffice to overwrite a few bytes after the signature (e.g. set 0x04-0xff to 0) on the server side, and restore these through JavaScript after load?
  • I thought agents use browsers, and are easy to make indistinguishable from human users?

    I don't get it lol
  • I just got a déjà vu I cannot suppress.
  • XOR, JavaScript, FontFace, and Blob URLs are all public and well-established technologies. XOR-based obfuscation/protection of fonts is certainly not new either. For example, the W3C’s Embedded OpenType (EOT) File Format specification mentions an XOR-based mechanism for encrypting embedded font data.
    What caught my attention here is therefore not XOR itself, nor any of the individual technologies, but the particular combination of them and the sequence in which they are used. That could, of course, be a coincidence, or simply the result of a problem that naturally leads to the same solution when using the available browser APIs.
    In October 2025, in the FontEnigma discussion, I described an approach to web-font protection involving:
    XOR-encrypted WOFF2 files → JavaScript decryption in the browser → FontFace → a temporary Blob URL.
    The relevant part of my post was:
    ‘More importantly, the woff2 font files are now XOR-encrypted, and then decrypted on the fly in the browser using JavaScript. Once decoded, the fonts are loaded using FontFace and referenced via a temporary ‘blob:’ URL, […].’
    Now, in September 2026, I came across this post describing:
    ‘The concept is simple, use a simple xor based encryption to scramble the bytes offline with a key. Then in the browser use a simple javascript to decrypt the encrypt font and registers it through the FontFace API via a Blob URL.’
    Perhaps there is an earlier implementation that predates mine, or perhaps there is another explanation entirely. Technical development is often cumulative and collaborative, and ideas can become detached from their original context as they circulate. So, I am curious about the history of this particular architecture.
    Does anyone know of an earlier example of the XOR → JavaScript → FontFace → Blob architecture?
    To be clear, I am not trying to establish who ‘invented’ the technique; XOR-based font obfuscation clearly predates this discussion by many years. I am just trying to understand how this particular browser-side implementation appeared in these two discussions, almost a year apart.