NDK NIP-46 backend: get_public_key bypasses the permit callback — pubkey disclosure is ungated/unauditable through our ACL seam #26
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Our daemon's entire authorization model sits behind NDK's single
Nip46PermitCallback/NDKNip46Backend.pubkeyAllowed()seam. While source-reading NDK for the #25 ACL redesign I confirmed thatget_public_keydoes not go through that seam — so ourcheckIfPubkeyAllowed(and any futuregrantIsLive(now)predicate) is never consulted when a client asks for the signer's pubkey.Verified against source
NDK
nostr-dev-kit/ndk@4b86acd(2026-04-05), nip46 undercore/src/signers/nip46/:Nip46PermitCallback = (params: {id, pubkey, method, params?}) => Promise<boolean>(backend/index.ts:29-43) — is invoked via the overridablepubkeyAllowed()(backend/index.ts:229-231) from inside each method strategy:connect.ts:27,sign-event.ts:33,nip44-encrypt.ts:27(+ nip04/nip44 siblings),ping.ts:17,switch-relays.ts:20.get_public_keyhas no such call.backend/get-public-key.ts:3-11returnsbackend.localUser?.pubkeydirectly, with nopubkeyAllowedinvocation. So every other method is gated;get_public_keyis not.For contrast, rust-nostr's
NostrConnectSignerActions::approve()(signer/nostr-connect/src/signer.rs:201-202, 342-345) wraps the entire request match — includingget_public_key. So this is an NDK-specific gap, not inherent to NIP-46.Why it matters for us
Requestrow or hitting the ACL. If our threat model ever wants to (a) gate pubkey disclosure per-app, (b) audit/log who queried identity, or (c) deny it for revoked/suspended/expired grants, the NDK seam cannot express it.grantIsLive(now)predicate consulted on every request."get_public_keyis a silent exception to "every request." Worth deciding deliberately whether that's acceptable (pubkey is arguably public-ish) or whether we override it.Options
get_public_keyas intentionally ungated (the pubkey is not secret), and note the exception explicitly in the ACL so it's not mistaken for full coverage.setStrategy('get_public_key', ...)(backend/index.ts:156-158) with a variant that callspubkeyAllowed()first, so disclosure is gated/audited like everything else.get_public_keythroughpubkeyAllowed(behind a flag, for back-compat) so embedders can choose.Cross-refs
docs/acl-prior-art-survey.md(NDK section).No action needed before #25 lands; filing so the exception is tracked rather than discovered later.