From 8eae0a69e4ecc7d630f2be2be9a5457dc6644bb7 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 24 Sep 2026 19:20:47 +0700 Subject: [PATCH] security: verify Amber signatures + pin MCP server versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes two items from the platform/completeness audit appendices. Amber signature verification (audit "open verification item", LOW): reconstructSignedEvent() previously trusted whatever `sig` the external Amber signer app returned — it recomputed the event id but never verified the Schnorr signature. A compromised or buggy signer could therefore return a forged/garbage signature that the app would treat as valid and publish. Now verifyEvent() is run on the reconstructed event before it is returned; failures return null, which every caller already handles as an "incomplete signature" error. This single choke point covers all Amber paths: the native AmberIntent signer, AmberCallbackPage, and AmberCallbackHandler. F9 (LOW): pin MCP server versions in .mcp.json. Both js-dev-mcp and nostrbook/mcp were fetched at @latest, silently pulling the newest release into the agent tool environment every session. Pinned to the current exact versions (0.6.3 / 0.5.3) so upgrades are deliberate. Co-authored-by: shakespeare.diy --- .mcp.json | 4 ++-- src/lib/amber.ts | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.mcp.json b/.mcp.json index bf437ae..673c280 100644 --- a/.mcp.json +++ b/.mcp.json @@ -3,12 +3,12 @@ "js-dev": { "type": "stdio", "command": "npx", - "args": ["-y", "@soapbox.pub/js-dev-mcp@latest"] + "args": ["-y", "@soapbox.pub/js-dev-mcp@0.6.3"] }, "nostr": { "type": "stdio", "command": "npx", - "args": ["-y", "@nostrbook/mcp@latest"] + "args": ["-y", "@nostrbook/mcp@0.5.3"] } } } \ No newline at end of file diff --git a/src/lib/amber.ts b/src/lib/amber.ts index 4719661..9af8794 100644 --- a/src/lib/amber.ts +++ b/src/lib/amber.ts @@ -1,5 +1,5 @@ import type { NostrEvent, NostrSigner } from '@nostrify/nostrify'; -import { getEventHash } from 'nostr-tools'; +import { getEventHash, verifyEvent } from 'nostr-tools'; /** * Pending Amber operation stored in multiple places for PWA resilience. @@ -257,7 +257,23 @@ export function reconstructSignedEvent( const id = parsed?.id && parsed.id.length === 64 ? parsed.id : getEventHash(base); - return { ...base, id, sig }; + const signed: NostrEvent = { ...base, id, sig }; + + // DEFENSE-IN-DEPTH: the external signer (Amber) is a separate app and is not + // implicitly trusted. Before handing this event back to the app to be + // published or trusted, prove it is genuinely signed by `eventPubkey`. + // `verifyEvent` re-checks both the id (hash of the serialized event) and the + // Schnorr signature, so a compromised or buggy signer that returns a forged + // or mismatched signature cannot produce a publishable event — we return null + // (all callers treat null as "incomplete signature" and surface a friendly + // "approve the request in Amber, then try again" error). + try { + if (!verifyEvent(signed)) return null; + } catch { + return null; + } + + return signed; } /**