feat(theseus): a subdomain rule applies wherever the name is served from

The gateway checks a name's host rules before it decides what to serve, so a
blocked or redirected subdomain behaves the same whatever record the name
carries. Theseus only inherited that for names it proxies through the
gateway's /bns/ mount. A name with both s3 and ip — the shape that caused
the 2026-08-13 subdomain bug — would have had its blocked subdomain answer
anyway, because Theseus talks straight to the IP.

It now asks the gateway for the host's verified rule before taking either of
the paths it serves itself, and only for those paths, so an ordinary
subdomain navigation gains no round trip. Verification stays in one place:
the client reads a decision, it does not re-derive one.

The spec catches up with what is implemented — it still described v1 and
called hosts a future idea.
This commit is contained in:
Silent Mode 2026-09-28 01:24:08 +02:00
parent 1a317007df
commit 24b83dee4d

View file

@ -1783,6 +1783,10 @@ const server = http.createServer(async (req, res) => {
if (!v.ok) { res.writeHead(502); res.end(JSON.stringify({ error: v.error, ...(v.detail || {}) })); return; }
const hostEntry = askedHost ? hostOverride(manifest, raw, name) : null;
const hostDns = hostEntry && typeof hostEntry.dns === "object" ? hostEntry.dns : null;
// Clients that serve a subdomain themselves — Theseus talks straight to
// an `ip` record rather than proxying through here — need the routing
// half too, and must not re-implement manifest verification to get it.
const hostAct = askedHost ? hostAction(hostEntry) : null;
// Verified. Cache the seq and hand back the DNS block.
_dnsSeqCache.set(name, manifest.seq);
res.setHeader("Cache-Control", "public, max-age=30");
@ -1790,6 +1794,7 @@ const server = http.createServer(async (req, res) => {
res.end(JSON.stringify({
name,
host: askedHost || null,
host_action: hostAct,
seq: manifest.seq,
updated_at: manifest.updated_at ?? null,
// A host that declares its own dns block owns its record set outright