From d35b98a8c9e328913b5ca65543969ca635f0cd5f Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 4 Jul 2026 15:58:08 +0200 Subject: [PATCH] docs(backend): pin get_public_key as an intentional, load-bearing ACL exception (#26) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_public_key returns the signer pubkey without routing through pubkeyAllowed(), unlike every other NIP-46 method. #26 flagged this as an unaudited/ungated disclosure through the ACL seam and asked us to decide deliberately between gating it and documenting the exception. Verified against the live clients: gating it would BREAK production. lnbits' _ensure_policy (remote_bunker.py DEFAULT_POLICY_RULES + DEFAULT_POLICY_METHODS_NO_KIND) grants only sign_event(kinds) + the four nip04/44 crypto methods — no get_public_key rule — and the client calls get_public_key as a spec-mandated, hardcoded post-connect session step (nip46_bunker_client.py connect()). Routing it through checkIfPubkeyAllowed would return `undefined`, dropping that call onto the admin-approval path and stalling session establishment → "signer unavailable" (the #41 outage class). So the correct resolution is #26's option A (accept + document): the pubkey isn't secret, NIP-46 mandates it ungated during session setup, and the clients carry no grant for it. Make the exception explicit and load-bearing in the code so a future refactor doesn't "helpfully" gate it and reintroduce the outage. No behavior change; tsc-clean; test:nip46 green. Co-Authored-By: Claude Opus 4.8 --- src/daemon/backend/index.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/daemon/backend/index.ts b/src/daemon/backend/index.ts index 1562fb0..7bfd39b 100644 --- a/src/daemon/backend/index.ts +++ b/src/daemon/backend/index.ts @@ -104,6 +104,29 @@ export class Backend { return ok ? "pong" : undefined; } case "get_public_key": + // Intentionally UNGATED — do NOT route through pubkeyAllowed(). + // This is a deliberate, documented exception to the "every + // request hits the ACL seam" rule (aiolabs/nsecbunkerd#26), not + // an oversight, and it is load-bearing — gating it breaks the + // live clients: + // 1. The pubkey isn't secret — it's the identity the bunker + // openly signs as; disclosure leaks nothing a relay + // observer couldn't already derive from published events. + // 2. NIP-46 mandates get_public_key as part of session + // establishment (46.md overview step 5: the client requests + // get_public_key to learn user-pubkey), called immediately + // post-connect, before any policy-bearing method. + // 3. lnbits' `_ensure_policy` (remote_bunker.py + // DEFAULT_POLICY_RULES + DEFAULT_POLICY_METHODS_NO_KIND) + // grants only sign_event(kinds) + nip04/44 encrypt/decrypt + // — there is NO get_public_key rule — so routing it through + // checkIfPubkeyAllowed would return `undefined`, and the + // client's spec-mandated post-connect get_public_key call + // would stall on the admin-approval path → "signer + // unavailable" (the outage class #41 addressed). + // Per-app gating/audit of identity disclosure, if ever wanted, + // must be co-designed with a policy rule the clients actually + // plant. See #26 for the full prior-art survey (NDK vs rust-nostr). return this.pubkey; case "sign_event": { const [eventString] = params;