From d9ee37c7e60df23e1720f833a664e36ea8522d90 Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Mon, 15 Jun 2026 21:35:37 +0530 Subject: [PATCH 1/7] feat: reject NIP-70 protected events --- .changeset/nip70-reject-protected-events.md | 5 + src/handlers/event-message-handler.ts | 29 ++++- .../handlers/event-message-handler.spec.ts | 112 ++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 .changeset/nip70-reject-protected-events.md diff --git a/.changeset/nip70-reject-protected-events.md b/.changeset/nip70-reject-protected-events.md new file mode 100644 index 00000000..a5247b13 --- /dev/null +++ b/.changeset/nip70-reject-protected-events.md @@ -0,0 +1,5 @@ +--- +"nostream": minor +--- + +feat: reject NIP-70 protected events and reposts embedding them diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index c58efeb9..19baf948 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -1,4 +1,4 @@ -import { ContextMetadataKey, EventExpirationTimeMetadataKey, EventKinds } from '../constants/base' +import { ContextMetadataKey, EventExpirationTimeMetadataKey, EventKinds, EventTags } from '../constants/base' import { DEFAULT_NIP05_VERIFY_EXPIRATION_MS, extractNip05FromEvent, @@ -21,6 +21,7 @@ import { isEventSignatureValid, isExpiredEvent, isFileMessageEvent, + isProtectedEvent, isRequestToVanishEvent, isSealEvent, isWelcomeRumorEvent, @@ -88,6 +89,13 @@ export class EventMessageHandler implements IMessageHandler { return } + reason = this.isProtectedEventBlocked(event) + if (reason) { + logger('event %s rejected: %s', event.id, reason) + this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, reason)) + return + } + reason = await this.isBlockedByRequestToVanish(event) if (reason) { logger('event %s rejected: %s', event.id, reason) @@ -224,6 +232,25 @@ export class EventMessageHandler implements IMessageHandler { } } + protected isProtectedEventBlocked(event: Event): string | undefined { + if (isProtectedEvent(event)) { + return 'auth-required: this event may only be published by its author' + } + + if (event.kind === EventKinds.REPOST && event.content.length > 0) { + try { + const embedded = JSON.parse(event.content) + if ( + Array.isArray(embedded?.tags) && + embedded.tags.some((tag: string[]) => Array.isArray(tag) && tag[0] === EventTags.Protected) + ) { + return 'blocked: reposts must not embed protected events' + } + } catch (_e) { + } + } + } + protected async isEventValid(event: Event): Promise { if (!(await isEventIdValid(event))) { return 'invalid: event id does not match' diff --git a/test/unit/handlers/event-message-handler.spec.ts b/test/unit/handlers/event-message-handler.spec.ts index 57ee66a9..76401433 100644 --- a/test/unit/handlers/event-message-handler.spec.ts +++ b/test/unit/handlers/event-message-handler.spec.ts @@ -2120,4 +2120,116 @@ describe('EventMessageHandler', () => { expect(nip05VerificationRepository.upsert).to.have.been.calledOnce }) }) + + describe('isProtectedEventBlocked', () => { + beforeEach(() => { + handler = new EventMessageHandler( + {} as any, + () => null, + {} as any, + userRepository, + () => + ({ + info: { relay_url: 'relay_url' }, + }) as any, + {} as any, + { hasKey: async () => false, setKey: async () => true } as any, + () => ({ hit: async () => false }), + ) + }) + + it('returns reason if event has a protected tag', () => { + event.tags = [['-']] + expect((handler as any).isProtectedEventBlocked(event)).to.equal( + 'auth-required: this event may only be published by its author', + ) + }) + + it('returns undefined if event has no protected tag', () => { + event.tags = [['e', 'abc']] + expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + + it('returns undefined if event has no tags', () => { + event.tags = [] + expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + + it('returns reason if kind 6 repost embeds a protected event', () => { + event.kind = EventKinds.REPOST + event.content = JSON.stringify({ + id: 'a'.repeat(64), + pubkey: 'b'.repeat(64), + kind: 1, + tags: [['-']], + content: 'secret', + sig: 'c'.repeat(128), + created_at: 1000, + }) + event.tags = [] + expect((handler as any).isProtectedEventBlocked(event)).to.equal( + 'blocked: reposts must not embed protected events', + ) + }) + + it('returns undefined if kind 6 repost embeds a non-protected event', () => { + event.kind = EventKinds.REPOST + event.content = JSON.stringify({ + id: 'a'.repeat(64), + pubkey: 'b'.repeat(64), + kind: 1, + tags: [], + content: 'public', + sig: 'c'.repeat(128), + created_at: 1000, + }) + event.tags = [] + expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + + it('returns undefined if kind 6 repost has empty content', () => { + event.kind = EventKinds.REPOST + event.content = '' + event.tags = [] + expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + + it('returns undefined if kind 6 repost has invalid JSON content', () => { + event.kind = EventKinds.REPOST + event.content = 'not json' + event.tags = [] + expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + + it('returns undefined for non-repost event kinds with JSON content', () => { + event.kind = EventKinds.TEXT_NOTE + event.content = JSON.stringify({ tags: [['-']] }) + event.tags = [] + expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + + it('rejects on the protected tag before checking embedded repost content', () => { + event.kind = EventKinds.REPOST + event.content = JSON.stringify({ + id: 'a'.repeat(64), + pubkey: 'b'.repeat(64), + kind: 1, + tags: [['-']], + content: 'secret', + sig: 'c'.repeat(128), + created_at: 1000, + }) + event.tags = [['-']] + expect((handler as any).isProtectedEventBlocked(event)).to.equal( + 'auth-required: this event may only be published by its author', + ) + }) + + it('returns undefined if kind 6 repost has non-array embedded tags', () => { + event.kind = EventKinds.REPOST + event.content = JSON.stringify({ tags: 'not-an-array' }) + event.tags = [] + expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + }) }) From 86fbcae0a746ea87d9f69de87ad943ad113d5d76 Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Sat, 20 Jun 2026 23:50:18 +0530 Subject: [PATCH 2/7] fix: add suggested comment --- src/handlers/event-message-handler.ts | 1 + test/unit/handlers/event-message-handler.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index 19baf948..7f50089a 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -247,6 +247,7 @@ export class EventMessageHandler implements IMessageHandler { return 'blocked: reposts must not embed protected events' } } catch (_e) { + // Ignore invalid JSON: repost content is not a valid embedded event } } } diff --git a/test/unit/handlers/event-message-handler.spec.ts b/test/unit/handlers/event-message-handler.spec.ts index 76401433..67121a00 100644 --- a/test/unit/handlers/event-message-handler.spec.ts +++ b/test/unit/handlers/event-message-handler.spec.ts @@ -2139,7 +2139,7 @@ describe('EventMessageHandler', () => { }) it('returns reason if event has a protected tag', () => { - event.tags = [['-']] + event.tags = [['-']] as any expect((handler as any).isProtectedEventBlocked(event)).to.equal( 'auth-required: this event may only be published by its author', ) @@ -2219,7 +2219,7 @@ describe('EventMessageHandler', () => { sig: 'c'.repeat(128), created_at: 1000, }) - event.tags = [['-']] + event.tags = [['-']] as any expect((handler as any).isProtectedEventBlocked(event)).to.equal( 'auth-required: this event may only be published by its author', ) From 8b746eee92afde12a8c375c1fd6746c097b102d7 Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Sun, 28 Jun 2026 12:06:22 +0530 Subject: [PATCH 3/7] fix: validate embedded repost events and log parse failures --- src/handlers/event-message-handler.ts | 22 +++--- .../handlers/event-message-handler.spec.ts | 76 +++++++++++-------- 2 files changed, 57 insertions(+), 41 deletions(-) diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index 7f50089a..38d0c5ca 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -1,4 +1,6 @@ -import { ContextMetadataKey, EventExpirationTimeMetadataKey, EventKinds, EventTags } from '../constants/base' +import { ContextMetadataKey, EventExpirationTimeMetadataKey, EventKinds } from '../constants/base' +import { attemptValidation } from '../utils/validation' +import { eventSchema } from '../schemas/event-schema' import { DEFAULT_NIP05_VERIFY_EXPIRATION_MS, extractNip05FromEvent, @@ -89,7 +91,7 @@ export class EventMessageHandler implements IMessageHandler { return } - reason = this.isProtectedEventBlocked(event) + reason = await this.isProtectedEventBlocked(event) if (reason) { logger('event %s rejected: %s', event.id, reason) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, reason)) @@ -232,22 +234,22 @@ export class EventMessageHandler implements IMessageHandler { } } - protected isProtectedEventBlocked(event: Event): string | undefined { + protected async isProtectedEventBlocked(event: Event): Promise { if (isProtectedEvent(event)) { return 'auth-required: this event may only be published by its author' } if (event.kind === EventKinds.REPOST && event.content.length > 0) { try { - const embedded = JSON.parse(event.content) - if ( - Array.isArray(embedded?.tags) && - embedded.tags.some((tag: string[]) => Array.isArray(tag) && tag[0] === EventTags.Protected) - ) { + const embedded = attemptValidation(eventSchema)(JSON.parse(event.content)) as Event + if (!(await isEventIdValid(embedded)) || !(await isEventSignatureValid(embedded))) { + return + } + if (isProtectedEvent(embedded)) { return 'blocked: reposts must not embed protected events' } - } catch (_e) { - // Ignore invalid JSON: repost content is not a valid embedded event + } catch (error) { + logger('event %s repost embedded event validation failed: %o', event.id, error) } } } diff --git a/test/unit/handlers/event-message-handler.spec.ts b/test/unit/handlers/event-message-handler.spec.ts index 67121a00..44ebf248 100644 --- a/test/unit/handlers/event-message-handler.spec.ts +++ b/test/unit/handlers/event-message-handler.spec.ts @@ -2122,6 +2122,8 @@ describe('EventMessageHandler', () => { }) describe('isProtectedEventBlocked', () => { + const PRIVKEY = '0000000000000000000000000000000000000000000000000000000000000001' + beforeEach(() => { handler = new EventMessageHandler( {} as any, @@ -2138,77 +2140,89 @@ describe('EventMessageHandler', () => { ) }) - it('returns reason if event has a protected tag', () => { + const createValidEmbeddedEvent = async (tags: string[][]): Promise => { + const unsigned = await identifyEvent({ + pubkey: '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'.slice(2), + created_at: 1000, + kind: 1, + tags, + content: 'test content', + }) + return signEvent(PRIVKEY)(unsigned) + } + + it('returns reason if event has a protected tag', async () => { event.tags = [['-']] as any - expect((handler as any).isProtectedEventBlocked(event)).to.equal( + expect(await (handler as any).isProtectedEventBlocked(event)).to.equal( 'auth-required: this event may only be published by its author', ) }) - it('returns undefined if event has no protected tag', () => { + it('returns undefined if event has no protected tag', async () => { event.tags = [['e', 'abc']] - expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined }) - it('returns undefined if event has no tags', () => { + it('returns undefined if event has no tags', async () => { event.tags = [] - expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined }) - it('returns reason if kind 6 repost embeds a protected event', () => { + it('returns reason if kind 6 repost embeds a valid protected event', async () => { + const embedded = await createValidEmbeddedEvent([['-']]) event.kind = EventKinds.REPOST - event.content = JSON.stringify({ - id: 'a'.repeat(64), - pubkey: 'b'.repeat(64), - kind: 1, - tags: [['-']], - content: 'secret', - sig: 'c'.repeat(128), - created_at: 1000, - }) + event.content = JSON.stringify(embedded) event.tags = [] - expect((handler as any).isProtectedEventBlocked(event)).to.equal( + expect(await (handler as any).isProtectedEventBlocked(event)).to.equal( 'blocked: reposts must not embed protected events', ) }) - it('returns undefined if kind 6 repost embeds a non-protected event', () => { + it('returns undefined if kind 6 repost embeds a valid non-protected event', async () => { + const embedded = await createValidEmbeddedEvent([]) + event.kind = EventKinds.REPOST + event.content = JSON.stringify(embedded) + event.tags = [] + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + + it('returns undefined if embedded event has invalid id/sig (forged protected tag)', async () => { event.kind = EventKinds.REPOST event.content = JSON.stringify({ id: 'a'.repeat(64), pubkey: 'b'.repeat(64), kind: 1, - tags: [], - content: 'public', + tags: [['-']], + content: 'secret', sig: 'c'.repeat(128), created_at: 1000, }) event.tags = [] - expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined }) - it('returns undefined if kind 6 repost has empty content', () => { + it('returns undefined if kind 6 repost has empty content', async () => { event.kind = EventKinds.REPOST event.content = '' event.tags = [] - expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined }) - it('returns undefined if kind 6 repost has invalid JSON content', () => { + it('returns undefined if kind 6 repost has invalid JSON content', async () => { event.kind = EventKinds.REPOST event.content = 'not json' event.tags = [] - expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined }) - it('returns undefined for non-repost event kinds with JSON content', () => { + it('returns undefined for non-repost event kinds with JSON content', async () => { event.kind = EventKinds.TEXT_NOTE event.content = JSON.stringify({ tags: [['-']] }) event.tags = [] - expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined }) - it('rejects on the protected tag before checking embedded repost content', () => { + it('rejects on the outer protected tag before checking embedded repost content', async () => { event.kind = EventKinds.REPOST event.content = JSON.stringify({ id: 'a'.repeat(64), @@ -2220,16 +2234,16 @@ describe('EventMessageHandler', () => { created_at: 1000, }) event.tags = [['-']] as any - expect((handler as any).isProtectedEventBlocked(event)).to.equal( + expect(await (handler as any).isProtectedEventBlocked(event)).to.equal( 'auth-required: this event may only be published by its author', ) }) - it('returns undefined if kind 6 repost has non-array embedded tags', () => { + it('returns undefined if kind 6 repost has non-array embedded tags', async () => { event.kind = EventKinds.REPOST event.content = JSON.stringify({ tags: 'not-an-array' }) event.tags = [] - expect((handler as any).isProtectedEventBlocked(event)).to.be.undefined + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined }) }) }) From f12cadc74f13c00c22588438ffece0c495aee44d Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Sun, 28 Jun 2026 13:20:32 +0530 Subject: [PATCH 4/7] test: fix TypeScript type error in event-message-handler.spec.ts --- test/unit/handlers/event-message-handler.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/handlers/event-message-handler.spec.ts b/test/unit/handlers/event-message-handler.spec.ts index 44ebf248..a27a270e 100644 --- a/test/unit/handlers/event-message-handler.spec.ts +++ b/test/unit/handlers/event-message-handler.spec.ts @@ -2145,7 +2145,7 @@ describe('EventMessageHandler', () => { pubkey: '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'.slice(2), created_at: 1000, kind: 1, - tags, + tags: tags as any, content: 'test content', }) return signEvent(PRIVKEY)(unsigned) From 3bd8a150b2eacc2436025df39698522a3d9cdabf Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Sun, 28 Jun 2026 13:25:16 +0530 Subject: [PATCH 5/7] fix: add NIP-18 Generic Repost check and use logger.warn for protected event validation --- src/constants/base.ts | 2 ++ src/handlers/event-message-handler.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/constants/base.ts b/src/constants/base.ts index d5b5f526..84625c8f 100644 --- a/src/constants/base.ts +++ b/src/constants/base.ts @@ -7,6 +7,8 @@ export enum EventKinds { DELETE = 5, REPOST = 6, REACTION = 7, + // NIP-18: Reposts + GENERIC_REPOST = 16, // NIP-17: Private Direct Messages SEAL = 13, DIRECT_MESSAGE = 14, diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index 38d0c5ca..a5a6f403 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -239,7 +239,7 @@ export class EventMessageHandler implements IMessageHandler { return 'auth-required: this event may only be published by its author' } - if (event.kind === EventKinds.REPOST && event.content.length > 0) { + if ((event.kind === EventKinds.REPOST || event.kind === EventKinds.GENERIC_REPOST) && event.content.length > 0) { try { const embedded = attemptValidation(eventSchema)(JSON.parse(event.content)) as Event if (!(await isEventIdValid(embedded)) || !(await isEventSignatureValid(embedded))) { @@ -249,7 +249,7 @@ export class EventMessageHandler implements IMessageHandler { return 'blocked: reposts must not embed protected events' } } catch (error) { - logger('event %s repost embedded event validation failed: %o', event.id, error) + logger.warn('event %s repost embedded event validation failed: %o', event.id, error) } } } From baeff1bfd2dc862d00ad9abefcaea6ce097d57ca Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Sun, 28 Jun 2026 13:30:22 +0530 Subject: [PATCH 6/7] fix: add missing NIP-42 auth check for NIP-70 protected events --- src/handlers/event-message-handler.ts | 4 +++- .../handlers/event-message-handler.spec.ts | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index a5a6f403..27e4b0fc 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -236,7 +236,9 @@ export class EventMessageHandler implements IMessageHandler { protected async isProtectedEventBlocked(event: Event): Promise { if (isProtectedEvent(event)) { - return 'auth-required: this event may only be published by its author' + if (!this.webSocket.getAuthenticatedPubkeys().has(event.pubkey)) { + return 'auth-required: this event may only be published by its author' + } } if ((event.kind === EventKinds.REPOST || event.kind === EventKinds.GENERIC_REPOST) && event.content.length > 0) { diff --git a/test/unit/handlers/event-message-handler.spec.ts b/test/unit/handlers/event-message-handler.spec.ts index a27a270e..873c0963 100644 --- a/test/unit/handlers/event-message-handler.spec.ts +++ b/test/unit/handlers/event-message-handler.spec.ts @@ -2126,7 +2126,7 @@ describe('EventMessageHandler', () => { beforeEach(() => { handler = new EventMessageHandler( - {} as any, + { getAuthenticatedPubkeys: () => new Set() } as any, () => null, {} as any, userRepository, @@ -2151,13 +2151,28 @@ describe('EventMessageHandler', () => { return signEvent(PRIVKEY)(unsigned) } - it('returns reason if event has a protected tag', async () => { + it('returns reason if event has a protected tag and user is not authenticated', async () => { event.tags = [['-']] as any expect(await (handler as any).isProtectedEventBlocked(event)).to.equal( 'auth-required: this event may only be published by its author', ) }) + it('returns undefined if event has a protected tag and user is authenticated', async () => { + event.tags = [['-']] as any + handler = new EventMessageHandler( + { getAuthenticatedPubkeys: () => new Set([event.pubkey]) } as any, + () => null, + {} as any, + userRepository, + () => ({ info: { relay_url: 'relay_url' } }) as any, + {} as any, + { hasKey: async () => false, setKey: async () => true } as any, + () => ({ hit: async () => false }), + ) + expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined + }) + it('returns undefined if event has no protected tag', async () => { event.tags = [['e', 'abc']] expect(await (handler as any).isProtectedEventBlocked(event)).to.be.undefined From a95ab0f3e1ff642868227303417f26f20fd19a25 Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Sun, 28 Jun 2026 13:35:04 +0530 Subject: [PATCH 7/7] fix: recursively check embedded events to prevent nested NIP-70 bypasses --- src/handlers/event-message-handler.ts | 30 ++++++++++++------- .../handlers/event-message-handler.spec.ts | 19 ++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index 27e4b0fc..67b6b146 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -241,18 +241,28 @@ export class EventMessageHandler implements IMessageHandler { } } - if ((event.kind === EventKinds.REPOST || event.kind === EventKinds.GENERIC_REPOST) && event.content.length > 0) { - try { - const embedded = attemptValidation(eventSchema)(JSON.parse(event.content)) as Event - if (!(await isEventIdValid(embedded)) || !(await isEventSignatureValid(embedded))) { - return + const checkEmbedded = async (evt: Event, depth = 0): Promise => { + if (depth > 10) return false // Prevent infinite loops or excessive recursion + if ((evt.kind === EventKinds.REPOST || evt.kind === EventKinds.GENERIC_REPOST) && evt.content.length > 0) { + try { + const embedded = attemptValidation(eventSchema)(JSON.parse(evt.content)) as Event + if (!(await isEventIdValid(embedded)) || !(await isEventSignatureValid(embedded))) { + return false + } + if (isProtectedEvent(embedded)) { + return true + } + return await checkEmbedded(embedded, depth + 1) + } catch (error) { + logger.warn('event %s repost embedded event validation failed: %o', evt.id, error) + return false } - if (isProtectedEvent(embedded)) { - return 'blocked: reposts must not embed protected events' - } - } catch (error) { - logger.warn('event %s repost embedded event validation failed: %o', event.id, error) } + return false + } + + if (await checkEmbedded(event)) { + return 'blocked: reposts must not embed protected events' } } diff --git a/test/unit/handlers/event-message-handler.spec.ts b/test/unit/handlers/event-message-handler.spec.ts index 873c0963..14c894da 100644 --- a/test/unit/handlers/event-message-handler.spec.ts +++ b/test/unit/handlers/event-message-handler.spec.ts @@ -2193,6 +2193,25 @@ describe('EventMessageHandler', () => { ) }) + it('returns reason if kind 6 repost embeds a repost that embeds a valid protected event (nested)', async () => { + const protectedEvent = await createValidEmbeddedEvent([['-']]) + const middleRepost = await identifyEvent({ + pubkey: '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'.slice(2), + created_at: 1000, + kind: EventKinds.GENERIC_REPOST, + tags: [], + content: JSON.stringify(protectedEvent), + }) + const signedMiddleRepost = await signEvent(PRIVKEY)(middleRepost) + + event.kind = EventKinds.REPOST + event.content = JSON.stringify(signedMiddleRepost) + event.tags = [] + expect(await (handler as any).isProtectedEventBlocked(event)).to.equal( + 'blocked: reposts must not embed protected events', + ) + }) + it('returns undefined if kind 6 repost embeds a valid non-protected event', async () => { const embedded = await createValidEmbeddedEvent([]) event.kind = EventKinds.REPOST