security: verify Amber signatures + pin MCP server versions
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 <assistant@shakespeare.diy>
This commit is contained in:
parent
358708ee27
commit
8eae0a69e4
2 changed files with 20 additions and 4 deletions
|
|
@ -3,12 +3,12 @@
|
||||||
"js-dev": {
|
"js-dev": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "@soapbox.pub/js-dev-mcp@latest"]
|
"args": ["-y", "@soapbox.pub/js-dev-mcp@0.6.3"]
|
||||||
},
|
},
|
||||||
"nostr": {
|
"nostr": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "@nostrbook/mcp@latest"]
|
"args": ["-y", "@nostrbook/mcp@0.5.3"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { NostrEvent, NostrSigner } from '@nostrify/nostrify';
|
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.
|
* Pending Amber operation stored in multiple places for PWA resilience.
|
||||||
|
|
@ -257,7 +257,23 @@ export function reconstructSignedEvent(
|
||||||
const id =
|
const id =
|
||||||
parsed?.id && parsed.id.length === 64 ? parsed.id : getEventHash(base);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue