Skip to content

About

Protect emails, phone numbers, & other content from bots using AES-GCM obfuscation for Astro/Starlight (SSR/SSG).

Topics

Resources

Security policy

Stars

26 stars

Watchers

1 watching

Forks

Repository files navigation

Astro Mail Obfuscation

npm Total Downloads npm Version

v3: Rebuilt. Advanced content obfuscator for Astro and Starlight.

Supports SSR, SSG, <ClientRouter />, and Trusted Types.

Background

Bots crawl the web for email addresses, phone numbers, and other personal data. This can lead to spam emails, unwanted calls, and other forms of abuse (e.g., invoice fraud).

Most of them take a straightforward approach. However, common obfuscation techniques (e.g., XOR, ROT13) can become predictable once their implementation patterns are known.

Approach

Astro Mail Obfuscation minimizes stable patterns that bots can reuse. Each build generates new attribute names, keys, and produces a client runtime obfuscated with JavaScript Obfuscator. Content is AES-GCM-encrypted and ECDSA-signed, while the client deliberately avoids exposing errors.

Source Comparison

Without:

<a href="tel:+498999998000">+49 89 99998 000</a>

With Astro Mail Obfuscation:

<a data-c8c327ad7b16="IcB8lePUuQlN..."></a>

JavaScript restores the content on the client side.

Installation

Requires Node.js >=22.12.0, Astro >=5.0.0 <8.0.0, and a secure context (localhost or HTTPS).

Astro CLI (recommended):

npx astro add astro-mail-obfuscation

Manual:

npm install astro-mail-obfuscation
# or pnpm add / yarn add / bun add
// astro.config.mjs
import { defineConfig } from "astro/config";

import mailObfuscation from "astro-mail-obfuscation";

export default defineConfig({
    integrations: [mailObfuscation()],
});

Migration from v2 to v3

Remove all deprecated options from mailObfuscation() in astro.config.mjs. Marker values are no longer evaluated; change data-obfuscation="..." to data-obfuscation.

Usage

Add the data-obfuscation marker to any HTML content element:

<a href="mailto:john.doe@example.com" data-obfuscation>
    <span>john.doe@example.com</span>
</a>

<p>Tax ID: <span data-obfuscation>[TAX_ID]</span></p>

<address data-obfuscation>
    John Doe's Placeholder Emporium<br />
    [STREET]<br />
    [POSTAL_CODE], [CITY]<br />
    [COUNTRY]
</address>

All attributes except critical ones (e.g., id, class, style, hidden) are obfuscated.

Do not nest data-obfuscation or include <noscript> in obfuscated content. Do not obfuscate <astro-island> or its contents, and do not use custom elements as marked roots.

Optional Configuration

Provide a <noscript> notice for users without JavaScript:

<div>
    <a href="mailto:..." data-obfuscation>
        <span>...</span>
    </a>
    <noscript>Please enable JavaScript.</noscript>
</div>

Do not put sensitive data inside <noscript>; use it for a general notice.

To replace the default marker, set markerAttributeName:

// astro.config.mjs
mailObfuscation({
    markerAttributeName: "data-shield", // /^data-[a-z0-9_-]+$/
});
<a href="mailto:..." data-shield>...</a>

Architecture

stateDiagram-v2
    [*] --> Mail_Obfuscation_Server: Setup
    Mail_Obfuscation_Server --> Middleware: AES_KEY + ECDSA_PRIVATE_KEY
    Middleware --> HTML_Source: Payload (encrypted/signed)
    Mail_Obfuscation_Server --> Mail_Obfuscation_Client: AES_KEY + ECDSA_PUBLIC_KEY
    Mail_Obfuscation_Vite --> Mail_Obfuscation_Client: obfuscates
    HTML_Source --> Mail_Obfuscation_Client
    Mail_Obfuscation_Client --> Error: ECDSA:REJECTED
    Mail_Obfuscation_Client --> AES_GCM: ECDSA:VERIFIED
    AES_GCM --> [*]: restored
Loading

The client runtime needs the AES_KEY to decrypt the payload. Because the same key can also be used to create a valid modified payload, AES-GCM alone cannot verify where the payload came from.

ECDSA provides this verification. The client runtime receives only the ECDSA_PUBLIC_KEY; the ECDSA_PRIVATE_KEY remains on the build/server side.

SSR Behavior

In SSR mode, HTML responses are buffered.

This is required because the integration needs the complete HTML document for validation and transformation. As a result, HTML responses cannot be streamed.

The middleware does not parse or reserialize marker-free HTML, and Cheerio is loaded lazily. All HTML responses are still inspected, and the small client runtime is injected on every page.

Threat Model

Obfuscation is not security. Instead, it raises the cost of automated data extraction.

Astro Mail Obfuscation protects against crawlers and scrapers that inspect raw HTML. It disrupts tools that rely on predictable patterns or static extractors.

Because the integration randomizes its client runtime and regenerates identifiers and cryptographic keys on every build, a single static extractor becomes significantly harder to reuse across sites and builds.

Security

Astro Mail Obfuscation does not sanitize HTML.

Sanitize your content before obfuscation if it comes from an untrusted source (e.g., content management system, user input). For example, use DOMPurify.

Trusted Types

If your CSP restricts Trusted Types policy names, configure a static one:

// astro.config.mjs
mailObfuscation({
    trustedTypesPolicyName: "tt-a7f3c9", // pass-through only; no sanitization
});

Avoid astro-mail-obfuscation and other obvious names. This option does not modify your CSP headers. Allow the same policy name in your trusted-types directive.

License

MIT - (c) 2024-2026 Andreas Brunner.

Built in Germany.

About

Protect emails, phone numbers, & other content from bots using AES-GCM obfuscation for Astro/Starlight (SSR/SSG).

Topics

Resources

Security policy

Stars

26 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages