The server is vulnerable to replay attacks for encrypted requests. While deduplication exists for the outer envelope (event.id), the inner event (currentEvent.id) is not properly deduplicated against execution.
The recent test nostr-server-transport.dedup-response.test.ts simulates two identical inner requests and asserts correlationStore.eventRouteCount === 1. However, because correlationStore.registerEventRoute uses an LruCache keyed by the inner eventId, the second request simply overwrites the Map entry—keeping the size at 1—but the code still proceeds to dispatch the message to onmessage(). So the non-idempotent tool is actually still executing twice.
To Reproduce
- An attacker intercepts a valid, signed inner event.
- The attacker repeatedly re-wraps it in new gift-wrap envelopes.
- The server decrypts each one. The outer dedupe check passes because the outer IDs are unique.
- The inner event is parsed and passed to
authorizeAndProcessEvent.
- The
correlationStore overwrites the existing route, but the message is still dispatched to onmessage(), causing duplicate execution.
Expected behavior
The server must explicitly check and cache the decrypted inner event ID against seenEventIds before proceeding with authorization and execution. Additionally, the test should be updated to spy on onmessage to ensure it is only called once.
The server is vulnerable to replay attacks for encrypted requests. While deduplication exists for the outer envelope (
event.id), the inner event (currentEvent.id) is not properly deduplicated against execution.The recent test
nostr-server-transport.dedup-response.test.tssimulates two identical inner requests and assertscorrelationStore.eventRouteCount === 1. However, becausecorrelationStore.registerEventRouteuses anLruCachekeyed by the innereventId, the second request simply overwrites the Map entry—keeping the size at 1—but the code still proceeds to dispatch the message toonmessage(). So the non-idempotent tool is actually still executing twice.To Reproduce
authorizeAndProcessEvent.correlationStoreoverwrites the existing route, but the message is still dispatched toonmessage(), causing duplicate execution.Expected behavior
The server must explicitly check and cache the decrypted inner event ID against
seenEventIdsbefore proceeding with authorization and execution. Additionally, the test should be updated to spy ononmessageto ensure it is only called once.