Skip to content

Commit 12a431b

Browse files
committed
fix(signing): trusted origins sign without a prompt (general NIP-07 signer)
handleSignEvent gated auto-signing on `trusted && autoSign && isSolidAuth`, so a trusted origin only signed kind-27235 (Solid auth) silently and re-prompted for every other event kind. A normal Nostr client — the forum publishes kind 0 / 10002 / 22242 on every load — therefore raised three approval popups on each page load, making Podkey unusable as a single general-purpose signer. Align signing with the trust model already used by GET_PUBLIC_KEY and nip44.{encrypt,decrypt}: a trusted origin is sufficient to skip the prompt (decrypting DMs is strictly more sensitive than signing, and is already silent for trusted origins). An untrusted origin still ALWAYS prompts, and approving it establishes revocable trust — no silent first-use. let shouldSign = trusted && autoSign && isSolidAuth; -> let shouldSign = trusted; Tests updated to the general-signer contract (133 pass, lint clean). Co-Authored-By: jjohare <github@thedreamlab.uk>
1 parent 384918d commit 12a431b

2 files changed

Lines changed: 34 additions & 27 deletions

File tree

src/background.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,18 @@ async function handleSignEvent (event, origin, _sender) {
160160

161161
const keypair = await getKeypair();
162162

163-
// Check if this is a Solid auth event (kind 27235) - auto-sign if trusted
163+
// A trusted origin signs without a prompt — the same trust model already used
164+
// by GET_PUBLIC_KEY and nip44.{encrypt,decrypt} (decrypting DMs is strictly
165+
// more sensitive than signing, and is silent for trusted origins). An
166+
// untrusted origin always prompts, and approving it establishes revocable
167+
// trust. The previous `&& autoSign && isSolidAuth` gate special-cased Solid
168+
// kind-27235 and made Podkey unusable as a general NIP-07 signer — a normal
169+
// client (kind 0/1/10002/22242…) re-prompted on every page load. isSolidAuth
170+
// now only tunes the wording of the first-contact prompt.
164171
const isSolidAuth = event.kind === 27235;
165172
const trusted = await isTrustedOrigin(origin);
166-
const autoSign = await getAutoSign();
167173

168-
let shouldSign = trusted && autoSign && isSolidAuth;
174+
let shouldSign = trusted;
169175

170176
if (!shouldSign) {
171177
// Show signing prompt with event preview

test/consent-approval.test.js

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
* - deny rejects the request ("User denied ...")
1212
* - the 60s timeout auto-denies
1313
* - APPROVE_SIGNING with approved:false (popup beforeunload = deny) rejects
14-
* - trusted-origin + autoSign auto-path: kind 27235 Solid auth signs with NO
15-
* prompt; any other kind still prompts even when trusted+autoSign
14+
* - trusted-origin auto-path: a trusted origin signs ANY event kind with no
15+
* prompt (general NIP-07 signer); an untrusted origin always prompts, and
16+
* approving it establishes revocable trust
1617
*/
1718

1819
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
@@ -186,7 +187,7 @@ describe('consent gate / approval flow', () => {
186187
});
187188
});
188189

189-
describe('trusted-origin auto-path (autoSign)', () => {
190+
describe('trusted-origin auto-path (general signer)', () => {
190191
let keypair;
191192

192193
beforeEach(async () => {
@@ -197,49 +198,49 @@ describe('trusted-origin auto-path (autoSign)', () => {
197198
await storeKeypair(keypair.privateKey, keypair.publicKey);
198199
});
199200

200-
it('signs a kind-27235 Solid event with NO prompt when trusted + autoSign', async () => {
201+
it('signs a kind-27235 Solid event with NO prompt when the origin is trusted', async () => {
201202
await addTrustedOrigin('https://pod.test');
202-
await setAutoSign(true);
203203
const result = await send({ type: 'SIGN_EVENT', event: SOLID_EVENT, origin: 'https://pod.test' });
204-
assert.equal(windowsCreated.length, 0, 'auto-sign path must not open a popup');
204+
assert.equal(windowsCreated.length, 0, 'trusted origin must not open a popup');
205205
assert.equal(result.kind, 27235);
206206
assert.equal(result.pubkey, keypair.publicKey);
207207
assert.equal(result.sig.length, 128);
208208
});
209209

210-
it('still PROMPTS for a non-Solid kind even when trusted + autoSign', async () => {
210+
it('signs a non-Solid kind with NO prompt when the origin is trusted (general signer)', async () => {
211+
// The forum publishes kind 0/10002/22242 on every load; a trusted origin
212+
// must sign these without a per-event prompt or Podkey is unusable as a
213+
// general NIP-07 signer. autoSign is irrelevant — trust is the grant.
211214
await addTrustedOrigin('https://pod.test');
212-
await setAutoSign(true);
213-
const pending = send({ type: 'SIGN_EVENT', event: NOTE_EVENT, origin: 'https://pod.test' });
214-
await flush();
215-
assert.equal(windowsCreated.length, 1, 'non-Solid kind must still require approval');
216-
respond(lastRequestId(), true);
217-
const result = await pending;
215+
const result = await send({ type: 'SIGN_EVENT', event: NOTE_EVENT, origin: 'https://pod.test' });
216+
assert.equal(windowsCreated.length, 0, 'trusted origin signs any kind without a popup');
218217
assert.equal(result.kind, 1);
218+
assert.equal(result.sig.length, 128);
219219
});
220220

221-
it('PROMPTS for a Solid event when autoSign is OFF (even if trusted)', async () => {
222-
await addTrustedOrigin('https://pod.test');
223-
await setAutoSign(false);
224-
const pending = send({ type: 'SIGN_EVENT', event: SOLID_EVENT, origin: 'https://pod.test' });
221+
it('ALWAYS prompts an untrusted origin, regardless of autoSign', async () => {
222+
// No silent first-use: an origin the user has never approved must prompt,
223+
// even for a Solid event and even with the autoSign convenience enabled.
224+
await setAutoSign(true);
225+
const pending = send({ type: 'SIGN_EVENT', event: SOLID_EVENT, origin: 'https://untrusted.test' });
225226
await flush();
226-
assert.equal(windowsCreated.length, 1, 'autoSign off must require explicit approval');
227+
assert.equal(windowsCreated.length, 1, 'untrusted origin must require explicit approval');
227228
respond(lastRequestId(), true);
228229
const result = await pending;
229230
assert.equal(result.kind, 27235);
230231
});
231232

232-
it('approving an untrusted signing request trusts the origin (no re-prompt)', async () => {
233-
await setAutoSign(true);
234-
// First request: untrusted -> prompt -> approve.
233+
it('approving an untrusted signing request trusts the origin (no re-prompt, any kind)', async () => {
234+
// First request: untrusted -> prompt -> approve (establishes trust).
235235
const first = send({ type: 'SIGN_EVENT', event: SOLID_EVENT, origin: 'https://new.test' });
236236
await flush();
237237
assert.equal(windowsCreated.length, 1);
238238
respond(lastRequestId(), true);
239239
await first;
240-
// Second Solid request from the now-trusted origin: auto-signs, no popup.
241-
const second = await send({ type: 'SIGN_EVENT', event: SOLID_EVENT, origin: 'https://new.test' });
240+
// Second request from the now-trusted origin — a *different* (non-Solid)
241+
// kind — auto-signs with no further popup.
242+
const second = await send({ type: 'SIGN_EVENT', event: NOTE_EVENT, origin: 'https://new.test' });
242243
assert.equal(windowsCreated.length, 1, 'origin became trusted; no second popup');
243-
assert.equal(second.kind, 27235);
244+
assert.equal(second.kind, 1);
244245
});
245246
});

0 commit comments

Comments
 (0)