Problem
Two things the platform is about to do need the same fact: that the person asking controls a domain.
- Single sign-on for a team (RFC 0027) sends everyone with an address at
company.hrto the company's identity provider. Without proof, anyone could claim the domain and capture its people's sign-ins. RFC 0027 decided how to prove it: a DNS TXT record at_inorbit-verify.company.hr, checked by the accounts service, a domain verified for one team at a time. - Testing from our cloud (RFC 0031) sends checks and load at an address. Without proof, the platform becomes a tool for hitting someone else's site. The chaos tool we run on our own stack takes any address today, which is acceptable only because it is ours.
The IETF's work on domain control validation describes how this is done well: an underscore name that cannot collide with a real host, a random token per account, an expiry, lookups from more than one place, and removal when no longer needed (draft-ietf-dnsop-domain-verification-techniques-13, 2026-06-22).
Proposal
Domains are a place of their own in the console, under the account (personal or
team), and the same objects over the API and in iohr. RFC 0027's Security page links to
them rather than holding them.
A wizard, not a form
Adding a domain is a guided path in the console, because for most people it is the first thing they do and a wrong DNS record is the most common reason it fails:
- The domain. A person types
company.hr. The wizard checks it is a real registered domain and not already verified by this account. - The scope. The whole domain and everything under it, or one subdomain such as
staging.company.hrand only what is under that (see Scope below). - The record. The platform makes a token, 160 random bits written in base32 so DNS
keeps it intact, bound to this account and this domain, and shows the one record to
add:
_inorbit-verify.company.hr TXT "inorbit-verify=<token>", with a copy button. It looks up who serves the domain's DNS and shows that provider's own steps (, Route 53, Google Cloud DNS, Azure DNS, the common registrars), or general steps when it does not recognise one. - The check. The wizard checks by itself while the page is open and says what it sees: nothing yet, a record with the wrong value, or the right one. The accounts service asks more than one public resolver, in different networks, and validates DNSSEC where the zone has it; a record seen by only one resolver does not count.
- The confirmation. A summary of what was proved (the domain, the scope, how and when) and what it unlocks for this account, which the person confirms. Only then is the domain verified. Unconfirmed tokens expire after seven days.
- What next. The last step offers the two things a verified domain is for: setting up single sign-on for a team (RFC 0027) and installing an agent for this domain (RFC 0029), which continues straight into the agent's own wizard with the domain already chosen.
The API and iohr domains follow the same steps without the pages: add, read the record,
verify, confirm.
Scope
A verified domain covers itself and every name under it (api.company.hr,
staging.company.hr). A company that wants less proves a subdomain instead
(staging.company.hr), and only that subtree is covered.
One account at a time, kept true
- A domain is verified for one account at a time, as RFC 0027 decided for teams. A second account that proves the same domain takes it over, the first is told, and the change is in both audit logs.
- The record is checked again every day. If it disappears, the domain becomes unverified after three failed days in a row, and everything that depends on it stops: checks and load from our cloud are refused, and single sign-on falls back to the owner's own sign-in.
- Removing a domain in the console tells the person they may now delete the record.
A diagram is drawn here in the RFCs product; this page does not show diagrams yet.
What depends on it
| Feature | Needs |
|---|---|
| Single sign-on and provisioning for a team (RFC 0027) | the e-mail domain verified for that team |
| Checks, load and capacity sweeps run from our cloud (RFC 0031) | every target address inside a verified domain |
| An agent (RFC 0029) acting on a named host | the host inside a verified domain the agent is bound to, as well as the agent's local policy |
An agent acting on an address with no public name (a bare private address, db.internal) |
the agent's local policy naming it; no domain can be proved for it |
| Faults | never from our cloud; only through an agent |
A diagram is drawn here in the RFCs product; this page does not show diagrams yet.
The same everywhere
- API:
GET/POST /v1/accounts/orgs/{account}/domains,GET/DELETE …/domains/{domain},POST …/domains/{domain}/checkandPOST …/domains/{domain}/confirm, under new scopesdomains:readanddomains:write, deny by default (RFC 0016). - iohr:
iohr domains add|verify|list|rm, printing the record to add and, with--wait, checking until it is seen. - Events:
domain.verified,domain.unverifiedanddomain.transferred, carrying ids, never content (RFC 0022), anddomain.*entries in the audit log.
Alternatives considered
A file at a well-known URL on the domain. Common, and it proves control of one web server, not the domain; every subdomain would need its own.
A CNAME pointing at us. Lets us renew the proof without the company touching DNS again. Useful later for automated renewal; the TXT record is what every DNS provider and every security team understands today.
Verify per feature. Single sign-on and testing would each ask for their own record. One proof, used by both, is less work for the company and one thing for us to keep true.
Trust the address a person types. What the internal chaos tool does, and the reason it cannot be offered to anyone else as it is.
Decision
Decided 2026-10-07, built and live as proposed: one TXT record per domain per account, as RFC 0027 decided, looked up from several resolvers, covering the domain and its subdomains, re-checked daily, and the one gate for single sign-on and for everything our cloud sends at a customer's systems.
Publication
The console gains Domains. The developer docs gain a page on proving a domain with each
common DNS provider and the API reference the domain endpoints and scopes; the command
line's reference gains iohr domains.
Status log
- 2026-10-03: opened, extending RFC 0027's domain record to every account and to testing from our cloud, with RFC 0028, 0029 and 0031.
- 2026-10-03: adding a domain is a wizard that ends in single sign-on or an agent, and an agent acts on named hosts only inside the verified domains it is bound to (RFC 0029).
- 2026-10-03: built in the accounts service: the
accounts.domainstable, lookups through three public resolvers over DNS-over-HTTPS with two that must agree, the daily re-check that unverifies after three failed days, transfer between accounts, the scopesdomains:readanddomains:write, the events, and the platform'sCoveredBycheck. The console gains Domains with the six-step wizard, and the developer docs a page per DNS provider. The API's verify step is two calls, check and confirm. - 2026-10-07: Decided: built and live. Domains (#137) run in accounts at 932c4445 and
the wizard in console-ui at 3a40912a;
iohr domainsmerged in inorbithr/sdk#40. Single sign-on, which will use the same gate, is RFC 0027 and not built.