@openclaw/nostr 2026.5.2 → 2026.5.3-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/api.js +532 -0
  2. package/dist/channel-DfEqBtUh.js +1466 -0
  3. package/dist/channel-plugin-api.js +2 -0
  4. package/dist/config-schema-DIk4jlBg.js +64 -0
  5. package/dist/default-relays-DLwdWOTu.js +4 -0
  6. package/dist/inbound-direct-dm-runtime-22bZWcIW.js +2 -0
  7. package/dist/index.js +84 -0
  8. package/dist/runtime-api.js +2 -0
  9. package/dist/setup-api.js +2 -0
  10. package/dist/setup-entry.js +11 -0
  11. package/dist/setup-plugin-api.js +165 -0
  12. package/dist/setup-surface-DxAaUTyC.js +336 -0
  13. package/dist/test-api.js +2 -0
  14. package/package.json +15 -6
  15. package/api.ts +0 -10
  16. package/channel-plugin-api.ts +0 -1
  17. package/index.ts +0 -97
  18. package/runtime-api.ts +0 -6
  19. package/setup-api.ts +0 -1
  20. package/setup-entry.ts +0 -9
  21. package/setup-plugin-api.ts +0 -3
  22. package/src/channel-api.ts +0 -15
  23. package/src/channel.inbound.test.ts +0 -176
  24. package/src/channel.outbound.test.ts +0 -128
  25. package/src/channel.setup.ts +0 -231
  26. package/src/channel.test.ts +0 -519
  27. package/src/channel.ts +0 -207
  28. package/src/config-schema.ts +0 -98
  29. package/src/default-relays.ts +0 -1
  30. package/src/gateway.ts +0 -302
  31. package/src/inbound-direct-dm-runtime.ts +0 -1
  32. package/src/metrics.ts +0 -458
  33. package/src/nostr-bus.fuzz.test.ts +0 -360
  34. package/src/nostr-bus.inbound.test.ts +0 -526
  35. package/src/nostr-bus.integration.test.ts +0 -472
  36. package/src/nostr-bus.test.ts +0 -190
  37. package/src/nostr-bus.ts +0 -789
  38. package/src/nostr-key-utils.ts +0 -94
  39. package/src/nostr-profile-core.ts +0 -134
  40. package/src/nostr-profile-http-runtime.ts +0 -6
  41. package/src/nostr-profile-http.test.ts +0 -632
  42. package/src/nostr-profile-http.ts +0 -594
  43. package/src/nostr-profile-import.test.ts +0 -119
  44. package/src/nostr-profile-import.ts +0 -262
  45. package/src/nostr-profile-url-safety.ts +0 -21
  46. package/src/nostr-profile.fuzz.test.ts +0 -430
  47. package/src/nostr-profile.test.ts +0 -412
  48. package/src/nostr-profile.ts +0 -144
  49. package/src/nostr-state-store.test.ts +0 -237
  50. package/src/nostr-state-store.ts +0 -223
  51. package/src/runtime.ts +0 -9
  52. package/src/seen-tracker.ts +0 -289
  53. package/src/session-route.ts +0 -25
  54. package/src/setup-surface.ts +0 -265
  55. package/src/test-fixtures.ts +0 -45
  56. package/src/types.ts +0 -117
  57. package/test/setup.ts +0 -5
  58. package/test-api.ts +0 -1
  59. package/tsconfig.json +0 -16
@@ -1,526 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { startNostrBus } from "./nostr-bus.js";
3
- import { TEST_HEX_PRIVATE_KEY } from "./test-fixtures.js";
4
-
5
- const BOT_PUBKEY = "b".repeat(64);
6
-
7
- const mockState = vi.hoisted(() => ({
8
- handlers: null as {
9
- onevent: (event: Record<string, unknown>) => void | Promise<void>;
10
- oneose?: () => void;
11
- onclose?: (reason: string[]) => void;
12
- } | null,
13
- verifyEvent: vi.fn(() => true),
14
- decrypt: vi.fn(() => "plaintext"),
15
- publishProfile: vi.fn(async () => ({
16
- createdAt: 0,
17
- eventId: "profile-event",
18
- successes: [],
19
- failures: [],
20
- })),
21
- }));
22
-
23
- vi.mock("nostr-tools", () => {
24
- class MockSimplePool {
25
- subscribeMany(
26
- _relays: string[],
27
- _filters: unknown,
28
- handlers: {
29
- onevent: (event: Record<string, unknown>) => void | Promise<void>;
30
- oneose?: () => void;
31
- onclose?: (reason: string[]) => void;
32
- },
33
- ) {
34
- mockState.handlers = handlers;
35
- return {
36
- close: vi.fn(),
37
- };
38
- }
39
-
40
- publish = vi.fn(async () => {});
41
- }
42
-
43
- return {
44
- SimplePool: MockSimplePool,
45
- finalizeEvent: vi.fn((event: unknown) => event),
46
- getPublicKey: vi.fn(() => BOT_PUBKEY),
47
- verifyEvent: mockState.verifyEvent,
48
- nip19: {
49
- decode: vi.fn(),
50
- npubEncode: vi.fn((value: string) => `npub-${value}`),
51
- },
52
- };
53
- });
54
-
55
- vi.mock("nostr-tools/nip04", () => ({
56
- decrypt: mockState.decrypt,
57
- encrypt: vi.fn(() => "ciphertext"),
58
- }));
59
-
60
- vi.mock("./nostr-state-store.js", () => ({
61
- readNostrBusState: vi.fn(async () => null),
62
- writeNostrBusState: vi.fn(async () => {}),
63
- computeSinceTimestamp: vi.fn(() => 0),
64
- readNostrProfileState: vi.fn(async () => null),
65
- writeNostrProfileState: vi.fn(async () => {}),
66
- }));
67
-
68
- vi.mock("./nostr-profile.js", () => ({
69
- publishProfile: mockState.publishProfile,
70
- }));
71
-
72
- function createEvent(overrides: Record<string, unknown> = {}) {
73
- return {
74
- id: "event-1",
75
- kind: 4,
76
- pubkey: "a".repeat(64),
77
- content: "ciphertext",
78
- created_at: Math.floor(Date.now() / 1000),
79
- tags: [["p", BOT_PUBKEY]],
80
- ...overrides,
81
- };
82
- }
83
-
84
- async function emitEvent(event: Record<string, unknown>) {
85
- if (!mockState.handlers) {
86
- throw new Error("missing subscription handlers");
87
- }
88
- await mockState.handlers.onevent(event);
89
- }
90
-
91
- describe("startNostrBus inbound guards", () => {
92
- beforeEach(() => {
93
- mockState.handlers = null;
94
- mockState.verifyEvent.mockClear();
95
- mockState.verifyEvent.mockReturnValue(true);
96
- mockState.decrypt.mockClear();
97
- mockState.decrypt.mockReturnValue("plaintext");
98
- });
99
-
100
- afterEach(() => {
101
- mockState.handlers = null;
102
- });
103
-
104
- it("checks sender authorization after verify and before decrypt", async () => {
105
- const onMessage = vi.fn(async () => {});
106
- const authorizeSender = vi.fn(async () => "block" as const);
107
- const bus = await startNostrBus({
108
- privateKey: TEST_HEX_PRIVATE_KEY,
109
- onMessage,
110
- authorizeSender,
111
- onMetric: () => {},
112
- });
113
-
114
- await emitEvent(createEvent());
115
-
116
- expect(authorizeSender).toHaveBeenCalledTimes(1);
117
- expect(mockState.verifyEvent).toHaveBeenCalledTimes(1);
118
- expect(mockState.decrypt).not.toHaveBeenCalled();
119
- expect(onMessage).not.toHaveBeenCalled();
120
- expect(bus.getMetrics().eventsReceived).toBe(1);
121
-
122
- bus.close();
123
- });
124
-
125
- it("rejects invalid signatures before sender authorization", async () => {
126
- mockState.verifyEvent.mockReturnValueOnce(false);
127
- const onMessage = vi.fn(async () => {});
128
- const authorizeSender = vi.fn(async () => "allow" as const);
129
- const bus = await startNostrBus({
130
- privateKey: TEST_HEX_PRIVATE_KEY,
131
- onMessage,
132
- authorizeSender,
133
- onMetric: () => {},
134
- });
135
-
136
- await emitEvent(createEvent());
137
-
138
- expect(mockState.verifyEvent).toHaveBeenCalledTimes(1);
139
- expect(authorizeSender).not.toHaveBeenCalled();
140
- expect(mockState.decrypt).not.toHaveBeenCalled();
141
- expect(onMessage).not.toHaveBeenCalled();
142
- expect(bus.getMetrics().eventsRejected.invalidSignature).toBe(1);
143
-
144
- bus.close();
145
- });
146
-
147
- it("dedupes replayed invalid-signature events before verify fans out again", async () => {
148
- mockState.verifyEvent.mockReturnValue(false);
149
- const onMessage = vi.fn(async () => {});
150
- const authorizeSender = vi.fn(async () => "allow" as const);
151
- const bus = await startNostrBus({
152
- privateKey: TEST_HEX_PRIVATE_KEY,
153
- onMessage,
154
- authorizeSender,
155
- onMetric: () => {},
156
- });
157
-
158
- const invalidEvent = createEvent({ id: "invalid-replay" });
159
-
160
- await emitEvent(invalidEvent);
161
- await emitEvent(invalidEvent);
162
-
163
- expect(mockState.verifyEvent).toHaveBeenCalledTimes(1);
164
- expect(authorizeSender).not.toHaveBeenCalled();
165
- expect(mockState.decrypt).not.toHaveBeenCalled();
166
- expect(onMessage).not.toHaveBeenCalled();
167
- expect(bus.getMetrics().eventsRejected.invalidSignature).toBe(1);
168
- expect(bus.getMetrics().eventsDuplicate).toBe(1);
169
-
170
- bus.close();
171
- });
172
-
173
- it("dedupes replayed self-message events before other guards rerun", async () => {
174
- const onMessage = vi.fn(async () => {});
175
- const authorizeSender = vi.fn(async () => "allow" as const);
176
- const bus = await startNostrBus({
177
- privateKey: TEST_HEX_PRIVATE_KEY,
178
- onMessage,
179
- authorizeSender,
180
- onMetric: () => {},
181
- });
182
-
183
- const selfEvent = createEvent({
184
- id: "self-replay",
185
- pubkey: BOT_PUBKEY,
186
- });
187
-
188
- await emitEvent(selfEvent);
189
- await emitEvent(selfEvent);
190
-
191
- expect(mockState.verifyEvent).not.toHaveBeenCalled();
192
- expect(authorizeSender).not.toHaveBeenCalled();
193
- expect(mockState.decrypt).not.toHaveBeenCalled();
194
- expect(onMessage).not.toHaveBeenCalled();
195
- expect(bus.getMetrics().eventsDuplicate).toBe(1);
196
-
197
- bus.close();
198
- });
199
-
200
- it("rate limits repeated events before decrypt", async () => {
201
- const onMessage = vi.fn(async () => {});
202
- const bus = await startNostrBus({
203
- privateKey: TEST_HEX_PRIVATE_KEY,
204
- onMessage,
205
- onMetric: () => {},
206
- });
207
-
208
- for (let i = 0; i < 21; i += 1) {
209
- await emitEvent(
210
- createEvent({
211
- id: `event-${i}`,
212
- }),
213
- );
214
- }
215
-
216
- const snapshot = bus.getMetrics();
217
- expect(snapshot.eventsRejected.rateLimited).toBe(1);
218
- expect(mockState.decrypt).toHaveBeenCalledTimes(20);
219
- expect(onMessage).toHaveBeenCalledTimes(20);
220
-
221
- bus.close();
222
- });
223
-
224
- it("does not let a blocked sender starve a different verified sender", async () => {
225
- const onMessage = vi.fn(async () => {});
226
- const authorizeSender = vi.fn(async ({ senderPubkey }: { senderPubkey: string }) =>
227
- senderPubkey.startsWith("blocked") ? ("block" as const) : ("allow" as const),
228
- );
229
- const bus = await startNostrBus({
230
- privateKey: TEST_HEX_PRIVATE_KEY,
231
- onMessage,
232
- authorizeSender,
233
- onMetric: () => {},
234
- guardPolicy: {
235
- rateLimit: {
236
- windowMs: 60_000,
237
- maxGlobalPerWindow: 2,
238
- maxPerSenderPerWindow: 1,
239
- maxTrackedSenderKeys: 32,
240
- },
241
- },
242
- });
243
-
244
- await emitEvent(
245
- createEvent({
246
- id: "blocked-event",
247
- pubkey: `blocked${"a".repeat(57)}`,
248
- }),
249
- );
250
- await emitEvent(
251
- createEvent({
252
- id: "allowed-event",
253
- pubkey: `allowed${"b".repeat(57)}`,
254
- }),
255
- );
256
-
257
- expect(authorizeSender).toHaveBeenCalledTimes(2);
258
- expect(mockState.decrypt).toHaveBeenCalledTimes(1);
259
- expect(onMessage).toHaveBeenCalledTimes(1);
260
- expect(bus.getMetrics().eventsRejected.rateLimited).toBe(0);
261
-
262
- bus.close();
263
- });
264
-
265
- it("dedupes replayed verified events that authorization blocks", async () => {
266
- const onMessage = vi.fn(async () => {});
267
- const authorizeSender = vi.fn(async () => "block" as const);
268
- const bus = await startNostrBus({
269
- privateKey: TEST_HEX_PRIVATE_KEY,
270
- onMessage,
271
- authorizeSender,
272
- onMetric: () => {},
273
- });
274
-
275
- const blockedEvent = createEvent({
276
- id: "blocked-replay",
277
- pubkey: `blocked${"a".repeat(57)}`,
278
- });
279
-
280
- await emitEvent(blockedEvent);
281
- await emitEvent(blockedEvent);
282
-
283
- expect(mockState.verifyEvent).toHaveBeenCalledTimes(1);
284
- expect(authorizeSender).toHaveBeenCalledTimes(1);
285
- expect(mockState.decrypt).not.toHaveBeenCalled();
286
- expect(onMessage).not.toHaveBeenCalled();
287
-
288
- bus.close();
289
- });
290
-
291
- it("retries a replayed event after the message handler fails", async () => {
292
- const onMessage = vi
293
- .fn<(sender: string, plaintext: string) => Promise<void>>()
294
- .mockRejectedValueOnce(new Error("boom"))
295
- .mockResolvedValueOnce(undefined);
296
- const bus = await startNostrBus({
297
- privateKey: TEST_HEX_PRIVATE_KEY,
298
- onMessage,
299
- onMetric: () => {},
300
- });
301
-
302
- const event = createEvent({
303
- id: "retry-after-handler-failure",
304
- });
305
-
306
- await emitEvent(event);
307
- await emitEvent(event);
308
-
309
- expect(mockState.verifyEvent).toHaveBeenCalledTimes(2);
310
- expect(mockState.decrypt).toHaveBeenCalledTimes(2);
311
- expect(onMessage).toHaveBeenCalledTimes(2);
312
- expect(bus.getMetrics().eventsProcessed).toBe(1);
313
-
314
- bus.close();
315
- });
316
-
317
- it("does not rate limit an allowed sender while another authorization is still pending", async () => {
318
- const onMessage = vi.fn(async () => {});
319
- let resolveBlocked: ((value: "block") => void) | undefined;
320
- const blockedPromise = new Promise<"block">((resolve) => {
321
- resolveBlocked = resolve;
322
- });
323
- const authorizeSender = vi
324
- .fn<(params: { senderPubkey: string }) => Promise<"allow" | "block" | "pairing">>()
325
- .mockImplementationOnce(async () => await blockedPromise)
326
- .mockResolvedValueOnce("allow");
327
- const bus = await startNostrBus({
328
- privateKey: TEST_HEX_PRIVATE_KEY,
329
- onMessage,
330
- authorizeSender,
331
- onMetric: () => {},
332
- guardPolicy: {
333
- rateLimit: {
334
- windowMs: 60_000,
335
- maxGlobalPerWindow: 2,
336
- maxPerSenderPerWindow: 1,
337
- maxTrackedSenderKeys: 32,
338
- },
339
- },
340
- });
341
-
342
- const blockedEventPromise = emitEvent(
343
- createEvent({
344
- id: "blocked-pending",
345
- pubkey: `blocked${"a".repeat(57)}`,
346
- }),
347
- );
348
- await emitEvent(
349
- createEvent({
350
- id: "allowed-during-pending-auth",
351
- pubkey: `allowed${"b".repeat(57)}`,
352
- }),
353
- );
354
- resolveBlocked?.("block");
355
- await blockedEventPromise;
356
-
357
- expect(authorizeSender).toHaveBeenCalledTimes(2);
358
- expect(mockState.decrypt).toHaveBeenCalledTimes(1);
359
- expect(onMessage).toHaveBeenCalledTimes(1);
360
- expect(bus.getMetrics().eventsRejected.rateLimited).toBe(0);
361
-
362
- bus.close();
363
- });
364
-
365
- it("rate limits repeated invalid signatures before authorization work fans out", async () => {
366
- mockState.verifyEvent.mockReturnValue(false);
367
- const onMessage = vi.fn(async () => {});
368
- const authorizeSender = vi.fn(async () => "allow" as const);
369
- const bus = await startNostrBus({
370
- privateKey: TEST_HEX_PRIVATE_KEY,
371
- onMessage,
372
- authorizeSender,
373
- onMetric: () => {},
374
- guardPolicy: {
375
- rateLimit: {
376
- windowMs: 60_000,
377
- maxGlobalPerWindow: 1,
378
- maxPerSenderPerWindow: 10,
379
- maxTrackedSenderKeys: 32,
380
- },
381
- },
382
- });
383
-
384
- await emitEvent(createEvent({ id: "invalid-1" }));
385
- await emitEvent(createEvent({ id: "invalid-2" }));
386
-
387
- expect(mockState.verifyEvent).toHaveBeenCalledTimes(1);
388
- expect(authorizeSender).not.toHaveBeenCalled();
389
- expect(bus.getMetrics().eventsRejected.invalidSignature).toBe(1);
390
- expect(bus.getMetrics().eventsRejected.rateLimited).toBe(1);
391
-
392
- bus.close();
393
- });
394
-
395
- it("counts oversized ciphertext toward the global inbound rate limit", async () => {
396
- const onMessage = vi.fn(async () => {});
397
- const bus = await startNostrBus({
398
- privateKey: TEST_HEX_PRIVATE_KEY,
399
- onMessage,
400
- onMetric: () => {},
401
- guardPolicy: {
402
- maxCiphertextBytes: 4,
403
- rateLimit: {
404
- windowMs: 60_000,
405
- maxGlobalPerWindow: 1,
406
- maxPerSenderPerWindow: 10,
407
- maxTrackedSenderKeys: 32,
408
- },
409
- },
410
- });
411
-
412
- await emitEvent(
413
- createEvent({
414
- id: "oversized-global-1",
415
- pubkey: `sender1${"a".repeat(57)}`,
416
- content: "ciphertext-too-large",
417
- }),
418
- );
419
- await emitEvent(
420
- createEvent({
421
- id: "oversized-global-2",
422
- pubkey: `sender2${"b".repeat(57)}`,
423
- content: "ciphertext-too-large",
424
- }),
425
- );
426
-
427
- expect(bus.getMetrics().eventsRejected.oversizedCiphertext).toBe(1);
428
- expect(bus.getMetrics().eventsRejected.rateLimited).toBe(1);
429
- expect(mockState.verifyEvent).not.toHaveBeenCalled();
430
- expect(mockState.decrypt).not.toHaveBeenCalled();
431
- expect(onMessage).not.toHaveBeenCalled();
432
-
433
- bus.close();
434
- });
435
-
436
- it("does not spend per-sender buckets on oversized ciphertext before verification", async () => {
437
- const onMessage = vi.fn(async () => {});
438
- const bus = await startNostrBus({
439
- privateKey: TEST_HEX_PRIVATE_KEY,
440
- onMessage,
441
- onMetric: () => {},
442
- guardPolicy: {
443
- maxCiphertextBytes: 4,
444
- rateLimit: {
445
- windowMs: 60_000,
446
- maxGlobalPerWindow: 10,
447
- maxPerSenderPerWindow: 1,
448
- maxTrackedSenderKeys: 32,
449
- },
450
- },
451
- });
452
-
453
- await emitEvent(
454
- createEvent({
455
- id: "oversized-sender-1",
456
- content: "ciphertext-too-large",
457
- }),
458
- );
459
- await emitEvent(
460
- createEvent({
461
- id: "oversized-sender-2",
462
- content: "ciphertext-too-large",
463
- }),
464
- );
465
- await emitEvent(
466
- createEvent({
467
- id: "allowed-after-oversized",
468
- content: "ok",
469
- }),
470
- );
471
-
472
- expect(bus.getMetrics().eventsRejected.oversizedCiphertext).toBe(2);
473
- expect(bus.getMetrics().eventsRejected.rateLimited).toBe(0);
474
- expect(mockState.verifyEvent).toHaveBeenCalledTimes(1);
475
- expect(mockState.decrypt).toHaveBeenCalledTimes(1);
476
- expect(onMessage).toHaveBeenCalledTimes(1);
477
-
478
- bus.close();
479
- });
480
-
481
- it("rejects far-future events before crypto", async () => {
482
- const onMessage = vi.fn(async () => {});
483
- const bus = await startNostrBus({
484
- privateKey: TEST_HEX_PRIVATE_KEY,
485
- onMessage,
486
- onMetric: () => {},
487
- });
488
-
489
- await emitEvent(
490
- createEvent({
491
- created_at: Math.floor(Date.now() / 1000) + 600,
492
- }),
493
- );
494
-
495
- const snapshot = bus.getMetrics();
496
- expect(snapshot.eventsRejected.future).toBe(1);
497
- expect(mockState.verifyEvent).not.toHaveBeenCalled();
498
- expect(mockState.decrypt).not.toHaveBeenCalled();
499
- expect(onMessage).not.toHaveBeenCalled();
500
-
501
- bus.close();
502
- });
503
-
504
- it("rejects oversized ciphertext before verify/decrypt", async () => {
505
- const onMessage = vi.fn(async () => {});
506
- const bus = await startNostrBus({
507
- privateKey: TEST_HEX_PRIVATE_KEY,
508
- onMessage,
509
- onMetric: () => {},
510
- });
511
-
512
- await emitEvent(
513
- createEvent({
514
- content: "x".repeat(20_000),
515
- }),
516
- );
517
-
518
- const snapshot = bus.getMetrics();
519
- expect(snapshot.eventsRejected.oversizedCiphertext).toBe(1);
520
- expect(mockState.verifyEvent).not.toHaveBeenCalled();
521
- expect(mockState.decrypt).not.toHaveBeenCalled();
522
- expect(onMessage).not.toHaveBeenCalled();
523
-
524
- bus.close();
525
- });
526
- });