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
- Inventory — every occurrence of each address: HTML anchors, JS literals, error strings, templates; note which files must keep the plain form.
- Choose the primary channel — contact form where available; obfuscated mailto for direct-contact points.
- 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.
- Wire the decoder — one external same-origin script that rebuilds mailto anchors at load; no CSP change on a
script-src 'self' site.
- Fix JS literals — replace embedded addresses with runtime
join('@') assembly.
- Decode-verify every payload before deploying — each must round-trip to the exact intended address.
- 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
- Address inventory — every occurrence, its treatment (form / obfuscated / runtime-assembled / legitimately plain)
- Implementation — payload spans, decoder file, noscript fallbacks, CSP confirmation
- Verification log — per-payload decode check; per-page curl+grep result
- Residual plain addresses — which files keep them and why, with unlinked/noindex status
Quality checklist
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 library. Learn more at the AI Agent Skills Library.
1---2name: email-address-obfuscation3description: 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 verification4license: MIT5---67# Email Address Obfuscation89## Purpose1011Publish 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.1213## When to use1415Before 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.1617## Inputs expected1819Partial inputs are fine.2021- The address(es) to publish and the pages/components that reference them22- The site's CSP (especially `script-src`) and whether a contact form exists or is planned23- Any email templates or files that legitimately must contain the plain address2425---2627## Guiding principles2829- **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.30- **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.31- **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.32- **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.33- **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.34- **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.3536## Process37381. **Inventory** — every occurrence of each address: HTML anchors, JS literals, error strings, templates; note which files must keep the plain form.392. **Choose the primary channel** — contact form where available; obfuscated mailto for direct-contact points.403. **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.414. **Wire the decoder** — one external same-origin script that rebuilds mailto anchors at load; no CSP change on a `script-src 'self'` site.425. **Fix JS literals** — replace embedded addresses with runtime `join('@')` assembly.436. **Decode-verify every payload** before deploying — each must round-trip to the exact intended address.447. **Verify live** — curl every served page and grep for each plain address; confirm only the intended template files contain it, unlinked and noindexed.4546## Output format47481. **Address inventory** — every occurrence, its treatment (form / obfuscated / runtime-assembled / legitimately plain)492. **Implementation** — payload spans, decoder file, noscript fallbacks, CSP confirmation503. **Verification log** — per-payload decode check; per-page curl+grep result514. **Residual plain addresses** — which files keep them and why, with unlinked/noindex status5253## Quality checklist5455- [ ] No plain address in any served HTML, JS, or error string (curl + grep proven)56- [ ] Payloads generated by the real encoder or copied verbatim — none hand-written; each decode-verified57- [ ] Decoder is an external same-origin file; CSP unchanged on a `script-src 'self'` site58- [ ] `<noscript>` fallback present and screen-reader friendly ("name [at] domain [dot] tld")59- [ ] JS-literal addresses assembled at runtime with `join('@')`60- [ ] Remaining plain addresses confined to files that must contain them, unlinked and noindexed6162## Avoid6364- HTML entity encoding as the protection — 1–5% of harvesters read straight through it65- Image or CSS-reversal tricks — they break screen readers and copy-paste66- Inline decoder scripts on a CSP site — keep the decoder external and same-origin67- Hand-writing a "plausible" hex payload — it decodes to garbage; generate or copy verbatim, then verify68- Assuming two different payloads for one address indicate an error — keys are random per instance69- Forgetting addresses in JS string literals and error messages — they are harvested from source like any other text7071## Example usage7273> "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."7475---7677_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)._