@fedify/fedify 2.3.12 → 2.3.13

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 (68) hide show
  1. package/dist/{assert_rejects-DN60FHPX.mjs → assert_rejects-D1HYAik3.mjs} +2 -20
  2. package/dist/{assert_throws-BOkhLGYc.mjs → assert_throws-BbSMEFKo.mjs} +19 -1
  3. package/dist/{builder-B6ixdT3y.mjs → builder-BagNDcQw.mjs} +2 -2
  4. package/dist/compat/transformers.test.mjs +2 -2
  5. package/dist/{deno-D4dcoVfF.mjs → deno-XpcjbHwj.mjs} +1 -1
  6. package/dist/{docloader-NOKfswJV.mjs → docloader-QDpF5t9M.mjs} +4 -2
  7. package/dist/federation/builder.test.mjs +3 -3
  8. package/dist/federation/circuit-breaker.test.mjs +3 -3
  9. package/dist/federation/collection.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +262 -9
  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 +1 -1
  14. package/dist/federation/kv.test.mjs +1 -1
  15. package/dist/federation/metrics.test.mjs +3 -3
  16. package/dist/federation/middleware.test.mjs +177 -15
  17. package/dist/federation/mod.cjs +1 -1
  18. package/dist/federation/mod.js +1 -1
  19. package/dist/federation/mq.test.mjs +4 -3
  20. package/dist/federation/negotiation.test.mjs +4 -4
  21. package/dist/federation/retry.test.mjs +1 -1
  22. package/dist/federation/router.test.mjs +3 -4
  23. package/dist/federation/send.test.mjs +7 -6
  24. package/dist/federation/temporal.test.mjs +4 -4
  25. package/dist/federation/webfinger.test.mjs +2 -2
  26. package/dist/{http-HjidyDfF.mjs → http-BRrpIDS1.mjs} +3 -3
  27. package/dist/{http-D_Xrr6kp.cjs → http-DwPnG87d.cjs} +1 -1
  28. package/dist/{http-DkyUBs_T.js → http-GHVGCeb5.js} +1 -1
  29. package/dist/inbox-BzOuCPLY.mjs +180 -0
  30. package/dist/{key-Dg-NXm4K.mjs → key-PdEu1lDF.mjs} +2 -2
  31. package/dist/{kv-cache-BhGeZk_o.js → kv-cache-BmqdF048.js} +3 -1
  32. package/dist/{kv-cache-BpVwvcSI.mjs → kv-cache-Clx3SwMp.mjs} +1 -1
  33. package/dist/{kv-cache-BwnC6JGt.cjs → kv-cache-poF6Tqnz.cjs} +3 -1
  34. package/dist/{ld-C1wveXXf.mjs → ld-D6UlzNnG.mjs} +3 -3
  35. package/dist/{metrics-cIaMxG0v.mjs → metrics-62qolVUY.mjs} +1 -1
  36. package/dist/{middleware-VxPpU0h_.mjs → middleware-C3Q3CoR8.mjs} +1 -1
  37. package/dist/{middleware-I-XlrRVl.mjs → middleware-CfbFdEO-.mjs} +96 -194
  38. package/dist/{middleware-DhOFTXmo.cjs → middleware-DkrXqZO_.cjs} +106 -34
  39. package/dist/{middleware-CnrzVu9i.js → middleware-xw9fBZoB.js} +106 -34
  40. package/dist/mod.cjs +4 -4
  41. package/dist/mod.js +4 -4
  42. package/dist/nodeinfo/client.test.mjs +2 -2
  43. package/dist/nodeinfo/handler.test.mjs +2 -2
  44. package/dist/nodeinfo/types.test.mjs +2 -2
  45. package/dist/otel/exporter.test.mjs +1 -1
  46. package/dist/{owner-CFKfE85G.mjs → owner-DWnLg-vk.mjs} +2 -2
  47. package/dist/{proof-HU__qtUD.js → proof-CUSzBztF.js} +1 -1
  48. package/dist/{proof-DBxu6g2K.cjs → proof-DF6CU-SU.cjs} +1 -1
  49. package/dist/{proof-DtpGEj0u.mjs → proof-mf6h7n4_.mjs} +3 -3
  50. package/dist/{send-CwMea5r6.mjs → send-LJUzNpVF.mjs} +3 -3
  51. package/dist/sig/http.test.mjs +6 -6
  52. package/dist/sig/key.test.mjs +6 -6
  53. package/dist/sig/ld.test.mjs +5 -5
  54. package/dist/sig/mod.cjs +2 -2
  55. package/dist/sig/mod.js +2 -2
  56. package/dist/sig/owner.test.mjs +4 -4
  57. package/dist/sig/proof.test.mjs +5 -4
  58. package/dist/{std__assert-BvGuO4xO.mjs → std__assert-BjJn2daJ.mjs} +2 -2
  59. package/dist/{temporal-ClSXyHyu.mjs → temporal-_3yIqdvy.mjs} +1 -1
  60. package/dist/utils/docloader.test.mjs +114 -39
  61. package/dist/utils/kv-cache.test.mjs +1 -1
  62. package/dist/utils/mod.cjs +1 -1
  63. package/dist/utils/mod.js +1 -1
  64. package/package.json +6 -6
  65. /package/dist/{assert-jSdZMYam.mjs → assert-CEO06XCw.mjs} +0 -0
  66. /package/dist/{circuit-breaker-test-utils-Dm5MM6l9.mjs → circuit-breaker-test-utils-CAdwjgMi.mjs} +0 -0
  67. /package/dist/{negotiation-CDW-_gUU.mjs → negotiation-DDstyBvc.mjs} +0 -0
  68. /package/dist/{retry-_VvV0h9f.mjs → retry-CXg_MBI-.mjs} +0 -0
@@ -2,25 +2,7 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { l as AssertionError, s as format } from "./assert_equals-C-ZRDbaf.mjs";
5
- import { n as assertIsError } from "./assert_throws-BOkhLGYc.mjs";
6
- //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_false.js
7
- /**
8
- * Make an assertion, error will be thrown if `expr` have truthy value.
9
- *
10
- * @example Usage
11
- * ```ts no-eval
12
- * import { assertFalse } from "@std/assert/assert-false";
13
- *
14
- * assertFalse(false); // Doesn't throw
15
- * assertFalse(true); // Throws
16
- * ```
17
- *
18
- * @param expr The expression to test.
19
- * @param msg The optional message to display if the assertion fails.
20
- */ function assertFalse(expr, msg = "") {
21
- if (expr) throw new AssertionError(msg);
22
- }
23
- //#endregion
5
+ import { n as assertIsError } from "./assert_throws-BbSMEFKo.mjs";
24
6
  //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_greater_or_equal.js
25
7
  /**
26
8
  * Make an assertion that `actual` is greater than or equal to `expected`.
@@ -79,4 +61,4 @@ async function assertRejects(fn, errorClassOrMsg, msgIncludesOrMsg, msg) {
79
61
  return err;
80
62
  }
81
63
  //#endregion
82
- export { assertGreaterOrEqual as n, assertFalse as r, assertRejects as t };
64
+ export { assertGreaterOrEqual as n, assertRejects as t };
@@ -2,6 +2,24 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { l as AssertionError, o as stripAnsiCode } from "./assert_equals-C-ZRDbaf.mjs";
5
+ //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_false.js
6
+ /**
7
+ * Make an assertion, error will be thrown if `expr` have truthy value.
8
+ *
9
+ * @example Usage
10
+ * ```ts no-eval
11
+ * import { assertFalse } from "@std/assert/assert-false";
12
+ *
13
+ * assertFalse(false); // Doesn't throw
14
+ * assertFalse(true); // Throws
15
+ * ```
16
+ *
17
+ * @param expr The expression to test.
18
+ * @param msg The optional message to display if the assertion fails.
19
+ */ function assertFalse(expr, msg = "") {
20
+ if (expr) throw new AssertionError(msg);
21
+ }
22
+ //#endregion
5
23
  //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_is_error.js
6
24
  /**
7
25
  * Make an assertion that `error` is an `Error`.
@@ -70,4 +88,4 @@ function assertThrows(fn, errorClassOrMsg, msgIncludesOrMsg, msg) {
70
88
  return err;
71
89
  }
72
90
  //#endregion
73
- export { assertIsError as n, assertThrows as t };
91
+ export { assertIsError as n, assertFalse as r, assertThrows 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-D4dcoVfF.mjs";
4
+ import { n as version, t as name } from "./deno-XpcjbHwj.mjs";
5
5
  import { t as ActivityListenerSet } from "./activity-listener-tztVvlNb.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
@@ -81,7 +81,7 @@ var FederationBuilderImpl = class {
81
81
  * specified.
82
82
  */
83
83
  async build(options) {
84
- const { FederationImpl } = await import("./middleware-VxPpU0h_.mjs");
84
+ const { FederationImpl } = await import("./middleware-C3Q3CoR8.mjs");
85
85
  const f = new FederationImpl(options);
86
86
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
87
87
  f.router = this.router.clone();
@@ -2,9 +2,9 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
5
+ import { n as assertInstanceOf, t as assert } from "../assert-CEO06XCw.mjs";
6
6
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
7
- import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-I-XlrRVl.mjs";
7
+ import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-CfbFdEO-.mjs";
8
8
  import { Follow, Person } from "@fedify/vocab";
9
9
  import { test } from "@fedify/fixture";
10
10
  //#region src/compat/transformers.test.ts
@@ -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.3.12";
6
+ var version = "2.3.13";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { s as validateCryptoKey } from "./key-Dg-NXm4K.mjs";
5
- import { n as doubleKnock } from "./http-HjidyDfF.mjs";
4
+ import { s as validateCryptoKey } from "./key-PdEu1lDF.mjs";
5
+ import { n as doubleKnock } from "./http-BRrpIDS1.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
8
8
  import { curry } from "es-toolkit";
@@ -59,6 +59,8 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
59
59
  });
60
60
  }
61
61
  async function validateUrl(url) {
62
+ const protocol = new URL(url).protocol;
63
+ if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
62
64
  if (!allowPrivateAddress) try {
63
65
  await validatePublicUrl(url);
64
66
  } catch (error) {
@@ -2,11 +2,11 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import { n as assertExists } from "../std__assert-BvGuO4xO.mjs";
6
- import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
5
+ import { n as assertExists } from "../std__assert-BjJn2daJ.mjs";
6
+ import { t as assertThrows } from "../assert_throws-BbSMEFKo.mjs";
7
7
  import { t as assertStrictEquals } from "../assert_strict_equals-XEgZAlrj.mjs";
8
8
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
9
- import { r as createFederationBuilder } from "../builder-B6ixdT3y.mjs";
9
+ import { r as createFederationBuilder } from "../builder-BagNDcQw.mjs";
10
10
  import { DisallowedOperatorError, DisallowedVarSpecModifierError, DuplicateRouteVariableError, RouteTemplateOptionsNotMatchedError, RouterError } from "@fedify/uri-template";
11
11
  import { Activity, Note, Person } from "@fedify/vocab";
12
12
  import { test } from "@fedify/fixture";
@@ -2,11 +2,11 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BvGuO4xO.mjs";
6
- import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
5
+ import "../std__assert-BjJn2daJ.mjs";
6
+ import { t as assertThrows } from "../assert_throws-BbSMEFKo.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
8
8
  import { n as normalizeCircuitBreakerOptions, r as parseCircuitBreakerKvState, t as CircuitBreaker } from "../circuit-breaker-D1fxthDI.mjs";
9
- import { t as markCircuitBreakerLegacySweepDone } from "../circuit-breaker-test-utils-Dm5MM6l9.mjs";
9
+ import { t as markCircuitBreakerLegacySweepDone } from "../circuit-breaker-test-utils-CAdwjgMi.mjs";
10
10
  import { test } from "@fedify/fixture";
11
11
  //#region src/federation/circuit-breaker.test.ts
12
12
  var AlwaysConflictingKvStore = class extends MemoryKvStore {
@@ -2,7 +2,7 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BvGuO4xO.mjs";
5
+ import "../std__assert-BjJn2daJ.mjs";
6
6
  import { n as digest, t as buildCollectionSynchronizationHeader } from "../collection-Cc3DVAhE.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  import { decodeHex } from "byte-encodings/hex";
@@ -3,17 +3,17 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-CxbCIptr.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
- import "../std__assert-BvGuO4xO.mjs";
7
- import { n as assertGreaterOrEqual, t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
- import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
6
+ import "../std__assert-BjJn2daJ.mjs";
7
+ import { n as assertGreaterOrEqual, t as assertRejects } from "../assert_rejects-D1HYAik3.mjs";
8
+ import { n as assertInstanceOf, t as assert } from "../assert-CEO06XCw.mjs";
9
9
  import { r as parseAcceptSignature } from "../accept-Dd__NiUL.mjs";
10
- import { a as generateCryptoKeyPair } from "../key-Dg-NXm4K.mjs";
11
- import { s as signRequest } from "../http-HjidyDfF.mjs";
12
- import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-BAK-tUlf.mjs";
10
+ import { a as generateCryptoKeyPair } from "../key-PdEu1lDF.mjs";
11
+ import { s as signRequest } from "../http-BRrpIDS1.mjs";
12
+ import { a as rsaPrivateKey3, c as rsaPublicKey3, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
13
13
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
14
- import { a as compactJsonLd, p as signJsonLd } from "../ld-C1wveXXf.mjs";
15
- import { r as signObject } from "../proof-DtpGEj0u.mjs";
16
- import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-I-XlrRVl.mjs";
14
+ import { a as compactJsonLd, p as signJsonLd } from "../ld-D6UlzNnG.mjs";
15
+ import { r as signObject } from "../proof-mf6h7n4_.mjs";
16
+ import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-CfbFdEO-.mjs";
17
17
  import { t as ActivityListenerSet } from "../activity-listener-tztVvlNb.mjs";
18
18
  import { Activity, Create, CryptographicKey, Multikey, Note, Person, Tombstone } from "@fedify/vocab";
19
19
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -3087,6 +3087,259 @@ test("handleInbox() - authentication bypass vulnerability", async () => {
3087
3087
  assertEquals(await response.text(), "The signer and the actor do not match.");
3088
3088
  assertEquals(processedActivity, void 0, `SECURITY VULNERABILITY: Malicious activity with mismatched signature was processed! Activity ID: ${processedActivity?.id?.href}, Claimed actor: ${processedActivity?.actorId?.href}`);
3089
3089
  });
3090
+ test("handleInbox() rejects cross-origin activity IDs", async () => {
3091
+ const federation = createFederation({ kv: new MemoryKvStore() });
3092
+ const kv = new MemoryKvStore();
3093
+ const inboxListeners = new ActivityListenerSet();
3094
+ let idempotencyCalls = 0;
3095
+ let listenerCalls = 0;
3096
+ inboxListeners.add(Create, () => {
3097
+ listenerCalls++;
3098
+ });
3099
+ const activity = new Create({
3100
+ id: new URL("https://victim.example/activities/1"),
3101
+ actor: rsaPublicKey3.ownerId,
3102
+ object: new Note({ content: "Forged" })
3103
+ });
3104
+ const activityJson = await activity.toJsonLd({
3105
+ format: "compact",
3106
+ contextLoader: mockDocumentLoader
3107
+ });
3108
+ const proofSigned = await signObject(activity, ed25519PrivateKey, ed25519Multikey.id, {
3109
+ contextLoader: mockDocumentLoader,
3110
+ documentLoader: mockDocumentLoader
3111
+ });
3112
+ const requests = [
3113
+ await signRequest(new Request("https://local.example/inbox", {
3114
+ method: "POST",
3115
+ body: JSON.stringify(activityJson)
3116
+ }), rsaPrivateKey3, rsaPublicKey3.id),
3117
+ new Request("https://local.example/inbox", {
3118
+ method: "POST",
3119
+ body: JSON.stringify(await signJsonLd(activityJson, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
3120
+ }),
3121
+ new Request("https://local.example/inbox", {
3122
+ method: "POST",
3123
+ body: JSON.stringify(await proofSigned.toJsonLd({
3124
+ format: "compact",
3125
+ contextLoader: mockDocumentLoader
3126
+ }))
3127
+ }),
3128
+ await signRequest(new Request("https://local.example/inbox", {
3129
+ method: "POST",
3130
+ body: JSON.stringify(await new Create({
3131
+ id: new URL("https://example.com/activities/multiple-actors"),
3132
+ actors: [rsaPublicKey3.ownerId, new URL("https://attacker.example/actor")]
3133
+ }).toJsonLd())
3134
+ }), rsaPrivateKey3, rsaPublicKey3.id)
3135
+ ];
3136
+ for (const request of requests) {
3137
+ const context = createRequestContext({
3138
+ federation,
3139
+ request,
3140
+ url: new URL(request.url),
3141
+ data: void 0,
3142
+ documentLoader: mockDocumentLoader,
3143
+ contextLoader: mockDocumentLoader
3144
+ });
3145
+ const response = await handleInbox(request, {
3146
+ recipient: null,
3147
+ context,
3148
+ inboxContextFactory(_activity) {
3149
+ return createInboxContext({
3150
+ ...context,
3151
+ clone: void 0
3152
+ });
3153
+ },
3154
+ kv,
3155
+ kvPrefixes: {
3156
+ activityIdempotence: ["_fedify", "activityIdempotence"],
3157
+ publicKey: ["_fedify", "publicKey"],
3158
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
3159
+ },
3160
+ actorDispatcher: () => null,
3161
+ inboxListeners,
3162
+ idempotencyStrategy() {
3163
+ idempotencyCalls++;
3164
+ return "forged";
3165
+ },
3166
+ onNotFound: () => new Response("Not found", { status: 404 }),
3167
+ signatureTimeWindow: { minutes: 5 },
3168
+ skipSignatureVerification: false
3169
+ });
3170
+ assertEquals([response.status, await response.text()], [401, "The activity and its actors do not have the same origin."]);
3171
+ }
3172
+ assertEquals(idempotencyCalls, 0);
3173
+ assertEquals(listenerCalls, 0);
3174
+ });
3175
+ test("handleInbox() preserves the signature-verification opt-out", async () => {
3176
+ const federation = createFederation({ kv: new MemoryKvStore() });
3177
+ const activity = new Create({
3178
+ id: new URL("https://victim.example/activities/1"),
3179
+ actor: new URL("https://attacker.example/actor")
3180
+ });
3181
+ const request = new Request("https://local.example/inbox", {
3182
+ method: "POST",
3183
+ body: JSON.stringify(await activity.toJsonLd())
3184
+ });
3185
+ const context = createRequestContext({
3186
+ federation,
3187
+ request,
3188
+ url: new URL(request.url),
3189
+ data: void 0,
3190
+ documentLoader: mockDocumentLoader
3191
+ });
3192
+ let listenerCalls = 0;
3193
+ const inboxListeners = new ActivityListenerSet();
3194
+ inboxListeners.add(Create, () => {
3195
+ listenerCalls++;
3196
+ });
3197
+ assertEquals((await handleInbox(request, {
3198
+ recipient: null,
3199
+ context,
3200
+ inboxContextFactory(_activity) {
3201
+ return createInboxContext({
3202
+ ...context,
3203
+ clone: void 0
3204
+ });
3205
+ },
3206
+ kv: new MemoryKvStore(),
3207
+ kvPrefixes: {
3208
+ activityIdempotence: ["_fedify", "activityIdempotence"],
3209
+ publicKey: ["_fedify", "publicKey"],
3210
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
3211
+ },
3212
+ actorDispatcher: () => null,
3213
+ inboxListeners,
3214
+ onNotFound: () => new Response("Not found", { status: 404 }),
3215
+ signatureTimeWindow: { minutes: 5 },
3216
+ skipSignatureVerification: true
3217
+ })).status, 202);
3218
+ assertEquals(listenerCalls, 1);
3219
+ });
3220
+ test("handleInbox() rejects missing actors before idempotency", async () => {
3221
+ const federation = createFederation({ kv: new MemoryKvStore() });
3222
+ const unsignedJson = {
3223
+ "@context": "https://www.w3.org/ns/activitystreams",
3224
+ id: "https://remote.example/activities/actorless",
3225
+ type: "Create"
3226
+ };
3227
+ const requests = [new Request("https://local.example/inbox", {
3228
+ method: "POST",
3229
+ body: JSON.stringify(unsignedJson)
3230
+ }), new Request("https://local.example/inbox", {
3231
+ method: "POST",
3232
+ body: JSON.stringify(await signJsonLd(unsignedJson, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
3233
+ })];
3234
+ let idempotencyCalls = 0;
3235
+ for (const request of requests) {
3236
+ const context = createRequestContext({
3237
+ federation,
3238
+ request,
3239
+ url: new URL(request.url),
3240
+ data: void 0,
3241
+ documentLoader: mockDocumentLoader,
3242
+ contextLoader: mockDocumentLoader
3243
+ });
3244
+ const response = await handleInbox(request, {
3245
+ recipient: null,
3246
+ context,
3247
+ inboxContextFactory(_activity) {
3248
+ return createInboxContext({
3249
+ ...context,
3250
+ clone: void 0
3251
+ });
3252
+ },
3253
+ kv: new MemoryKvStore(),
3254
+ kvPrefixes: {
3255
+ activityIdempotence: ["_fedify", "activityIdempotence"],
3256
+ publicKey: ["_fedify", "publicKey"],
3257
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
3258
+ },
3259
+ actorDispatcher: () => null,
3260
+ idempotencyStrategy() {
3261
+ idempotencyCalls++;
3262
+ return "actorless";
3263
+ },
3264
+ onNotFound: () => new Response("Not found", { status: 404 }),
3265
+ signatureTimeWindow: { minutes: 5 },
3266
+ skipSignatureVerification: false
3267
+ });
3268
+ assertEquals([response.status, await response.text()], [400, "Missing actor."]);
3269
+ }
3270
+ assertEquals(idempotencyCalls, 0);
3271
+ });
3272
+ test("handleInbox() leaves a rejected activity ID available", async () => {
3273
+ const activityId = new URL("https://example.com/activities/genuine");
3274
+ const attackerId = new URL("https://attacker.example/actor");
3275
+ const attackerKeyId = new URL(`${attackerId.href}#main-key`);
3276
+ const attackerKeys = await generateCryptoKeyPair("RSASSA-PKCS1-v1_5");
3277
+ const attackerDocument = await new Person({
3278
+ id: attackerId,
3279
+ publicKey: new CryptographicKey({
3280
+ id: attackerKeyId,
3281
+ owner: attackerId,
3282
+ publicKey: attackerKeys.publicKey
3283
+ })
3284
+ }).toJsonLd({ contextLoader: mockDocumentLoader });
3285
+ const documentLoader = (resource) => resource === attackerId.href || resource === attackerKeyId.href ? Promise.resolve({
3286
+ contextUrl: null,
3287
+ documentUrl: resource,
3288
+ document: attackerDocument
3289
+ }) : mockDocumentLoader(resource);
3290
+ for (const strategy of ["per-inbox", "global"]) {
3291
+ const kv = new MemoryKvStore();
3292
+ const federation = createFederation({ kv });
3293
+ const inboxListeners = new ActivityListenerSet();
3294
+ const received = [];
3295
+ inboxListeners.add(Create, (_ctx, activity) => {
3296
+ received.push(activity.actorId.href);
3297
+ });
3298
+ const handle = async (request) => {
3299
+ const context = createRequestContext({
3300
+ federation,
3301
+ request,
3302
+ url: new URL(request.url),
3303
+ data: void 0,
3304
+ documentLoader,
3305
+ contextLoader: mockDocumentLoader
3306
+ });
3307
+ return await handleInbox(request, {
3308
+ recipient: null,
3309
+ context,
3310
+ inboxContextFactory(_activity) {
3311
+ return createInboxContext({
3312
+ ...context,
3313
+ clone: void 0
3314
+ });
3315
+ },
3316
+ kv,
3317
+ kvPrefixes: {
3318
+ activityIdempotence: ["_fedify", "activityIdempotence"],
3319
+ publicKey: ["_fedify", "publicKey"],
3320
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
3321
+ },
3322
+ actorDispatcher: () => null,
3323
+ inboxListeners,
3324
+ idempotencyStrategy: strategy,
3325
+ onNotFound: () => new Response("Not found", { status: 404 }),
3326
+ signatureTimeWindow: { minutes: 5 },
3327
+ skipSignatureVerification: false
3328
+ });
3329
+ };
3330
+ const createRequest = async (actor, privateKey, keyId) => await signRequest(new Request("https://local.example/inbox", {
3331
+ method: "POST",
3332
+ body: JSON.stringify(await new Create({
3333
+ id: activityId,
3334
+ actor
3335
+ }).toJsonLd())
3336
+ }), privateKey, keyId);
3337
+ const forged = await handle(await createRequest(attackerId, attackerKeys.privateKey, attackerKeyId));
3338
+ const genuine = await handle(await createRequest(rsaPublicKey3.ownerId, rsaPrivateKey3, rsaPublicKey3.id));
3339
+ assertEquals([forged.status, genuine.status], [401, 202]);
3340
+ assertEquals(received, [rsaPublicKey3.ownerId.href]);
3341
+ }
3342
+ });
3090
3343
  test("respondWithObjectIfAcceptable", async () => {
3091
3344
  let request = new Request("https://example.com/", { headers: { Accept: "application/activity+json" } });
3092
3345
  let response = await respondWithObjectIfAcceptable(new Note({
@@ -2,11 +2,11 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BvGuO4xO.mjs";
5
+ import "../std__assert-BjJn2daJ.mjs";
6
6
  import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
8
- import { r as signObject } from "../proof-DtpGEj0u.mjs";
9
- import { o as createFederation } from "../middleware-I-XlrRVl.mjs";
8
+ import { r as signObject } from "../proof-mf6h7n4_.mjs";
9
+ import { o as createFederation } from "../middleware-CfbFdEO-.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,11 +2,35 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
5
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-BbSMEFKo.mjs";
6
6
  import { t as ActivityListenerSet } from "../activity-listener-tztVvlNb.mjs";
7
+ import { t as actorsHaveSameOrigin } from "../inbox-BzOuCPLY.mjs";
7
8
  import { Activity, Create, Invite, Offer, Update } from "@fedify/vocab";
8
9
  import { test } from "@fedify/fixture";
9
10
  //#region src/federation/inbox.test.ts
11
+ test("actorsHaveSameOrigin()", () => {
12
+ assertEquals(actorsHaveSameOrigin(new Create({ actor: new URL("https://remote.example/actor") })), true);
13
+ assertEquals(actorsHaveSameOrigin(new Create({
14
+ id: new URL("https://remote.example/activity"),
15
+ actors: [new URL("https://remote.example/actor/1"), new URL("https://remote.example:443/actor/2")]
16
+ })), true);
17
+ for (const actor of [
18
+ "http://remote.example/actor",
19
+ "https://sub.remote.example/actor",
20
+ "https://remote.example:8443/actor"
21
+ ]) assertFalse(actorsHaveSameOrigin(new Create({
22
+ id: new URL("https://remote.example/activity"),
23
+ actor: new URL(actor)
24
+ })));
25
+ assertFalse(actorsHaveSameOrigin(new Create({
26
+ id: new URL("urn:example:activity"),
27
+ actor: new URL("urn:example:actor")
28
+ })));
29
+ assertFalse(actorsHaveSameOrigin(new Create({
30
+ id: new URL("https://remote.example/activity"),
31
+ actors: [new URL("https://remote.example/actor/1"), new URL("https://other.example/actor/2")]
32
+ })));
33
+ });
10
34
  test("ActivityListenerSet", () => {
11
35
  const listeners = new ActivityListenerSet();
12
36
  const activity = new Activity({});
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
5
+ import { n as assertInstanceOf, t as assert } from "../assert-CEO06XCw.mjs";
6
6
  import { t as KvKeyCache } from "../keycache-Dkc_sflV.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
8
8
  import { CryptographicKey, Multikey } from "@fedify/vocab";
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BvGuO4xO.mjs";
5
+ import "../std__assert-BjJn2daJ.mjs";
6
6
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  //#region src/federation/kv.test.ts
@@ -2,9 +2,9 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BvGuO4xO.mjs";
6
- import { t as assertRejects } from "../assert_rejects-DN60FHPX.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-cIaMxG0v.mjs";
5
+ import "../std__assert-BjJn2daJ.mjs";
6
+ import { t as assertRejects } from "../assert_rejects-D1HYAik3.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-62qolVUY.mjs";
8
8
  import { createTestMeterProvider, test } from "@fedify/fixture";
9
9
  import { FetchError } from "@fedify/vocab-runtime";
10
10
  import { DataPointType, MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";