mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-10-31 18:41:00 +00:00
feat(verify): optional NIP-01 signature verification, on by default in the widget
Closes the gap the audit left open: administrative state fails closed without a verifier, and BitGate shipped none. @bitgate/verify is a separate package so core, nostr, and runtime keep no crypto dependency and can be audited without one. The widget bundles it, so the drop-in path is verified with no extra step; verify="off" disables it, sets data-unverified on the element, and emits bitgate:unverified — meant for local development, and meant to be visible. Verification recomputes the event id before checking the signature. Checking only the signature would let a forged event pass by reusing a genuine signature over an unrelated id, which is the failure mode that matters here. createVerifier also rejects events dated far in the future. A far-future created_at wins replaceable-event selection indefinitely and pins stale state in place; the signature is perfectly valid, so only a clock check catches it. Found while testing: #adminIngestAllowed checked that a verifier *exists*, not that this event was verified — so the public ingestEvent path, which "bring your own relays" integrations use, bypassed verification entirely. Verification now runs inside ingestEvent, with an already-verified flag for the loaders. An async verifier cannot be awaited there and is treated as unverified; ingestVerified() awaits properly. Conformance validated against 120 live events from a public relay across kinds 0, 1, 3 and 10002 — 59 with non-ASCII content and 71 containing quotes, newlines or tabs, which is where a serialization mismatch would surface. All 120 verified, zero id mismatches. Bundle grows 72K -> 120K. 855 tests; audit clean.
This commit is contained in:
1 parent
94ef6a72f5
commit
3b95a63058
14 files changed
+596
-12
No files matched your search
@@ -11,6 +11,7 @@
|
||||
|
||||
import { createBitGate, createCommands, createRelayTransport } from "@bitgate/runtime";
|
||||
import { getPolicyPreset } from "@bitgate/core";
|
||||
import { createVerifier } from "@bitgate/verify";
|
||||
|
||||
import { GovernanceElement, defineElement, escapeHtml } from "./base.js";
|
||||
|
||||
@@ -35,7 +36,17 @@ export function requestContext(element) {
|
||||
|
||||
export class BitGateProvider extends globalThis.HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ["relays", "root", "policy", "profile", "application", "namespace", "viewer", "trust"];
|
||||
return [
|
||||
"relays",
|
||||
"root",
|
||||
"policy",
|
||||
"profile",
|
||||
"application",
|
||||
"namespace",
|
||||
"viewer",
|
||||
"trust",
|
||||
"verify",
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@@ -150,6 +161,23 @@ export class BitGateProvider extends globalThis.HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verified by default. A relay can otherwise put the root's key on an
|
||||
// event the root never wrote, and administrative state would fail closed
|
||||
// anyway — so the secure path has to be the one that needs no opt-in.
|
||||
const verificationDisabled = this.getAttribute("verify") === "off";
|
||||
if (verificationDisabled) {
|
||||
this.setAttribute("data-unverified", "");
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("bitgate:unverified", {
|
||||
detail: { reason: 'verify="off"' },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.removeAttribute("data-unverified");
|
||||
}
|
||||
|
||||
this.transport = transport;
|
||||
this.runtime = createBitGate({
|
||||
applicationId: this.getAttribute("application") ?? globalThis.location?.hostname ?? "bitgate",
|
||||
@@ -159,6 +187,8 @@ export class BitGateProvider extends globalThis.HTMLElement {
|
||||
policy,
|
||||
storage: createLocalStorage(),
|
||||
now: () => Math.floor(Date.now() / 1000),
|
||||
verifySignature: verificationDisabled ? undefined : createVerifier(),
|
||||
trustUnsignedEvents: verificationDisabled,
|
||||
});
|
||||
this.commands = createCommands(this.runtime);
|
||||
|
||||
|
||||
Reference in new issue
Block a user