fix(kez-chat/web): verifiers surface real reasons instead of silent fall-through
DNS verifier used to say "no envelope found" even when a kez:z1: TXT was sitting there but failed to parse (DNS providers can mangle bytes at 255-char segment boundaries). GitHub verifier said "no proof found" even when the gists API returned 403 — rate-limited from the browser (unauthenticated GitHub allows only 60 req/hr/IP). Now: - DNS: distinguishes "found a kez record but it's corrupted" from "no kez record exists." Calls out provider-side segment mangling and tells the user to re-publish. - GitHub: returns the actual HTTP status and rate-limit reset time when the gists API rejects the request. - Both: when an envelope's primary doesn't match the local key, the error explicitly notes "probably signed with an older build — re-sign and re-publish" (relevant to anything created before cd8dda6). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
cd8dda681c
commit
21d9b705b7
@ -65,34 +65,47 @@ export const verifyDns: Verifier = async (ctx) => {
|
||||
.join(""),
|
||||
);
|
||||
|
||||
const parseErrors: string[] = [];
|
||||
for (const txt of candidates) {
|
||||
let fetched;
|
||||
try {
|
||||
const fetched = await parseAnyEnvelope(txt);
|
||||
if (fetched.payload.subject !== ctx.subject) continue;
|
||||
if (fetched.payload.primary !== ctx.primary) {
|
||||
return fail(
|
||||
`TXT envelope's primary (${fetched.payload.primary}) doesn't match your key`,
|
||||
{ evidence_url: url, fetched },
|
||||
);
|
||||
fetched = await parseAnyEnvelope(txt);
|
||||
} catch (e) {
|
||||
if (txt.includes("kez:z1:") || txt.includes("```kez")) {
|
||||
// Looks like it was MEANT to be a kez envelope — record why parse failed
|
||||
parseErrors.push(` • ${(e as Error).message} — value: ${txt.slice(0, 60)}…`);
|
||||
}
|
||||
if (!verifyEnvelope(fetched)) {
|
||||
return fail(`TXT envelope signature is invalid`, {
|
||||
evidence_url: url,
|
||||
fetched,
|
||||
});
|
||||
}
|
||||
return ok(`Verified via DNS TXT at ${queryName}`, {
|
||||
evidence_url: url,
|
||||
fetched,
|
||||
details: `Fetched ${candidates.length} TXT record(s); signature checked against ${ctx.primary}.`,
|
||||
});
|
||||
} catch {
|
||||
// not a kez envelope — try the next TXT record
|
||||
continue;
|
||||
}
|
||||
if (fetched.payload.subject !== ctx.subject) continue;
|
||||
if (fetched.payload.primary !== ctx.primary) {
|
||||
return fail(
|
||||
`TXT envelope's primary (${fetched.payload.primary ?? "missing"}) doesn't match your key (${ctx.primary}). The published value was probably signed with an older build — re-publish from your Claims page.`,
|
||||
{ evidence_url: url, fetched },
|
||||
);
|
||||
}
|
||||
if (!verifyEnvelope(fetched)) {
|
||||
return fail(`TXT envelope signature is invalid`, {
|
||||
evidence_url: url,
|
||||
fetched,
|
||||
});
|
||||
}
|
||||
return ok(`Verified via DNS TXT at ${queryName}`, {
|
||||
evidence_url: url,
|
||||
fetched,
|
||||
details: `Fetched ${candidates.length} TXT record(s); signature checked against ${ctx.primary}.`,
|
||||
});
|
||||
}
|
||||
return fail(`Found TXT records at ${queryName} but none contained a valid kez envelope for ${ctx.subject}`, {
|
||||
evidence_url: url,
|
||||
details: `Candidates examined:\n${candidates.map((c) => ` • ${c.slice(0, 80)}${c.length > 80 ? "…" : ""}`).join("\n")}`,
|
||||
});
|
||||
return fail(
|
||||
parseErrors.length > 0
|
||||
? `Found a kez TXT record at ${queryName} but it couldn't be decoded — your DNS provider may have mangled the value at a segment boundary. Try re-publishing.`
|
||||
: `Found TXT records at ${queryName} but none contained a kez envelope for ${ctx.subject}`,
|
||||
{
|
||||
evidence_url: url,
|
||||
details:
|
||||
parseErrors.length > 0
|
||||
? `Parse errors:\n${parseErrors.join("\n")}`
|
||||
: `Candidates examined:\n${candidates.map((c) => ` • ${c.slice(0, 80)}${c.length > 80 ? "…" : ""}`).join("\n")}`,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@ -29,36 +29,66 @@ async function tryGists(
|
||||
const resp = await fetch(url, {
|
||||
headers: { Accept: "application/vnd.github+json" },
|
||||
});
|
||||
if (!resp.ok) return null;
|
||||
if (!resp.ok) {
|
||||
// Surface the failure instead of silently falling through.
|
||||
// 403 with X-RateLimit-Remaining: 0 is the common case for
|
||||
// unauthenticated browser callers (60 req/hr/IP).
|
||||
const reset = resp.headers.get("x-ratelimit-reset");
|
||||
const remaining = resp.headers.get("x-ratelimit-remaining");
|
||||
return fail(
|
||||
`GitHub gists API responded ${resp.status}` +
|
||||
(remaining === "0" ? " (rate-limited)" : ""),
|
||||
{
|
||||
evidence_url: url,
|
||||
details:
|
||||
`GitHub's unauthenticated rate limit is 60 req/hr/IP.\n` +
|
||||
(reset
|
||||
? `Limit resets at ${new Date(Number(reset) * 1000).toLocaleString()}.\n`
|
||||
: "") +
|
||||
`Wait and try again, or check that the user exists.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
const gists = (await resp.json()) as Gist[];
|
||||
for (const g of gists) {
|
||||
const kezFile = Object.values(g.files).find(
|
||||
(f) => f.filename.toLowerCase() === "kez.md",
|
||||
);
|
||||
if (!kezFile) continue;
|
||||
let raw: string;
|
||||
try {
|
||||
const raw = await fetch(kezFile.raw_url).then((r) => r.text());
|
||||
const fetched = await parseAnyEnvelope(raw);
|
||||
if (fetched.payload.subject !== ctx.subject) continue;
|
||||
if (fetched.payload.primary !== ctx.primary) {
|
||||
return fail(
|
||||
`Gist envelope's primary doesn't match your key`,
|
||||
{ evidence_url: g.html_url, fetched },
|
||||
);
|
||||
}
|
||||
if (!verifyEnvelope(fetched)) {
|
||||
return fail(`Gist envelope signature is invalid`, {
|
||||
evidence_url: g.html_url,
|
||||
fetched,
|
||||
});
|
||||
}
|
||||
return ok(`Verified via public gist`, {
|
||||
raw = await fetch(kezFile.raw_url).then((r) => r.text());
|
||||
} catch (e) {
|
||||
return fail(`Failed to fetch gist raw: ${(e as Error).message}`, {
|
||||
evidence_url: g.html_url,
|
||||
});
|
||||
}
|
||||
let fetched;
|
||||
try {
|
||||
fetched = await parseAnyEnvelope(raw);
|
||||
} catch (e) {
|
||||
return fail(
|
||||
`Gist ${g.id} has kez.md but it doesn't contain a parseable kez envelope: ${(e as Error).message}`,
|
||||
{ evidence_url: g.html_url },
|
||||
);
|
||||
}
|
||||
if (fetched.payload.subject !== ctx.subject) continue;
|
||||
if (fetched.payload.primary !== ctx.primary) {
|
||||
return fail(
|
||||
`Gist envelope's primary (${fetched.payload.primary ?? "missing"}) doesn't match your key (${ctx.primary}). The published value was probably signed with an older build — re-sign on the Claims page and update the gist.`,
|
||||
{ evidence_url: g.html_url, fetched },
|
||||
);
|
||||
}
|
||||
if (!verifyEnvelope(fetched)) {
|
||||
return fail(`Gist envelope signature is invalid`, {
|
||||
evidence_url: g.html_url,
|
||||
fetched,
|
||||
});
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
return ok(`Verified via public gist`, {
|
||||
evidence_url: g.html_url,
|
||||
fetched,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user