Merged to main as 9bed97b (tenant-iso-domains; Fable review plus one re-check, CI green). Proof of control is opt-in, via CONSOLEDOMAINPROOF=true; a deployment that hosts apps for other people should set it after its fleet has rolled. Account namespaces, including those of deleted accounts, are reserved to their own account unconditionally. Fleet boxes see claimed hostnames, but only their own account's claim bodies, without createdby. Accepted risks are in the branch description. For flagship: set CONSOLEDOMAINPROOF and CONSOLERESERVEPLATFORMDOMAINS once the fleet runs a release with this change.
Custom domain claims need proof of control: dangling-DNS takeover, squatting, unreserved account namespaces, claims readable fleet-wide Issue closed
On branch tenant-iso-domains
Claimed by claude~paul@keymail.dev 3 weeks ago
Found by the 2026-10-03 tenant-isolation audit of main 78fc8d40, by reading code.
- Custom hostnames are first-come, first-served with no proof of control (
console/domainattach.go:144,174,domains/claims.go:121-141). This is intended by2026-08-27-managed-custom-domains-design.md:28-29, but it fails tenants in two ways: - Takeover: if B's DNS already points at the edge (B pointed it before attaching, detached, or deleted the app), A attaches the name.Edge.Allowedadmits the exact host, HTTP-01 passes, and A serves content on B's name with a platform-issued certificate. - Squatting: A can claim any domain first. B gets a 409 and has no self-serve way to get it back. - The apps domain is unreserved unless the deployment sets
CONSOLE_RESERVE_PLATFORM_DOMAINS=true(console/apidomains.go:132-155). Without it, A can attach<new>.<B-sqid>.<appsdomain>. That name serves immediately through the DNS wildcard and B's account wildcard certificate, andclaimApp'sapexHoldercheck then stops B creating that app.parentsFor(domains/wildcard.go:207-214) also orders a wildcard certificate for each invented label, which can use up the Let's Encrypt quota for the apps domain. I couldn't confirm whether the flagship sets it: the console's env isn't in carloku-infrastructure. Whatever a deployment sets, an account's own<sqid>namespace belongs to that account and should be refused to everyone else. - Every fleet box can read all of `domains/
** (edge/storedoor.go:389-405, 559). The comment says it is "public via DNS anyway", but each claim also carriesCreatedBy(a person's Keymail address), the account sqid, the app name,Paths` maps, and claims for names that don't resolve yet. Anyone can create an account and a fleet and list it.
Related: #209 (operator attach of the platform's own apex), which is on platform-domain-claims-209. Fixes are going on a separate branch from this session, kept clear of that one.