@fedify/fedify 2.5.0-dev.2370 → 2.5.0-dev.2420

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 (93) hide show
  1. package/dist/{builder-DzGfERAV.mjs → builder-Cwc3Eu59.mjs} +2 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-hnZ7MNwN.mjs → compound-proof-Dtwrhs1I.mjs} +2 -2
  6. package/dist/{context-C2-UDo2c.d.ts → context-2wLkQrjb.d.ts} +11 -0
  7. package/dist/{context-BjD_CTxh.d.cts → context-DPocjp1l.d.cts} +11 -0
  8. package/dist/{deno-CkkDV5gw.mjs → deno-D4J5fszd.mjs} +1 -1
  9. package/dist/{docloader-D6eQ0vA8.mjs → docloader-ChK1DQkj.mjs} +4 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  13. package/dist/federation/handler.test.mjs +258 -6
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox-report.test.mjs +30 -5
  17. package/dist/federation/inbox.test.mjs +43 -0
  18. package/dist/federation/metrics.test.mjs +1 -1
  19. package/dist/federation/middleware.test.mjs +172 -10
  20. package/dist/federation/mod.cjs +1 -1
  21. package/dist/federation/mod.d.cts +2 -2
  22. package/dist/federation/mod.d.ts +2 -2
  23. package/dist/federation/mod.js +1 -1
  24. package/dist/federation/outgoing-proof.test.mjs +4 -5
  25. package/dist/federation/portable-actor.test.mjs +3 -3
  26. package/dist/federation/portable-chain.test.mjs +3 -3
  27. package/dist/federation/portable-collection.test.mjs +4 -4
  28. package/dist/federation/portable-delivery.test.mjs +2 -2
  29. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  30. package/dist/federation/portable-inbox.test.mjs +4 -4
  31. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  32. package/dist/federation/portable-route-activity.test.mjs +2 -2
  33. package/dist/federation/portable.test.mjs +3 -3
  34. package/dist/federation/send.test.mjs +3 -3
  35. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  36. package/dist/federation/tasks/tasks.test.mjs +2 -2
  37. package/dist/federation/temporal.test.mjs +1 -1
  38. package/dist/federation/webfinger.test.mjs +1 -1
  39. package/dist/{federation-BkIRrGXG.cjs → federation-DxQI9dSY.cjs} +125 -20
  40. package/dist/{http-DxljNiCC.mjs → http--kHQomKT.mjs} +4 -4
  41. package/dist/{http-C2gfnrKF.js → http-C8rIQ1pK.js} +110 -12
  42. package/dist/{http-CjpGhvxc.cjs → http-SBTDzPkh.cjs} +109 -11
  43. package/dist/inbox-BGD7K-Ra.mjs +188 -0
  44. package/dist/{kv-cache-DDwF4zee.mjs → kv-cache-B8LMKVe8.mjs} +1 -1
  45. package/dist/{kv-cache-nsVJF31j.js → kv-cache-DSUypZWS.js} +3 -1
  46. package/dist/{kv-cache-BVtotkZw.cjs → kv-cache-Dp_dDR1f.cjs} +3 -1
  47. package/dist/{key-8sZWp4lR.mjs → ld-BeBdBDAY.mjs} +760 -16
  48. package/dist/{metrics-DiQRjv6y.mjs → metrics-Dtb81JxK.mjs} +1 -1
  49. package/dist/{middleware-C_EdDn9c.js → middleware-DNjv5rBL.js} +125 -20
  50. package/dist/{middleware-BBnHLNpJ.mjs → middleware-DhI-75xh.mjs} +1 -1
  51. package/dist/{middleware-ClqRVObS.mjs → middleware-rQqAHzIo.mjs} +107 -180
  52. package/dist/{mod-BBPd4M_0.d.cts → mod-DKGoPoQC.d.cts} +1 -1
  53. package/dist/{mod-CUYBGKof.d.ts → mod-Dm-7RdhR.d.ts} +1 -1
  54. package/dist/mod.cjs +4 -4
  55. package/dist/mod.d.cts +2 -2
  56. package/dist/mod.d.ts +2 -2
  57. package/dist/mod.js +4 -4
  58. package/dist/nodeinfo/handler.test.mjs +1 -1
  59. package/dist/{owner-IiJfae6n.mjs → owner-u_-HQrgE.mjs} +3 -3
  60. package/dist/{portable-collection-BeS_vx1G.cjs → portable-collection-B-x4XwxP.cjs} +1 -1
  61. package/dist/{portable-collection-CUDbGQ5_.mjs → portable-collection-BKkTJQTd.mjs} +2 -2
  62. package/dist/{portable-collection-BtyE_2Rv.js → portable-collection-DReoGwZQ.js} +1 -1
  63. package/dist/{portable-key-ENr3Sc97.mjs → portable-key-Bkm6sXJB.mjs} +1 -1
  64. package/dist/{proof-CGtiU4Lz.mjs → proof-DOV4ZiAo.mjs} +3 -4
  65. package/dist/{send-CRYlnzg6.mjs → send-C_Fffu0I.mjs} +3 -3
  66. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  67. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  68. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  69. package/dist/sig/compound-proof.test.mjs +2 -2
  70. package/dist/sig/http.test.mjs +2 -2
  71. package/dist/sig/key.test.mjs +208 -3
  72. package/dist/sig/ld.test.mjs +1 -2
  73. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  74. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  75. package/dist/sig/mod.cjs +2 -2
  76. package/dist/sig/mod.js +2 -2
  77. package/dist/sig/owner.test.mjs +1 -1
  78. package/dist/sig/portable-collection.test.mjs +1 -1
  79. package/dist/sig/portable-interop.test.mjs +3 -3
  80. package/dist/sig/portable-key.test.mjs +5 -6
  81. package/dist/sig/proof.test.mjs +3 -3
  82. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  83. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  84. package/dist/sig/verification.test.mjs +3 -3
  85. package/dist/{temporal-CDbdX_g_.mjs → temporal-BRgOZRRr.mjs} +1 -1
  86. package/dist/testing/kv.test.mjs +1 -1
  87. package/dist/testing/mod.d.mts +11 -0
  88. package/dist/utils/docloader.test.mjs +112 -37
  89. package/dist/utils/kv-cache.test.mjs +1 -1
  90. package/dist/utils/mod.cjs +1 -1
  91. package/dist/utils/mod.js +1 -1
  92. package/package.json +7 -7
  93. package/dist/ld-DXaIENwZ.mjs +0 -658
@@ -2,7 +2,7 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { i as getPortableDid, l as isPortableId, n as getCanonicalPortableId } from "./portable-key-id-Ca30-hOa.mjs";
5
- import { n as version, t as name } from "./deno-CkkDV5gw.mjs";
5
+ import { n as version, t as name } from "./deno-D4J5fszd.mjs";
6
6
  import { t as ActivityListenerSet } from "./activity-listener-C-BSbKI7.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
@@ -91,7 +91,7 @@ var FederationBuilderImpl = class {
91
91
  * specified.
92
92
  */
93
93
  async build(options) {
94
- const { FederationImpl } = await import("./middleware-BBnHLNpJ.mjs");
94
+ const { FederationImpl } = await import("./middleware-DhI-75xh.mjs");
95
95
  const f = new FederationImpl(options);
96
96
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
97
97
  f.router = this.router.clone();
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { en as ActivityTransformer, n as Context } from "../context-BjD_CTxh.cjs";
2
+ import { en as ActivityTransformer, n as Context } from "../context-DPocjp1l.cjs";
3
3
  import { Activity } from "@fedify/vocab";
4
4
 
5
5
  //#region src/compat/transformers.d.ts
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { en as ActivityTransformer, n as Context } from "../context-C2-UDo2c.js";
2
+ import { en as ActivityTransformer, n as Context } from "../context-2wLkQrjb.js";
3
3
  import { Activity } from "@fedify/vocab";
4
4
 
5
5
  //#region src/compat/transformers.d.ts
@@ -5,7 +5,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
7
7
  import { t as assert } from "../assert-D1Eafmd_.mjs";
8
- import { O as actorDehydrator, k as autoIdAssigner, n as FederationImpl } from "../middleware-ClqRVObS.mjs";
8
+ import { O as actorDehydrator, k as autoIdAssigner, n as FederationImpl } from "../middleware-rQqAHzIo.mjs";
9
9
  import { Follow, Person } from "@fedify/vocab";
10
10
  import { test } from "@fedify/fixture";
11
11
  //#region src/compat/transformers.test.ts
@@ -3,9 +3,9 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { a as getRawCanonicalPortableId, l as isPortableId, n as getCanonicalPortableId } from "./portable-key-id-Ca30-hOa.mjs";
5
5
  import { a as verificationObservation, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
6
+ import { c as getNormalizationContextLoader } from "./ld-BeBdBDAY.mjs";
6
7
  import { n as preloadedOnlyDocumentLoader } from "./public-audience--klBIOk1.mjs";
7
- import { c as getNormalizationContextLoader } from "./ld-DXaIENwZ.mjs";
8
- import { a as preparePortableObjectProofPolicy, f as verifyPreparedPortableObjectProofPolicy, n as classifyFep2277CoreType, s as verifyMapLocalProof, u as verifyPortableObjectProofPolicy } from "./proof-CGtiU4Lz.mjs";
8
+ import { a as preparePortableObjectProofPolicy, f as verifyPreparedPortableObjectProofPolicy, n as classifyFep2277CoreType, s as verifyMapLocalProof, u as verifyPortableObjectProofPolicy } from "./proof-DOV4ZiAo.mjs";
9
9
  import { parseIri, preloadedContexts } from "@fedify/vocab-runtime";
10
10
  import jsonld from "@fedify/vocab-runtime/jsonld";
11
11
  //#region src/sig/compound-proof.ts
@@ -42,6 +42,17 @@ type InboxAuthentication = {
42
42
  readonly detail?: InboxProofPolicyFailureReason;
43
43
  } | {
44
44
  readonly type: "invalidNonce";
45
+ } | {
46
+ /**
47
+ * The activity's ID does not have the same [FEP-fe34] origin as all
48
+ * of its actors.
49
+ *
50
+ * [FEP-fe34]: https://w3id.org/fep/fe34
51
+ * @since 2.5.0
52
+ */
53
+ readonly type: "activityOriginMismatch";
54
+ readonly activityId: URL;
55
+ readonly actorIds: readonly URL[];
45
56
  };
46
57
  } | {
47
58
  readonly status: "skipped";
@@ -40,6 +40,17 @@ type InboxAuthentication = {
40
40
  readonly detail?: InboxProofPolicyFailureReason;
41
41
  } | {
42
42
  readonly type: "invalidNonce";
43
+ } | {
44
+ /**
45
+ * The activity's ID does not have the same [FEP-fe34] origin as all
46
+ * of its actors.
47
+ *
48
+ * [FEP-fe34]: https://w3id.org/fep/fe34
49
+ * @since 2.5.0
50
+ */
51
+ readonly type: "activityOriginMismatch";
52
+ readonly activityId: URL;
53
+ readonly actorIds: readonly URL[];
43
54
  };
44
55
  } | {
45
56
  readonly status: "skipped";
@@ -3,6 +3,6 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  //#region deno.json
5
5
  var name = "@fedify/fedify";
6
- var version = "2.5.0-dev.2370+e0cfd033";
6
+ var version = "2.5.0-dev.2420+4db1db92";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as validateCryptoKey } from "./key-8sZWp4lR.mjs";
5
- import { n as doubleKnock } from "./http-DxljNiCC.mjs";
4
+ import { T as validateCryptoKey } from "./ld-BeBdBDAY.mjs";
5
+ import { n as doubleKnock } from "./http--kHQomKT.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, resolveDocumentLoaderTimeout, validatePublicUrl, withDocumentLoaderTimeout } from "@fedify/vocab-runtime";
8
8
  import { curry } from "es-toolkit";
@@ -64,6 +64,8 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
64
64
  }, options);
65
65
  }
66
66
  async function validateUrl(url, options) {
67
+ const protocol = new URL(url).protocol;
68
+ if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
67
69
  if (!allowPrivateAddress) {
68
70
  try {
69
71
  await validatePublicUrl(url);
@@ -7,7 +7,7 @@ import { t as assertExists } from "../assert_exists-cffSpJ7X.mjs";
7
7
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
10
- import { r as createFederationBuilder } from "../builder-DzGfERAV.mjs";
10
+ import { r as createFederationBuilder } from "../builder-Cwc3Eu59.mjs";
11
11
  import { DisallowedOperatorError, DisallowedVarSpecModifierError, DuplicateRouteVariableError, RouteTemplateOptionsNotMatchedError, RouterError } from "@fedify/uri-template";
12
12
  import { Activity, Note, Person } from "@fedify/vocab";
13
13
  import { test } from "@fedify/fixture";
@@ -6,11 +6,11 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import "../std__assert-DvMDRPes.mjs";
8
8
  import { t as assert } from "../assert-D1Eafmd_.mjs";
9
- import { n as exportJwk } from "../key-8sZWp4lR.mjs";
9
+ import { y as exportJwk } from "../ld-BeBdBDAY.mjs";
10
10
  import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
11
- import { o as signObject, p as verifyProof } from "../proof-CGtiU4Lz.mjs";
11
+ import { o as signObject, p as verifyProof } from "../proof-DOV4ZiAo.mjs";
12
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
13
- import { n as FederationImpl } from "../middleware-ClqRVObS.mjs";
13
+ import { n as FederationImpl } from "../middleware-rQqAHzIo.mjs";
14
14
  import { Create, DataIntegrityProof, Note } from "@fedify/vocab";
15
15
  import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
16
16
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -5,10 +5,10 @@ import { r as createRequestContext, t as createInboxContext } from "../context-8
5
5
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import "../std__assert-DvMDRPes.mjs";
8
- import { a as compactJsonLd, m as verifyCompactJsonLd, p as signJsonLd } from "../ld-DXaIENwZ.mjs";
9
- import { l as signRequest } from "../http-DxljNiCC.mjs";
8
+ import { a as compactJsonLd, m as verifyCompactJsonLd, p as signJsonLd } from "../ld-BeBdBDAY.mjs";
9
+ import { l as signRequest } from "../http--kHQomKT.mjs";
10
10
  import { a as rsaPrivateKey3, c as rsaPublicKey3, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
11
- import { f as handleInbox, o as createFederation } from "../middleware-ClqRVObS.mjs";
11
+ import { f as handleInbox, o as createFederation } from "../middleware-rQqAHzIo.mjs";
12
12
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
13
13
  import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
14
14
  import { Create, CryptographicKey, Multikey, Person, Update } from "@fedify/vocab";
@@ -134,7 +134,7 @@ test("handleInbox() enforces compounds after alternate outer auth", async () =>
134
134
  test("handleInbox() preserves ordinary top-level proof behavior", async () => {
135
135
  assertEquals((await handle(await secureDocument({
136
136
  "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
137
- id: "https://example.com/activities/ordinary",
137
+ id: `${map_local_create_note_default.keys.outer.controller}/activities/ordinary`,
138
138
  type: "Create",
139
139
  actor: map_local_create_note_default.keys.outer.controller,
140
140
  object: {
@@ -254,7 +254,7 @@ test("handleInbox() keeps embedded signature properties in proof inputs", async
254
254
  test("handleInbox() inspects compounds without the Linked Data Signature", async () => {
255
255
  const activity = await secureDocument({
256
256
  "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
257
- id: "https://example.com/activities/ordinary-ld",
257
+ id: `${map_local_create_note_default.keys.outer.controller}/activities/ordinary-ld`,
258
258
  type: "Create",
259
259
  actor: map_local_create_note_default.keys.outer.controller,
260
260
  object: {
@@ -11,12 +11,11 @@ import "../std__assert-DvMDRPes.mjs";
11
11
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
12
12
  import { t as assert } from "../assert-D1Eafmd_.mjs";
13
13
  import { r as parseAcceptSignature } from "../accept-CPVqki78.mjs";
14
- import { o as generateCryptoKeyPair } from "../key-8sZWp4lR.mjs";
15
- import { a as compactJsonLd, p as signJsonLd } from "../ld-DXaIENwZ.mjs";
16
- import { o as signObject } from "../proof-CGtiU4Lz.mjs";
17
- import { l as signRequest } from "../http-DxljNiCC.mjs";
18
- import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
19
- import { d as handleCustomCollection, f as handleInbox, g as respondWithObjectIfAcceptable, h as respondWithObject, l as handleActor, m as handleOutbox, o as createFederation, p as handleObject, u as handleCollection } from "../middleware-ClqRVObS.mjs";
14
+ import { C as generateCryptoKeyPair, a as compactJsonLd, p as signJsonLd } from "../ld-BeBdBDAY.mjs";
15
+ import { o as signObject } from "../proof-DOV4ZiAo.mjs";
16
+ import { l as signRequest } from "../http--kHQomKT.mjs";
17
+ import { a as rsaPrivateKey3, c as rsaPublicKey3, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
18
+ import { d as handleCustomCollection, f as handleInbox, g as respondWithObjectIfAcceptable, h as respondWithObject, l as handleActor, m as handleOutbox, o as createFederation, p as handleObject, u as handleCollection } from "../middleware-rQqAHzIo.mjs";
20
19
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
21
20
  import { t as FedifySpanExporter } from "../exporter-DThFfe7P.mjs";
22
21
  import { configure, getLogger, reset, withContext } from "@logtape/logtape";
@@ -3346,6 +3345,259 @@ test("handleInbox() - authentication bypass vulnerability", async () => {
3346
3345
  assertEquals(await response.text(), "The signer and the actor do not match.");
3347
3346
  assertEquals(processedActivity, void 0, `SECURITY VULNERABILITY: Malicious activity with mismatched signature was processed! Activity ID: ${processedActivity?.id?.href}, Claimed actor: ${processedActivity?.actorId?.href}`);
3348
3347
  });
3348
+ test("handleInbox() rejects cross-origin activity IDs", async () => {
3349
+ const federation = createFederation({ kv: new MemoryKvStore() });
3350
+ const kv = new MemoryKvStore();
3351
+ const inboxListeners = new ActivityListenerSet();
3352
+ let idempotencyCalls = 0;
3353
+ let listenerCalls = 0;
3354
+ inboxListeners.add(Create, () => {
3355
+ listenerCalls++;
3356
+ });
3357
+ const activity = new Create({
3358
+ id: new URL("https://victim.example/activities/1"),
3359
+ actor: rsaPublicKey3.ownerId,
3360
+ object: new Note({ content: "Forged" })
3361
+ });
3362
+ const activityJson = await activity.toJsonLd({
3363
+ format: "compact",
3364
+ contextLoader: mockDocumentLoader
3365
+ });
3366
+ const proofSigned = await signObject(activity, ed25519PrivateKey, ed25519Multikey.id, {
3367
+ contextLoader: mockDocumentLoader,
3368
+ documentLoader: mockDocumentLoader
3369
+ });
3370
+ const requests = [
3371
+ await signRequest(new Request("https://local.example/inbox", {
3372
+ method: "POST",
3373
+ body: JSON.stringify(activityJson)
3374
+ }), rsaPrivateKey3, rsaPublicKey3.id),
3375
+ new Request("https://local.example/inbox", {
3376
+ method: "POST",
3377
+ body: JSON.stringify(await signJsonLd(activityJson, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
3378
+ }),
3379
+ new Request("https://local.example/inbox", {
3380
+ method: "POST",
3381
+ body: JSON.stringify(await proofSigned.toJsonLd({
3382
+ format: "compact",
3383
+ contextLoader: mockDocumentLoader
3384
+ }))
3385
+ }),
3386
+ await signRequest(new Request("https://local.example/inbox", {
3387
+ method: "POST",
3388
+ body: JSON.stringify(await new Create({
3389
+ id: new URL("https://example.com/activities/multiple-actors"),
3390
+ actors: [rsaPublicKey3.ownerId, new URL("https://attacker.example/actor")]
3391
+ }).toJsonLd())
3392
+ }), rsaPrivateKey3, rsaPublicKey3.id)
3393
+ ];
3394
+ for (const request of requests) {
3395
+ const context = createRequestContext({
3396
+ federation,
3397
+ request,
3398
+ url: new URL(request.url),
3399
+ data: void 0,
3400
+ documentLoader: mockDocumentLoader,
3401
+ contextLoader: mockDocumentLoader
3402
+ });
3403
+ const response = await handleInbox(request, {
3404
+ recipient: null,
3405
+ context,
3406
+ inboxContextFactory(_activity) {
3407
+ return createInboxContext({
3408
+ ...context,
3409
+ clone: void 0
3410
+ });
3411
+ },
3412
+ kv,
3413
+ kvPrefixes: {
3414
+ activityIdempotence: ["_fedify", "activityIdempotence"],
3415
+ publicKey: ["_fedify", "publicKey"],
3416
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
3417
+ },
3418
+ actorDispatcher: () => null,
3419
+ inboxListeners,
3420
+ idempotencyStrategy() {
3421
+ idempotencyCalls++;
3422
+ return "forged";
3423
+ },
3424
+ onNotFound: () => new Response("Not found", { status: 404 }),
3425
+ signatureTimeWindow: { minutes: 5 },
3426
+ skipSignatureVerification: false
3427
+ });
3428
+ assertEquals([response.status, await response.text()], [401, "The activity and its actors do not have the same origin."]);
3429
+ }
3430
+ assertEquals(idempotencyCalls, 0);
3431
+ assertEquals(listenerCalls, 0);
3432
+ });
3433
+ test("handleInbox() preserves the signature-verification opt-out", async () => {
3434
+ const federation = createFederation({ kv: new MemoryKvStore() });
3435
+ const activity = new Create({
3436
+ id: new URL("https://victim.example/activities/1"),
3437
+ actor: new URL("https://attacker.example/actor")
3438
+ });
3439
+ const request = new Request("https://local.example/inbox", {
3440
+ method: "POST",
3441
+ body: JSON.stringify(await activity.toJsonLd())
3442
+ });
3443
+ const context = createRequestContext({
3444
+ federation,
3445
+ request,
3446
+ url: new URL(request.url),
3447
+ data: void 0,
3448
+ documentLoader: mockDocumentLoader
3449
+ });
3450
+ let listenerCalls = 0;
3451
+ const inboxListeners = new ActivityListenerSet();
3452
+ inboxListeners.add(Create, () => {
3453
+ listenerCalls++;
3454
+ });
3455
+ assertEquals((await handleInbox(request, {
3456
+ recipient: null,
3457
+ context,
3458
+ inboxContextFactory(_activity) {
3459
+ return createInboxContext({
3460
+ ...context,
3461
+ clone: void 0
3462
+ });
3463
+ },
3464
+ kv: new MemoryKvStore(),
3465
+ kvPrefixes: {
3466
+ activityIdempotence: ["_fedify", "activityIdempotence"],
3467
+ publicKey: ["_fedify", "publicKey"],
3468
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
3469
+ },
3470
+ actorDispatcher: () => null,
3471
+ inboxListeners,
3472
+ onNotFound: () => new Response("Not found", { status: 404 }),
3473
+ signatureTimeWindow: { minutes: 5 },
3474
+ skipSignatureVerification: true
3475
+ })).status, 202);
3476
+ assertEquals(listenerCalls, 1);
3477
+ });
3478
+ test("handleInbox() rejects missing actors before idempotency", async () => {
3479
+ const federation = createFederation({ kv: new MemoryKvStore() });
3480
+ const unsignedJson = {
3481
+ "@context": "https://www.w3.org/ns/activitystreams",
3482
+ id: "https://remote.example/activities/actorless",
3483
+ type: "Create"
3484
+ };
3485
+ const requests = [new Request("https://local.example/inbox", {
3486
+ method: "POST",
3487
+ body: JSON.stringify(unsignedJson)
3488
+ }), new Request("https://local.example/inbox", {
3489
+ method: "POST",
3490
+ body: JSON.stringify(await signJsonLd(unsignedJson, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
3491
+ })];
3492
+ let idempotencyCalls = 0;
3493
+ for (const request of requests) {
3494
+ const context = createRequestContext({
3495
+ federation,
3496
+ request,
3497
+ url: new URL(request.url),
3498
+ data: void 0,
3499
+ documentLoader: mockDocumentLoader,
3500
+ contextLoader: mockDocumentLoader
3501
+ });
3502
+ const response = await handleInbox(request, {
3503
+ recipient: null,
3504
+ context,
3505
+ inboxContextFactory(_activity) {
3506
+ return createInboxContext({
3507
+ ...context,
3508
+ clone: void 0
3509
+ });
3510
+ },
3511
+ kv: new MemoryKvStore(),
3512
+ kvPrefixes: {
3513
+ activityIdempotence: ["_fedify", "activityIdempotence"],
3514
+ publicKey: ["_fedify", "publicKey"],
3515
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
3516
+ },
3517
+ actorDispatcher: () => null,
3518
+ idempotencyStrategy() {
3519
+ idempotencyCalls++;
3520
+ return "actorless";
3521
+ },
3522
+ onNotFound: () => new Response("Not found", { status: 404 }),
3523
+ signatureTimeWindow: { minutes: 5 },
3524
+ skipSignatureVerification: false
3525
+ });
3526
+ assertEquals([response.status, await response.text()], [400, "Missing actor."]);
3527
+ }
3528
+ assertEquals(idempotencyCalls, 0);
3529
+ });
3530
+ test("handleInbox() leaves a rejected activity ID available", async () => {
3531
+ const activityId = new URL("https://example.com/activities/genuine");
3532
+ const attackerId = new URL("https://attacker.example/actor");
3533
+ const attackerKeyId = new URL(`${attackerId.href}#main-key`);
3534
+ const attackerKeys = await generateCryptoKeyPair("RSASSA-PKCS1-v1_5");
3535
+ const attackerDocument = await new Person({
3536
+ id: attackerId,
3537
+ publicKey: new CryptographicKey({
3538
+ id: attackerKeyId,
3539
+ owner: attackerId,
3540
+ publicKey: attackerKeys.publicKey
3541
+ })
3542
+ }).toJsonLd({ contextLoader: mockDocumentLoader });
3543
+ const documentLoader = (resource) => resource === attackerId.href || resource === attackerKeyId.href ? Promise.resolve({
3544
+ contextUrl: null,
3545
+ documentUrl: resource,
3546
+ document: attackerDocument
3547
+ }) : mockDocumentLoader(resource);
3548
+ for (const strategy of ["per-inbox", "global"]) {
3549
+ const kv = new MemoryKvStore();
3550
+ const federation = createFederation({ kv });
3551
+ const inboxListeners = new ActivityListenerSet();
3552
+ const received = [];
3553
+ inboxListeners.add(Create, (_ctx, activity) => {
3554
+ received.push(activity.actorId.href);
3555
+ });
3556
+ const handle = async (request) => {
3557
+ const context = createRequestContext({
3558
+ federation,
3559
+ request,
3560
+ url: new URL(request.url),
3561
+ data: void 0,
3562
+ documentLoader,
3563
+ contextLoader: mockDocumentLoader
3564
+ });
3565
+ return await handleInbox(request, {
3566
+ recipient: null,
3567
+ context,
3568
+ inboxContextFactory(_activity) {
3569
+ return createInboxContext({
3570
+ ...context,
3571
+ clone: void 0
3572
+ });
3573
+ },
3574
+ kv,
3575
+ kvPrefixes: {
3576
+ activityIdempotence: ["_fedify", "activityIdempotence"],
3577
+ publicKey: ["_fedify", "publicKey"],
3578
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
3579
+ },
3580
+ actorDispatcher: () => null,
3581
+ inboxListeners,
3582
+ idempotencyStrategy: strategy,
3583
+ onNotFound: () => new Response("Not found", { status: 404 }),
3584
+ signatureTimeWindow: { minutes: 5 },
3585
+ skipSignatureVerification: false
3586
+ });
3587
+ };
3588
+ const createRequest = async (actor, privateKey, keyId) => await signRequest(new Request("https://local.example/inbox", {
3589
+ method: "POST",
3590
+ body: JSON.stringify(await new Create({
3591
+ id: activityId,
3592
+ actor
3593
+ }).toJsonLd())
3594
+ }), privateKey, keyId);
3595
+ const forged = await handle(await createRequest(attackerId, attackerKeys.privateKey, attackerKeyId));
3596
+ const genuine = await handle(await createRequest(rsaPublicKey3.ownerId, rsaPrivateKey3, rsaPublicKey3.id));
3597
+ assertEquals([forged.status, genuine.status], [401, 202]);
3598
+ assertEquals(received, [rsaPublicKey3.ownerId.href]);
3599
+ }
3600
+ });
3349
3601
  test("respondWithObjectIfAcceptable", async () => {
3350
3602
  let request = new Request("https://example.com/", { headers: { Accept: "application/activity+json" } });
3351
3603
  let response = await respondWithObjectIfAcceptable(new Note({
@@ -10,8 +10,8 @@ import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
11
11
  import { t as assert } from "../assert-D1Eafmd_.mjs";
12
12
  import { r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
13
- import { o as createFederation, x as parseHashlinkGatewayRequest } from "../middleware-ClqRVObS.mjs";
14
- import { r as createFederationBuilder } from "../builder-DzGfERAV.mjs";
13
+ import { o as createFederation, x as parseHashlinkGatewayRequest } from "../middleware-rQqAHzIo.mjs";
14
+ import { r as createFederationBuilder } from "../builder-Cwc3Eu59.mjs";
15
15
  import { n as RouterError } from "../router-u5O5KCQr.mjs";
16
16
  import { Note } from "@fedify/vocab";
17
17
  import { computeDigestMultibase, encodeMultibase, exportDidKey } from "@fedify/vocab-runtime";
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
- import { o as signObject } from "../proof-CGtiU4Lz.mjs";
7
+ import { o as signObject } from "../proof-DOV4ZiAo.mjs";
8
8
  import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
9
- import { o as createFederation } from "../middleware-ClqRVObS.mjs";
9
+ import { o as createFederation } from "../middleware-rQqAHzIo.mjs";
10
10
  import { Create, Follow, Person } from "@fedify/vocab";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region src/federation/idempotency.test.ts
@@ -2,12 +2,12 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
- import { p as signJsonLd } from "../ld-DXaIENwZ.mjs";
6
- import { o as signObject } from "../proof-CGtiU4Lz.mjs";
7
- import { l as signRequest } from "../http-DxljNiCC.mjs";
5
+ import { p as signJsonLd } from "../ld-BeBdBDAY.mjs";
6
+ import { o as signObject } from "../proof-DOV4ZiAo.mjs";
7
+ import { l as signRequest } from "../http--kHQomKT.mjs";
8
8
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
9
- import { o as createFederation } from "../middleware-ClqRVObS.mjs";
10
- import { r as createFederationBuilder } from "../builder-DzGfERAV.mjs";
9
+ import { o as createFederation } from "../middleware-rQqAHzIo.mjs";
10
+ import { r as createFederationBuilder } from "../builder-Cwc3Eu59.mjs";
11
11
  import { Create, Person } from "@fedify/vocab";
12
12
  import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
13
13
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -249,6 +249,31 @@ test("onRequestFinished retains HTTP keys and separates ownership rejection", as
249
249
  strictEqual(reports[2].authentication.reason.type, "actorKeyMismatch");
250
250
  strictEqual(reports[2].attempts.at(-1)?.status, "verified");
251
251
  });
252
+ test("onRequestFinished reports an activity ID on another origin than its actor", async () => {
253
+ const { federation, setters, reports } = setup();
254
+ let dispatched = false;
255
+ setters.on(Create, () => {
256
+ dispatched = true;
257
+ });
258
+ const signed = await signRequest(request(await payload("https://other.example/create")), rsaPrivateKey3, new URL("https://example.com/person2#key3"));
259
+ strictEqual((await federation.fetch(signed, { contextData: void 0 })).status, 401);
260
+ strictEqual(dispatched, false);
261
+ deepStrictEqual(reports[0].authentication, {
262
+ status: "rejected",
263
+ reason: {
264
+ type: "activityOriginMismatch",
265
+ activityId: new URL("https://other.example/create"),
266
+ actorIds: [new URL("https://example.com/person2")]
267
+ }
268
+ });
269
+ deepStrictEqual(reports[0].outcome, {
270
+ type: "response",
271
+ status: 401,
272
+ disposition: "rejected",
273
+ reason: "authentication"
274
+ });
275
+ strictEqual(reports[0].attempts.at(-1)?.status, "verified");
276
+ });
252
277
  test("onRequestFinished retains LDS crypto success before HTTP fallback", async () => {
253
278
  const { federation, reports } = setup();
254
279
  const signed = await signRequest(request(await signJsonLd(await payload("https://example.com/lds", "https://example.com/person"), rsaPrivateKey3, new URL("https://example.com/person2#key3"), loaders)), rsaPrivateKey2, new URL("https://example.com/key2"));
@@ -2,11 +2,54 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
+ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
5
6
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
6
7
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
8
+ import { t as actorsHaveSameOrigin } from "../inbox-BGD7K-Ra.mjs";
7
9
  import { Activity, Create, Invite, Offer, Update } from "@fedify/vocab";
10
+ import { parseIri } from "@fedify/vocab-runtime";
8
11
  import { test } from "@fedify/fixture";
9
12
  //#region src/federation/inbox.test.ts
13
+ test("actorsHaveSameOrigin()", () => {
14
+ assertEquals(actorsHaveSameOrigin(new Create({ actor: new URL("https://remote.example/actor") })), true);
15
+ assertEquals(actorsHaveSameOrigin(new Create({
16
+ id: new URL("https://remote.example/activity"),
17
+ actors: [new URL("https://remote.example/actor/1"), new URL("https://remote.example:443/actor/2")]
18
+ })), true);
19
+ for (const actor of [
20
+ "http://remote.example/actor",
21
+ "https://sub.remote.example/actor",
22
+ "https://remote.example:8443/actor"
23
+ ]) assertFalse(actorsHaveSameOrigin(new Create({
24
+ id: new URL("https://remote.example/activity"),
25
+ actor: new URL(actor)
26
+ })));
27
+ assertFalse(actorsHaveSameOrigin(new Create({
28
+ id: new URL("urn:example:activity"),
29
+ actor: new URL("urn:example:actor")
30
+ })));
31
+ assertFalse(actorsHaveSameOrigin(new Create({
32
+ id: new URL("https://remote.example/activity"),
33
+ actors: [new URL("https://remote.example/actor/1"), new URL("https://other.example/actor/2")]
34
+ })));
35
+ });
36
+ test("actorsHaveSameOrigin() compares portable IDs by their DIDs", () => {
37
+ const did = "did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2";
38
+ const otherDid = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
39
+ const activityId = parseIri(`ap+ef61://${did}/activities/1`);
40
+ for (const actor of [`ap+ef61://${did}/actor`, `https://gw.example/.well-known/apgateway/${did}/actor`]) assertEquals(actorsHaveSameOrigin(new Create({
41
+ id: activityId,
42
+ actor: parseIri(actor)
43
+ })), true);
44
+ for (const actor of [
45
+ `ap+ef61://${otherDid}/actor`,
46
+ `https://gw.example/.well-known/apgateway/${otherDid}/actor`,
47
+ "https://gw.example/actor"
48
+ ]) assertFalse(actorsHaveSameOrigin(new Create({
49
+ id: activityId,
50
+ actor: parseIri(actor)
51
+ })));
52
+ });
10
53
  test("ActivityListenerSet", () => {
11
54
  const listeners = new ActivityListenerSet();
12
55
  const activity = new Activity({});
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
7
- import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-DiQRjv6y.mjs";
7
+ import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-Dtb81JxK.mjs";
8
8
  import { FetchError } from "@fedify/vocab-runtime";
9
9
  import { createTestMeterProvider, test } from "@fedify/fixture";
10
10
  import { DataPointType, MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";