Skip to content

NIP-98 (kind:27235) sign_event: malformed-params hang + stale-created_at on delayed approval #89

Description

@DocNR

Investigating an App Store review reporting that NIP-98 HTTP-auth signing (sign_event kind:27235 — used by gitworkshop, nsite, and media uploads) did not work: "lacking parity with Amber. wasnt able to HTTP Auth challenge requests." Review is mild (4-star, "seems promising"), no repro steps, and no matching GitHub account/issue exists from the reviewer. This issue reports what live testing against Clave's real signing code actually found — two confirmed bugs, one path cleared, and what's still unconfirmed.

Confirmed via live wire tests

research/nip98-signing-repro/run.sh drives ClaveTests/LightSignerNip98WireSmokeTests — Clave's actual LightSigner.handleRequest — against a local nak serve relay, then decrypts and checks the kind:24133 responses with nak.

Cleared: a spec-compliant sign_event(kind:27235) request auto-signs correctly on medium trust (the default bunker pairing). The signed event passes all four NIP-98 server checks (kind, exact u/method tags, created_at within the suggested 60s window) and the signature verifies. This path is not broken.

Bug A — malformed RPC params hang the client silently, on any trust level. LightSigner.swift:145-158: if a client sends sign_event with params shaped as [{...}] (a nested object) instead of the spec-required [json_stringified_string], decode fails and params degrades to []. Because extractEventKind then returns nil, LightSigner.swift:374-377 forces allowed = false before trust level is even consulted — so the request gets queued for approval even on medium/full trust, and per NIP-46's "no terminal error while a request is pending" rule, Clave sends no response at all. A spec-compliant client (nostr-tools/NDK — what gitworkshop and nsyte are built on) has no client-side timeout; it just hangs. The queued entry doesn't even carry a kind number to show the user (eventKind is nil, not 27235).

Bug B — a delayed low-trust approval delivers an already-stale event. LightEvent.swift:110 (signUnsignedEvent) uses the client-supplied created_at verbatim; Clave never refreshes it to the actual signing moment. If a user on low trust takes more than NIP-98's suggested 60s to approve — plausible, since the prompt shows no kind label explaining what's being approved (see below) — the delivered event is already outside the freshness window the instant Clave signs it. Clave reports "signed" successfully; the failure only surfaces one hop later, at the HTTP server's 401.

Already-known parity gaps (static, not re-litigated here)

  • Approval prompt has no label for kind 27235 — KnownKinds.names (ClientPermissions.swift:261-291) has no entry, so a low-trust prompt shows bare "Kind 27235".
  • Clave ignores the optional perms param clients request at connect (nothing reads it).

Not confirmed

  • The reviewer's exact failure — no repro steps to match against.
  • The push/NSE cold-start wake path. The iOS Simulator has no push, so this couldn't be exercised here. If the reviewer's device was backgrounded when the RPC arrived, this path — not the two bugs above — is the more likely culprit, and it remains untested.
  • An end-to-end run against actual gitworkshop.dev or nsyte. The tests above drive Clave's real signing code with the exact wire shape those clients' underlying libraries produce, but don't exercise gitworkshop's/nsyte's own client code.

Repro

research/nip98-signing-repro/run.sh <simulator-udid>

Starts a local nak serve relay, runs ClaveTests/LightSignerNip98WireSmokeTests, decrypts the wire responses with nak, and checks Bugs A and B live against the actual signed events.

Source: App Store review triage, 2026-08-23.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions