@fedify/fedify 2.2.18-dev.2386 → 2.2.19

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 (65) hide show
  1. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-WyYSTDDX.mjs} +2 -20
  2. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-D4DNWrux.mjs} +19 -1
  3. package/dist/{builder-CiKYvR0I.mjs → builder-UmydCxtK.mjs} +2 -2
  4. package/dist/compat/public-audience.test.mjs +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{deno-CkabM5bf.mjs → deno-CD-8UlUi.mjs} +1 -1
  7. package/dist/{docloader-B4Hk3LSj.mjs → docloader-D1ghurDJ.mjs} +6 -2
  8. package/dist/federation/builder.test.mjs +3 -3
  9. package/dist/federation/collection.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +261 -8
  11. package/dist/federation/idempotency.test.mjs +3 -3
  12. package/dist/federation/inbox.test.mjs +25 -1
  13. package/dist/federation/keycache.test.mjs +18 -7
  14. package/dist/federation/kv.test.mjs +1 -1
  15. package/dist/federation/middleware.test.mjs +128 -10
  16. package/dist/federation/mod.cjs +1 -1
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/mq.test.mjs +2 -2
  19. package/dist/federation/negotiation.test.mjs +3 -3
  20. package/dist/federation/retry.test.mjs +2 -2
  21. package/dist/federation/router.test.mjs +2 -3
  22. package/dist/federation/send.test.mjs +7 -6
  23. package/dist/federation/temporal.test.mjs +3 -3
  24. package/dist/federation/webfinger.test.mjs +2 -2
  25. package/dist/{http-DovhI1VM.mjs → http-BBBUbwKh.mjs} +2 -2
  26. package/dist/{http-DP-vbG4o.js → http-DJK2udgX.js} +17 -3
  27. package/dist/{http-C_Qq0cNx.cjs → http-DrUG4fPS.cjs} +17 -3
  28. package/dist/inbox-Dca_0fwc.mjs +162 -0
  29. package/dist/{key-BtVvc9pe.mjs → key-DolXUaLH.mjs} +17 -3
  30. package/dist/{keycache-Dkc_sflV.mjs → keycache-BimEQucx.mjs} +1 -1
  31. package/dist/{kv-cache-Ds_adaor.cjs → kv-cache-Bk3V1Tiu.cjs} +5 -1
  32. package/dist/{kv-cache-D8yJIXRo.js → kv-cache-DDm80t1K.js} +5 -1
  33. package/dist/{ld-1J9cdTp0.mjs → ld-rTdIfZ63.mjs} +2 -2
  34. package/dist/{middleware-B_M6lcay.cjs → middleware-B4-Rzdi3.cjs} +1 -1
  35. package/dist/{middleware-BWXhlsuq.cjs → middleware-BWOwEGpy.cjs} +97 -27
  36. package/dist/{middleware-BrFMyQSp.js → middleware-BimXKBcP.js} +96 -26
  37. package/dist/{middleware-ruFPV2xR.mjs → middleware-CpXoRHs3.mjs} +85 -168
  38. package/dist/{middleware-DMkt5ShX.mjs → middleware-DYK_res8.mjs} +1 -1
  39. package/dist/mod.cjs +4 -4
  40. package/dist/mod.js +4 -4
  41. package/dist/nodeinfo/client.test.mjs +1 -1
  42. package/dist/nodeinfo/handler.test.mjs +2 -2
  43. package/dist/nodeinfo/types.test.mjs +2 -2
  44. package/dist/otel/exporter.test.mjs +1 -1
  45. package/dist/{owner-CgurOfJ9.mjs → owner-CVfzyM5V.mjs} +2 -2
  46. package/dist/{proof-BJ332wO_.js → proof-CaQ4KG6y.js} +1 -1
  47. package/dist/{proof-C6uAR-Hj.cjs → proof-DhaTTn-P.cjs} +1 -1
  48. package/dist/{proof-Dn4nm7pr.mjs → proof-gOGM-sJS.mjs} +2 -2
  49. package/dist/{send-iVwnK504.mjs → send-D35Va72B.mjs} +2 -2
  50. package/dist/sig/http.test.mjs +6 -6
  51. package/dist/sig/key.test.mjs +199 -5
  52. package/dist/sig/ld.test.mjs +49 -4
  53. package/dist/sig/mod.cjs +2 -2
  54. package/dist/sig/mod.js +2 -2
  55. package/dist/sig/owner.test.mjs +48 -3
  56. package/dist/sig/proof.test.mjs +55 -3
  57. package/dist/{std__assert-BVFj8-_V.mjs → std__assert-DNZ4Liil.mjs} +2 -2
  58. package/dist/{temporal-BZrzDMq2.mjs → temporal-CA4Y9hBJ.mjs} +1 -1
  59. package/dist/utils/docloader.test.mjs +178 -39
  60. package/dist/utils/mod.cjs +1 -1
  61. package/dist/utils/mod.js +1 -1
  62. package/package.json +5 -5
  63. /package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-BkxW5QJI.mjs} +0 -0
  64. /package/dist/{negotiation-BHtZN6Y1.mjs → negotiation-SQvQgUqe.mjs} +0 -0
  65. /package/dist/{retry-bMXBL97A.mjs → retry-v_sGLH1d.mjs} +0 -0
@@ -4,20 +4,20 @@ globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
5
  import { n as RouterError } from "../router-CrMLXoOr.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
7
- import "../std__assert-BVFj8-_V.mjs";
8
- import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
7
+ import "../std__assert-DNZ4Liil.mjs";
8
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
9
9
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
10
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
11
- import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
10
+ import { t as assertNotEquals } from "../assert_not_equals-BkxW5QJI.mjs";
11
+ import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
12
12
  import { t as assertStrictEquals } from "../assert_strict_equals-Dmjbg-bA.mjs";
13
- import { l as verifyRequest, s as signRequest } from "../http-DovhI1VM.mjs";
13
+ import { l as verifyRequest, s as signRequest } from "../http-BBBUbwKh.mjs";
14
14
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
15
- import { t as getAuthenticatedDocumentLoader } from "../docloader-B4Hk3LSj.mjs";
15
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-D1ghurDJ.mjs";
16
16
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
17
- import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-1J9cdTp0.mjs";
18
- import { t as doesActorOwnKey } from "../owner-CgurOfJ9.mjs";
19
- import { i as verifyObject, r as signObject } from "../proof-Dn4nm7pr.mjs";
20
- import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-ruFPV2xR.mjs";
17
+ import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-rTdIfZ63.mjs";
18
+ import { t as doesActorOwnKey } from "../owner-CVfzyM5V.mjs";
19
+ import { i as verifyObject, r as signObject } from "../proof-gOGM-sJS.mjs";
20
+ import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-CpXoRHs3.mjs";
21
21
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
22
22
  import * as vocab from "@fedify/vocab";
23
23
  import { Create, Offer, Person, getTypeId, lookupObject } from "@fedify/vocab";
@@ -4547,6 +4547,124 @@ test("ContextImpl.routeActivity() marks queued signed activities as non-LDS", as
4547
4547
  assertEquals(inboxMessage.ldSignatureVerified, false);
4548
4548
  assertEquals(inboxMessage.normalizedActivity, void 0);
4549
4549
  });
4550
+ for (const queued of [false, true]) test(`ContextImpl.routeActivity() rejects a proof-signed cross-origin ID (queued: ${queued})`, async () => {
4551
+ const messages = [];
4552
+ const queue = {
4553
+ enqueue(message) {
4554
+ messages.push(message);
4555
+ return Promise.resolve();
4556
+ },
4557
+ async listen() {}
4558
+ };
4559
+ const federation = createFederation({
4560
+ kv: new MemoryKvStore(),
4561
+ documentLoaderFactory: () => mockDocumentLoader,
4562
+ contextLoaderFactory: () => mockDocumentLoader,
4563
+ queue: queued ? {
4564
+ inbox: queue,
4565
+ outbox: queue
4566
+ } : void 0,
4567
+ manuallyStartQueue: true
4568
+ });
4569
+ let listenerCalls = 0;
4570
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
4571
+ listenerCalls++;
4572
+ });
4573
+ const signed = await signObject(new Create({
4574
+ id: new URL("https://victim.example/activities/1"),
4575
+ actor: ed25519Multikey.controllerId
4576
+ }), ed25519PrivateKey, ed25519Multikey.id, {
4577
+ contextLoader: mockDocumentLoader,
4578
+ documentLoader: mockDocumentLoader
4579
+ });
4580
+ assertFalse(await federation.createContext(new URL("https://local.example/")).routeActivity(null, signed));
4581
+ assertEquals(listenerCalls, 0);
4582
+ assertEquals(messages, []);
4583
+ });
4584
+ test("ContextImpl.routeActivity() rejects a missing actor before routing", async () => {
4585
+ const federation = createFederation({
4586
+ kv: new MemoryKvStore(),
4587
+ documentLoaderFactory: () => mockDocumentLoader,
4588
+ contextLoaderFactory: () => mockDocumentLoader
4589
+ });
4590
+ let listenerCalls = 0;
4591
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
4592
+ listenerCalls++;
4593
+ });
4594
+ assertFalse(await federation.createContext(new URL("https://local.example/")).routeActivity(null, new Create({ id: new URL("https://remote.example/activities/1") })));
4595
+ assertEquals(listenerCalls, 0);
4596
+ });
4597
+ test("FederationImpl.processQueuedTask() rejects untrusted activity origins before KV access", async () => {
4598
+ class TrackingKvStore extends MemoryKvStore {
4599
+ reads = 0;
4600
+ writes = 0;
4601
+ get(key) {
4602
+ this.reads++;
4603
+ return super.get(key);
4604
+ }
4605
+ set(key, value, options) {
4606
+ this.writes++;
4607
+ return super.set(key, value, options);
4608
+ }
4609
+ }
4610
+ for (const activity of [{
4611
+ "@context": "https://www.w3.org/ns/activitystreams",
4612
+ id: "https://victim.example/activities/1",
4613
+ type: "Create",
4614
+ actor: "https://attacker.example/actor"
4615
+ }, {
4616
+ "@context": "https://www.w3.org/ns/activitystreams",
4617
+ id: "https://victim.example/activities/2",
4618
+ type: "Create"
4619
+ }]) {
4620
+ const kv = new TrackingKvStore();
4621
+ const federation = new FederationImpl({ kv });
4622
+ let listenerCalls = 0;
4623
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
4624
+ listenerCalls++;
4625
+ });
4626
+ await federation.processQueuedTask(void 0, {
4627
+ type: "inbox",
4628
+ id: crypto.randomUUID(),
4629
+ baseUrl: "https://local.example",
4630
+ activity,
4631
+ started: (/* @__PURE__ */ new Date()).toISOString(),
4632
+ attempt: 0,
4633
+ identifier: null,
4634
+ traceContext: {}
4635
+ });
4636
+ assertEquals(listenerCalls, 0);
4637
+ assertEquals(kv.reads, 0);
4638
+ assertEquals(kv.writes, 0);
4639
+ }
4640
+ const kv = new TrackingKvStore();
4641
+ const federation = new FederationImpl({
4642
+ kv,
4643
+ skipSignatureVerification: true
4644
+ });
4645
+ let listenerCalls = 0;
4646
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
4647
+ listenerCalls++;
4648
+ });
4649
+ await federation.processQueuedTask(void 0, {
4650
+ type: "inbox",
4651
+ id: crypto.randomUUID(),
4652
+ baseUrl: "https://local.example",
4653
+ activity: {
4654
+ "@context": "https://www.w3.org/ns/activitystreams",
4655
+ id: "https://victim.example/activities/opt-out",
4656
+ type: "Create",
4657
+ actor: "https://attacker.example/actor"
4658
+ },
4659
+ started: (/* @__PURE__ */ new Date()).toISOString(),
4660
+ attempt: 0,
4661
+ identifier: null,
4662
+ traceContext: {}
4663
+ });
4664
+ assertEquals(listenerCalls, 1);
4665
+ assertEquals(kv.reads, 1);
4666
+ assertEquals(kv.writes, 1);
4667
+ });
4550
4668
  test("ContextImpl.getCollectionUri()", () => {
4551
4669
  const federation = new FederationImpl({ kv: new MemoryKvStore() });
4552
4670
  const base = "https://example.com";
@@ -2,7 +2,7 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("../chunk-DDcVe30Y.cjs");
5
- const require_middleware = require("../middleware-BWXhlsuq.cjs");
5
+ const require_middleware = require("../middleware-BWOwEGpy.cjs");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  //#region src/federation/kv.ts
8
8
  /**
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-BrFMyQSp.js";
3
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-BimXKBcP.js";
4
4
  import { isEqual } from "es-toolkit";
5
5
  //#region src/federation/kv.ts
6
6
  /**
@@ -2,8 +2,8 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import { n as assertGreaterOrEqual } from "../std__assert-BVFj8-_V.mjs";
6
- import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
5
+ import { n as assertGreaterOrEqual } from "../std__assert-DNZ4Liil.mjs";
6
+ import { r as assertFalse } from "../assert_throws-D4DNWrux.mjs";
7
7
  import { t as assert } from "../assert-BhQd8QHP.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  import { delay } from "es-toolkit";
@@ -1,10 +1,10 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import "../std__assert-BVFj8-_V.mjs";
5
- import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
4
+ import "../std__assert-DNZ4Liil.mjs";
5
+ import { r as assertFalse } from "../assert_throws-D4DNWrux.mjs";
6
6
  import { t as assert } from "../assert-BhQd8QHP.mjs";
7
- import { t as acceptsJsonLd } from "../negotiation-BHtZN6Y1.mjs";
7
+ import { t as acceptsJsonLd } from "../negotiation-SQvQgUqe.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/negotiation.test.ts
10
10
  test("acceptsJsonLd()", () => {
@@ -2,8 +2,8 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { l as AssertionError, t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
6
- import { t as createExponentialBackoffPolicy } from "../retry-bMXBL97A.mjs";
5
+ import { t as assertNotEquals } from "../assert_not_equals-BkxW5QJI.mjs";
6
+ import { t as createExponentialBackoffPolicy } from "../retry-v_sGLH1d.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  //#region src/federation/retry.test.ts
9
9
  test("createExponentialBackoffPolicy()", () => {
@@ -3,10 +3,9 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError, t as Router } from "../router-CrMLXoOr.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-BVFj8-_V.mjs";
7
- import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
6
+ import "../std__assert-DNZ4Liil.mjs";
7
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
8
8
  import { t as assert } from "../assert-BhQd8QHP.mjs";
9
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
10
9
  import { test } from "@fedify/fixture";
11
10
  //#region src/federation/router.test.ts
12
11
  function setUp(options = {}) {
@@ -3,14 +3,15 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-BVFj8-_V.mjs";
7
- import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
6
+ import "../std__assert-DNZ4Liil.mjs";
7
+ import { r as assertFalse } from "../assert_throws-D4DNWrux.mjs";
8
8
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
9
- import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
10
- import { l as verifyRequest } from "../http-DovhI1VM.mjs";
9
+ import { t as assertNotEquals } from "../assert_not_equals-BkxW5QJI.mjs";
10
+ import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
11
+ import { l as verifyRequest } from "../http-BBBUbwKh.mjs";
11
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
12
- import { t as doesActorOwnKey } from "../owner-CgurOfJ9.mjs";
13
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-iVwnK504.mjs";
13
+ import { t as doesActorOwnKey } from "../owner-CVfzyM5V.mjs";
14
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-D35Va72B.mjs";
14
15
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
15
16
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
16
17
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -1,10 +1,10 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import "../std__assert-BVFj8-_V.mjs";
5
- import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
4
+ import "../std__assert-DNZ4Liil.mjs";
5
+ import { r as assertFalse } from "../assert_throws-D4DNWrux.mjs";
6
6
  import { t as assert } from "../assert-BhQd8QHP.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-BZrzDMq2.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-CA4Y9hBJ.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/temporal.test.ts
10
10
  test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
@@ -3,9 +3,9 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { r as createRequestContext } from "../context-Dk_tacqz.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-BVFj8-_V.mjs";
6
+ import "../std__assert-DNZ4Liil.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
8
- import { o as createFederation, s as handleWebFinger } from "../middleware-ruFPV2xR.mjs";
8
+ import { o as createFederation, s as handleWebFinger } from "../middleware-CpXoRHs3.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  //#region src/federation/webfinger.test.ts
@@ -1,9 +1,9 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CkabM5bf.mjs";
4
+ import { n as version, t as name } from "./deno-CD-8UlUi.mjs";
5
5
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-Dd__NiUL.mjs";
6
- import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-BtVvc9pe.mjs";
6
+ import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-DolXUaLH.mjs";
7
7
  import { CryptographicKey } from "@fedify/vocab";
8
8
  import { SpanStatusCode, trace } from "@opentelemetry/api";
9
9
  import { FetchError } from "@fedify/vocab-runtime";
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.2.18-dev.2386+41abb5f5";
13
+ var version = "2.2.19";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -291,16 +291,23 @@ async function importJwk(jwk, type) {
291
291
  /**
292
292
  * Fetches the document that the given actor URI dereferences to, and returns
293
293
  * the actor only if the host that served the document is authoritative for
294
- * the id the document claims.
294
+ * the id the document claims, and that id is the actor that was asked for.
295
295
  *
296
296
  * Only the origin that serves an actor id can speak for it. Without that
297
297
  * rule any host could serve a document describing somebody else's actor—and
298
298
  * listing its own keys as that actor's.
299
299
  *
300
+ * The returned actor always has exactly the requested `actorId` as its `id`.
301
+ * A document loader follows redirects and alternate document links, so the
302
+ * document an actor id leads to may describe a different actor altogether,
303
+ * on the same origin or on another; accepting it would attribute that other
304
+ * actor's keys to the requested one. See GHSA-8863-8468-7hq7.
305
+ *
300
306
  * @param actorId The URI of the actor to fetch.
301
307
  * @param options Options for fetching the document.
302
308
  * @returns The actor, or `null` if the document cannot be fetched, is not an
303
- * actor, or belongs to another origin.
309
+ * actor, belongs to another origin, or describes an actor other
310
+ * than the requested one.
304
311
  * @internal
305
312
  */
306
313
  async function fetchActorDocument(actorId, options = {}) {
@@ -355,6 +362,13 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
355
362
  });
356
363
  return null;
357
364
  }
365
+ if (object.id.href !== actorId.href) {
366
+ logger.debug("The document fetched for the actor {actorId} is the actor {documentActorId} instead; refusing to treat it as the requested actor's document.", {
367
+ actorId: actorId.href,
368
+ documentActorId: object.id.href
369
+ });
370
+ return null;
371
+ }
358
372
  return object;
359
373
  }
360
374
  /**
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
11
11
  let byte_encodings_base64 = require("byte-encodings/base64");
12
12
  //#region deno.json
13
13
  var name = "@fedify/fedify";
14
- var version = "2.2.18-dev.2386+41abb5f5";
14
+ var version = "2.2.19";
15
15
  //#endregion
16
16
  //#region src/sig/accept.ts
17
17
  /**
@@ -292,16 +292,23 @@ async function importJwk(jwk, type) {
292
292
  /**
293
293
  * Fetches the document that the given actor URI dereferences to, and returns
294
294
  * the actor only if the host that served the document is authoritative for
295
- * the id the document claims.
295
+ * the id the document claims, and that id is the actor that was asked for.
296
296
  *
297
297
  * Only the origin that serves an actor id can speak for it. Without that
298
298
  * rule any host could serve a document describing somebody else's actor—and
299
299
  * listing its own keys as that actor's.
300
300
  *
301
+ * The returned actor always has exactly the requested `actorId` as its `id`.
302
+ * A document loader follows redirects and alternate document links, so the
303
+ * document an actor id leads to may describe a different actor altogether,
304
+ * on the same origin or on another; accepting it would attribute that other
305
+ * actor's keys to the requested one. See GHSA-8863-8468-7hq7.
306
+ *
301
307
  * @param actorId The URI of the actor to fetch.
302
308
  * @param options Options for fetching the document.
303
309
  * @returns The actor, or `null` if the document cannot be fetched, is not an
304
- * actor, or belongs to another origin.
310
+ * actor, belongs to another origin, or describes an actor other
311
+ * than the requested one.
305
312
  * @internal
306
313
  */
307
314
  async function fetchActorDocument(actorId, options = {}) {
@@ -356,6 +363,13 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
356
363
  });
357
364
  return null;
358
365
  }
366
+ if (object.id.href !== actorId.href) {
367
+ logger.debug("The document fetched for the actor {actorId} is the actor {documentActorId} instead; refusing to treat it as the requested actor's document.", {
368
+ actorId: actorId.href,
369
+ documentActorId: object.id.href
370
+ });
371
+ return null;
372
+ }
359
373
  return object;
360
374
  }
361
375
  /**
@@ -0,0 +1,162 @@
1
+ import { Temporal } from "@js-temporal/polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { n as version, t as name } from "./deno-CD-8UlUi.mjs";
5
+ import { getTypeId } from "@fedify/vocab";
6
+ import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
7
+ import { getLogger } from "@logtape/logtape";
8
+ //#region src/federation/inbox.ts
9
+ /**
10
+ * Checks whether an activity's ID and all of its actor IDs have the same
11
+ * non-opaque origin. Activities without an ID have no origin to authenticate.
12
+ *
13
+ * The caller is responsible for rejecting a missing primary actor ID before
14
+ * relying on this check.
15
+ * @internal
16
+ */
17
+ function actorsHaveSameOrigin(activity) {
18
+ if (activity.id == null) return true;
19
+ const origin = activity.id.origin;
20
+ return origin !== "null" && activity.actorIds.every((actor) => actor.origin === origin);
21
+ }
22
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
23
+ const logger = getLogger([
24
+ "fedify",
25
+ "federation",
26
+ "inbox"
27
+ ]);
28
+ if (activity.actorId == null) {
29
+ logger.error("Missing actor.", { activity: json });
30
+ span.setStatus({
31
+ code: SpanStatusCode.ERROR,
32
+ message: "Missing actor."
33
+ });
34
+ return "missingActor";
35
+ }
36
+ let cacheKey = null;
37
+ if (activity.id != null) {
38
+ const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
39
+ const strategy = idempotencyStrategy ?? "per-inbox";
40
+ let keyString;
41
+ if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
42
+ else switch (strategy) {
43
+ case "global":
44
+ keyString = activity.id.href;
45
+ break;
46
+ case "per-origin":
47
+ keyString = `${ctx.origin}\n${activity.id.href}`;
48
+ break;
49
+ case "per-inbox":
50
+ keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
51
+ break;
52
+ default: keyString = `${ctx.origin}\n${activity.id.href}`;
53
+ }
54
+ if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
55
+ }
56
+ if (cacheKey != null) {
57
+ if (await kv.get(cacheKey) === true) {
58
+ logger.debug("Activity {activityId} has already been processed.", {
59
+ activityId: activity.id?.href,
60
+ activity: json,
61
+ recipient
62
+ });
63
+ span.setStatus({
64
+ code: SpanStatusCode.UNSET,
65
+ message: `Activity ${activity.id?.href} has already been processed.`
66
+ });
67
+ return "alreadyProcessed";
68
+ }
69
+ }
70
+ span.setAttribute("activitypub.actor.id", activity.actorId.href);
71
+ if (queue != null) {
72
+ const carrier = {};
73
+ propagation.inject(context.active(), carrier);
74
+ try {
75
+ await queue.enqueue({
76
+ type: "inbox",
77
+ id: crypto.randomUUID(),
78
+ baseUrl: ctx.origin,
79
+ activity: originalJson ?? json,
80
+ ...normalizedActivity == null ? {} : { normalizedActivity },
81
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
82
+ identifier: recipient,
83
+ attempt: 0,
84
+ started: (/* @__PURE__ */ new Date()).toISOString(),
85
+ traceContext: carrier
86
+ });
87
+ } catch (error) {
88
+ logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
89
+ error,
90
+ activityId: activity.id?.href,
91
+ activity: json,
92
+ recipient
93
+ });
94
+ span.setStatus({
95
+ code: SpanStatusCode.ERROR,
96
+ message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
97
+ });
98
+ throw error;
99
+ }
100
+ logger.info("Activity {activityId} is enqueued.", {
101
+ activityId: activity.id?.href,
102
+ activity: json,
103
+ recipient
104
+ });
105
+ return "enqueued";
106
+ }
107
+ tracerProvider = tracerProvider ?? trace.getTracerProvider();
108
+ return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
109
+ const dispatched = inboxListeners?.dispatchWithClass(activity);
110
+ if (dispatched == null) {
111
+ logger.error("Unsupported activity type:\n{activity}", {
112
+ activity: json,
113
+ recipient
114
+ });
115
+ span.setStatus({
116
+ code: SpanStatusCode.UNSET,
117
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
118
+ });
119
+ span.end();
120
+ return "unsupportedActivity";
121
+ }
122
+ const { class: cls, listener } = dispatched;
123
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
124
+ try {
125
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
126
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, getTypeId(activity).href), activity);
127
+ } catch (error) {
128
+ try {
129
+ await inboxErrorHandler?.(ctx, error);
130
+ } catch (error) {
131
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
132
+ error,
133
+ activityId: activity.id?.href,
134
+ activity: json,
135
+ recipient
136
+ });
137
+ }
138
+ logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
139
+ error,
140
+ activityId: activity.id?.href,
141
+ activity: json,
142
+ recipient
143
+ });
144
+ span.setStatus({
145
+ code: SpanStatusCode.ERROR,
146
+ message: String(error)
147
+ });
148
+ span.end();
149
+ return "error";
150
+ }
151
+ if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
152
+ logger.info("Activity {activityId} has been processed.", {
153
+ activityId: activity.id?.href,
154
+ activity: json,
155
+ recipient
156
+ });
157
+ span.end();
158
+ return "success";
159
+ });
160
+ }
161
+ //#endregion
162
+ export { routeActivity as n, actorsHaveSameOrigin as t };
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CkabM5bf.mjs";
4
+ import { n as version, t as name } from "./deno-CD-8UlUi.mjs";
5
5
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -146,16 +146,23 @@ async function importJwk(jwk, type) {
146
146
  /**
147
147
  * Fetches the document that the given actor URI dereferences to, and returns
148
148
  * the actor only if the host that served the document is authoritative for
149
- * the id the document claims.
149
+ * the id the document claims, and that id is the actor that was asked for.
150
150
  *
151
151
  * Only the origin that serves an actor id can speak for it. Without that
152
152
  * rule any host could serve a document describing somebody else's actor—and
153
153
  * listing its own keys as that actor's.
154
154
  *
155
+ * The returned actor always has exactly the requested `actorId` as its `id`.
156
+ * A document loader follows redirects and alternate document links, so the
157
+ * document an actor id leads to may describe a different actor altogether,
158
+ * on the same origin or on another; accepting it would attribute that other
159
+ * actor's keys to the requested one. See GHSA-8863-8468-7hq7.
160
+ *
155
161
  * @param actorId The URI of the actor to fetch.
156
162
  * @param options Options for fetching the document.
157
163
  * @returns The actor, or `null` if the document cannot be fetched, is not an
158
- * actor, or belongs to another origin.
164
+ * actor, belongs to another origin, or describes an actor other
165
+ * than the requested one.
159
166
  * @internal
160
167
  */
161
168
  async function fetchActorDocument(actorId, options = {}) {
@@ -210,6 +217,13 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
210
217
  });
211
218
  return null;
212
219
  }
220
+ if (object.id.href !== actorId.href) {
221
+ logger.debug("The document fetched for the actor {actorId} is the actor {documentActorId} instead; refusing to treat it as the requested actor's document.", {
222
+ actorId: actorId.href,
223
+ documentActorId: object.id.href
224
+ });
225
+ return null;
226
+ }
213
227
  return object;
214
228
  }
215
229
  /**
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { CryptographicKey, Multikey } from "@fedify/vocab";
5
5
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
6
6
  //#region src/federation/keycache.ts
7
- const KEY_CACHE_GENERATION = "2";
7
+ const KEY_CACHE_GENERATION = "3";
8
8
  var KvKeyCache = class {
9
9
  kv;
10
10
  prefix;
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-C_Qq0cNx.cjs");
4
+ const require_http = require("./http-DrUG4fPS.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
@@ -58,6 +58,10 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
58
58
  });
59
59
  }
60
60
  async function validateUrl(url) {
61
+ const parsedUrl = new URL(url);
62
+ const protocol = parsedUrl.protocol;
63
+ if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
64
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new _fedify_vocab_runtime.UrlError("URLs with credentials are not allowed");
61
65
  if (!allowPrivateAddress) try {
62
66
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
63
67
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { f as validateCryptoKey, t as doubleKnock } from "./http-DP-vbG4o.js";
3
+ import { f as validateCryptoKey, t as doubleKnock } from "./http-DJK2udgX.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
6
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -57,6 +57,10 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
57
57
  });
58
58
  }
59
59
  async function validateUrl(url) {
60
+ const parsedUrl = new URL(url);
61
+ const protocol = parsedUrl.protocol;
62
+ if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
63
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
60
64
  if (!allowPrivateAddress) try {
61
65
  await validatePublicUrl(url);
62
66
  } catch (error) {