From 24b83dee4d1e8327bdfad8dd80fe4702d29adaa9 Mon Sep 17 00:00:00 2001 From: Silent Mode Date: Mon, 28 Sep 2026 01:24:08 +0200 Subject: [PATCH] feat(theseus): a subdomain rule applies wherever the name is served from MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/gateway/public-gateway.mjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gateway/public-gateway.mjs b/src/gateway/public-gateway.mjs index 4bc5abb..4bac17a 100644 --- a/src/gateway/public-gateway.mjs +++ b/src/gateway/public-gateway.mjs @@ -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