# Email Address Obfuscation

> Protect published email addresses from scrapers on a static site — JavaScript-assembled mailto links with an XOR-hex payload, accessible fallbacks, CSP-clean implementation, and payload verification

- Skill: `powr-data/email-address-obfuscation` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add powr-data/email-address-obfuscation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/powr-data/email-address-obfuscation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- License: MIT
- Author: POWR-DATA (https://skillmd.com/u/powr-data)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/powr-data/email-address-obfuscation

---


# Email Address Obfuscation

## Purpose

Publish contact email addresses on a static website without feeding harvesters: a JavaScript-assembled mailto pattern (the only technique that blocked 100% of live harvesters in testing) implemented so it survives a strict CSP, degrades accessibly without JavaScript, and is verified end to end so no plain address leaks from any served file.

## When to use

Before a contact email goes onto any public page — and when auditing an existing site where addresses appear in HTML, JavaScript string literals, or auth-page error messages. Apply alongside `static-website-config-and-csp` (the decoder must fit the site's CSP) and before an address starts receiving spam, not after.

## Inputs expected

Partial inputs are fine.

- The address(es) to publish and the pages/components that reference them
- The site's CSP (especially `script-src`) and whether a contact form exists or is planned
- Any email templates or files that legitimately must contain the plain address

---

## Guiding principles

- **JavaScript assembly is the only technique that fully works.** 2026 honeypot testing against roughly 700 live harvesters ranks the options: JavaScript-assembled addresses blocked 100%, HTML entity encoding only 95–99%, and image or CSS-reversal tricks block bots but break screen readers and copy-paste. Major platforms use a contact form or support portal as the primary channel and never leave a raw mailto in HTML — prefer a form as the primary path where one exists.
- **Use the self-implemented Scrape Shield pattern.** Store the address as XOR-hex in a data attribute on a `<span>` (first byte is the key), have a tiny same-origin script rebuild a normal mailto anchor at load, and provide a `<noscript>` fallback showing "name [at] domain [dot] tld" so no-JS visitors still get the address accessibly.
- **Sweep beyond mailto anchors.** Addresses inside JS string literals (auth-page error messages, form fallback text) are regex-harvestable from source — build them at runtime with `['name','domain.tld'].join('@')`. Leave a real mailto only in files that must contain it (email templates), unlinked and noindexed.
- **Keep the decoder an external same-origin file** so a strict CSP (`script-src 'self'`) needs no change — no inline script, no new CSP source.
- **Never hand-write or improvise encoded payloads.** Generate them with the real encoder or copy a payload verbatim from an already-shipped page, then decode-verify each one before deploying — an invented hex string looks plausible and decodes to garbage. Keep a decode one-liner handy; payloads for the same address may legitimately differ because keys are random per instance.
- **Verify by fetching, not by reading source.** Curl every served page and grep for the plain address — the proof is that no served byte stream contains it.

## Process

1. **Inventory** — every occurrence of each address: HTML anchors, JS literals, error strings, templates; note which files must keep the plain form.
2. **Choose the primary channel** — contact form where available; obfuscated mailto for direct-contact points.
3. **Encode** — run the encoder per instance (random key per payload); place XOR-hex payloads in `data-*` attributes on spans; add the `<noscript>` "[at]/[dot]" fallback.
4. **Wire the decoder** — one external same-origin script that rebuilds mailto anchors at load; no CSP change on a `script-src 'self'` site.
5. **Fix JS literals** — replace embedded addresses with runtime `join('@')` assembly.
6. **Decode-verify every payload** before deploying — each must round-trip to the exact intended address.
7. **Verify live** — curl every served page and grep for each plain address; confirm only the intended template files contain it, unlinked and noindexed.

## Output format

1. **Address inventory** — every occurrence, its treatment (form / obfuscated / runtime-assembled / legitimately plain)
2. **Implementation** — payload spans, decoder file, noscript fallbacks, CSP confirmation
3. **Verification log** — per-payload decode check; per-page curl+grep result
4. **Residual plain addresses** — which files keep them and why, with unlinked/noindex status

## Quality checklist

- [ ] No plain address in any served HTML, JS, or error string (curl + grep proven)
- [ ] Payloads generated by the real encoder or copied verbatim — none hand-written; each decode-verified
- [ ] Decoder is an external same-origin file; CSP unchanged on a `script-src 'self'` site
- [ ] `<noscript>` fallback present and screen-reader friendly ("name [at] domain [dot] tld")
- [ ] JS-literal addresses assembled at runtime with `join('@')`
- [ ] Remaining plain addresses confined to files that must contain them, unlinked and noindexed

## Avoid

- HTML entity encoding as the protection — 1–5% of harvesters read straight through it
- Image or CSS-reversal tricks — they break screen readers and copy-paste
- Inline decoder scripts on a CSP site — keep the decoder external and same-origin
- Hand-writing a "plausible" hex payload — it decodes to garbage; generate or copy verbatim, then verify
- Assuming two different payloads for one address indicate an error — keys are random per instance
- Forgetting addresses in JS string literals and error messages — they are harvested from source like any other text

## Example usage

> "Our contact page has a plain `mailto:` link and the password-reset page embeds the support address in an error string. The site runs a `script-src 'self'` CSP. Obfuscate every published address the way Cloudflare's Scrape Shield does, keep it accessible without JavaScript, and prove nothing served still contains the plain address."

---

_Source: This skill is sourced from the [Matrix Skills](https://github.com/POWR-DATA/mtx-skills) library. Learn more at the [AI Agent Skills Library](https://powrdata.com.au/ai-agent-skills)._

