Compare commits

..

2 commits

Author SHA1 Message Date
1f4afbf1c2 Merge pull request 'docs(backend): pin get_public_key as an intentional, load-bearing ACL exception (#26)' (#53) from fix/26-document-get-public-key-ungated into dev
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
Reviewed-on: #53
2026-07-04 13:59:30 +00:00
d35b98a8c9 docs(backend): pin get_public_key as an intentional, load-bearing ACL exception (#26)
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
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 <noreply@anthropic.com>
2026-07-04 15:58:08 +02:00

View file

@ -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;