attacker-controlled-site.com
Published 2026-07-24

Use .example, .test and .invalid — the RFC everyone ignores

Standards reserve four names so documentation never points at a domain someone can buy. What to use instead, and the places people forget.

There is one rule, and it fits in a sentence.

Never put a registrable domain in documentation, sample code, test configuration or a security payload.

Everything below is why that rule exists and where people break it without noticing.

The names that were set aside for this

Two RFCs did the work. RFC 2606 (1999) reserved a set of names for documentation and testing, and RFC 6761 (2013) formalised them as Special-Use Domain Names with an IANA registry behind them.

What you get:

Name Use it for
.example Any example hostname you like: attacker.example, api.example
.test Testing and development
.invalid Names that are meant to fail resolution
.localhost The loopback interface
example.com, example.net, example.org Second-level names, held by IANA

None of these can be registered by anyone. That is the entire point: an example is supposed to be inert. attacker.example makes exactly the same pedagogical point as any scary-sounding real domain, with the difference that it can never resolve to somebody’s server.

What people write instead

They reach for something that reads like a threat. evil.com. malicious.com. attacker.com. attacker-controlled-site.com.

Every one of those is a real, registrable domain name. Three of them have been owned since the 1990s. The fourth was registrable until 2026, which is how this site came to exist — it appeared in advisories, blog posts and vendor documentation as the place a stolen token was supposed to land, and nobody had claimed it.

That is the gap. A name that reads as fictional is not fictional. The browser, the HTTP client and the mail server do not know that the author meant it as a placeholder.

Safe substitutions, if you want to keep the tone:

Instead of Write
attacker-controlled-site.com attacker.example
evil.com evil.example
malicious.com malicious.example
yourcompany.com example.com
test.mysite.com test.example

Where it actually bites

Prose in a blog post is the harmless end. The damage happens where an example gets executed, deployed or delivered.

Copy-pasted proof of concept

A PoC is written to be run. Someone reproducing a bug pastes the payload verbatim into their own environment, and the exfiltration target in that payload is a live domain. If it is registered by somebody else, the reproduction step quietly ships real data off-site.

Configuration that survives the tutorial

A proxy_pass, a webhook URL, an OAuth redirect_uri allowlist. These get copied into a config file, the config file gets committed, and the placeholder becomes infrastructure. It stops looking like an example the moment it is three directories deep in a repo.

SPF includes and mail

An include: in an SPF record points at a domain you do not control. If that domain expires and someone else registers it, they inherit a say in who may send mail as you. Same shape of problem, different protocol.

Email addresses in examples

admin@yourcompany.com in an onboarding doc is a real mailbox somewhere. Support forms, signup flows and test fixtures all end up sending to it.

Test fixtures and CI

Fixtures outlive the person who wrote them. A test that hits a placeholder domain is a test that makes an outbound request to a stranger’s server on every CI run, for years, until somebody wonders why the build is flaky.

This is not a scolding

Everybody needs an example domain, and the authors who used this one picked something readable that made the danger obvious. That is good writing. The mistake is old, widespread, and easy to make in an afternoon when you are focused on explaining the actual vulnerability.

It is also easy to fix, and the fix is entirely mechanical: search your documentation for domains, and replace anything that is not a reserved name.

rg -o '[a-z0-9-]+\.(com|net|org|io|dev)' docs/ examples/ | sort -u

Read the list. Anything you do not own and did not mean as a real reference should be a .example name instead.

The one-line version

If an example resolves, it is not an example. It is infrastructure you do not control.