From 26c663510c716ffaac90d971360eba12ecd07504 Mon Sep 17 00:00:00 2001 From: TheCryptoDonkey Date: Fri, 7 Aug 2026 11:42:39 +0100 Subject: [PATCH] fix: post refuses to sign as the wrong identity The signer that answers is whichever bunker happens to be running, and a note is as much an identity claim as a document is. publish and unpublish have refused a mismatch since 0.16.2; post had no guard at all. The failure it prevents is a quiet one and the setup for it is ordinary: publish a hole under a project key, leave the bunker up, write a note, and the note goes out under the project rather than under you. That is the 2026-08-06 mistake pointed the other way. --as costs nothing, because get_public_key is not a signing operation. --- CHANGELOG.md | 9 +++++++++ src/cli.ts | 22 +++++++++++++++++++--- test/cli.test.ts | 15 +++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c64fb56..6aba059 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Unreleased + +- `post` takes `--as npub1...` and refuses to sign as anyone else. The signer + that answers is whichever bunker happens to be running, so announcing from + the wrong identity was silent and easy: publish one thing as a project key, + leave the bunker up, post a note, and the note goes out under the project + rather than under you. `publish` and `unpublish` have refused a mismatch + since 0.16.2 and there was never a reason for `post` not to + ## 0.16.6 (2026-08-07) - permit SGR colour in menu display text, and forbid every other control there. diff --git a/src/cli.ts b/src/cli.ts index 8f3eafa..681a888 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -58,7 +58,7 @@ const USAGE = `usage: targets: npub[/path], nostr: entity, name@domain (NIP-05), gopher:// url write (needs a signer, see below): - gopherkind post [--dry-run] sign and broadcast a kind 1 note + gopherkind post [--as npub1...] [--dry-run] sign and broadcast a note gopherkind delete [--wide] [--dry-run] gopherkind publish [--as npub1...] [--expire 30d] [--dry-run] [--force] only signs documents the relays do not already carry unchanged. @@ -467,11 +467,27 @@ if (command === 'serve') { const { values, positionals } = parseArgs({ args: rest, allowPositionals: true, - options: { ...COMMON, 'dry-run': { type: 'boolean', default: false } }, + options: { + ...COMMON, + 'dry-run': { type: 'boolean', default: false }, + as: { type: 'string' }, + }, }) const text = positionals.join(' ') if (text.trim() === '') fail('usage: gopherkind post ') - run(cmdPost(text, relaysOf(values), pairingsOf(values), values['dry-run'])) + // A note is as much an identity claim as a document is, and the signer that + // answers is whichever bunker happens to be running. publish has refused a + // mismatch since 0.16.2; there was no reason for post not to. + resolveSigner(pairingsOf(values)) + .then((signer) => requireSignerIdentity(signer, values.as)) + .then((signer) => + cmdPost(text, relaysOf(values), pairingsOf(values), values['dry-run'], signer), + ) + .then((out) => { + process.stdout.write(out) + process.exit(0) + }) + .catch((err: unknown) => fail(err instanceof Error ? err.message : String(err))) } else if (command === 'delete') { const { values, positionals } = parseArgs({ args: rest, diff --git a/test/cli.test.ts b/test/cli.test.ts index da4a2ef..e59adf7 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -152,3 +152,18 @@ test('public HTTP identity requires an explicit safe proxy contract', () => { assert.notEqual(badOrigin.status, 0) assert.match(badOrigin.out, /without a path/) }) + +test('post accepts --as, so a note can name the identity it claims', () => { + // The signer that answers is whichever bunker happens to be running, and a + // note is as much an identity claim as a document is. Without --as, + // announcing from the wrong key is silent. publish has refused a mismatch + // since 0.16.2; post had no such guard until 0.16.7. + // + // Reaching the comparison needs a signer, and requireSignerIdentity is + // covered directly in the signing tests. What this asserts is that the flag + // is wired at all: before it was, this failed with ERR_PARSE_ARGS_UNKNOWN_OPTION. + const result = run(srcCli, ['post', 'hello', '--as', 'npub1whatever']) + assert.notEqual(result.status, 0) + assert.doesNotMatch(result.out, /UNKNOWN_OPTION/) + assert.match(result.out, /no signer/) +})