@fedify/fedify 2.2.11 → 2.2.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 (102) hide show
  1. package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert-BhQd8QHP.mjs} +19 -1
  2. package/dist/{builder-8d-PNxcc.mjs → builder-uTlVksLw.mjs} +3 -3
  3. package/dist/{client-dx126nMS.mjs → client-CQ7zd8tW.mjs} +78 -4
  4. package/dist/compat/mod.d.cts +1 -1
  5. package/dist/compat/mod.d.ts +1 -1
  6. package/dist/compat/outgoing-jsonld.test.mjs +1 -1
  7. package/dist/compat/public-audience.test.mjs +1 -1
  8. package/dist/compat/transformers.test.mjs +3 -4
  9. package/dist/{context-BU-1O90h.d.ts → context-Bt2jnbC7.d.ts} +4 -3
  10. package/dist/{context-DVA8wHZ0.d.cts → context-Cls_M1HU.d.cts} +4 -3
  11. package/dist/{deno-BdneH5EA.mjs → deno-Cg_cZHcI.mjs} +1 -1
  12. package/dist/{docloader-dW-3XcQa.mjs → docloader-BwXP5fDH.mjs} +2 -2
  13. package/dist/federation/builder.test.mjs +2 -2
  14. package/dist/federation/collection.test.mjs +1 -1
  15. package/dist/federation/handler.test.mjs +127 -10
  16. package/dist/federation/idempotency.test.mjs +4 -4
  17. package/dist/federation/inbox.test.mjs +1 -1
  18. package/dist/federation/keycache.test.mjs +44 -10
  19. package/dist/federation/kv.test.mjs +1 -1
  20. package/dist/federation/middleware.test.mjs +103 -15
  21. package/dist/federation/mod.cjs +1 -1
  22. package/dist/federation/mod.d.cts +2 -2
  23. package/dist/federation/mod.d.ts +2 -2
  24. package/dist/federation/mod.js +1 -1
  25. package/dist/federation/mq.test.mjs +1 -1
  26. package/dist/federation/negotiation.test.mjs +2 -2
  27. package/dist/federation/retry.test.mjs +1 -1
  28. package/dist/federation/router.test.mjs +1 -1
  29. package/dist/federation/send.test.mjs +138 -6
  30. package/dist/federation/temporal.test.mjs +2 -2
  31. package/dist/federation/webfinger.test.mjs +2 -2
  32. package/dist/{http-DcjN1Tul.mjs → http-B3V9unT4.mjs} +3 -3
  33. package/dist/{http-CVNTMjI8.cjs → http-BdbKIiay.cjs} +166 -6
  34. package/dist/{http-dst14yhu.js → http-DaLdtguq.js} +155 -7
  35. package/dist/{http-BDZeS5om.d.ts → http-Dl00HXr4.d.ts} +1 -1
  36. package/dist/{http-C87EWkO0.d.cts → http-DqZhyEso.d.cts} +1 -1
  37. package/dist/{key-C6EM8eSE.mjs → key-CLRfRmA_.mjs} +154 -6
  38. package/dist/{keycache-EGATflN-.mjs → keycache-C1aVprwy.mjs} +12 -4
  39. package/dist/{kv-cache-CSs1SO_n.cjs → kv-cache-K6eHO_YH.cjs} +1 -1
  40. package/dist/{kv-cache-Dg1p1urK.js → kv-cache-TPuR5wYg.js} +1 -1
  41. package/dist/{ld-Ci6Z4_tn.mjs → ld-C0s4f-GU.mjs} +2 -2
  42. package/dist/{middleware-DdSIe9K3.js → middleware-BDRZkGPG.js} +90 -14
  43. package/dist/{middleware-BVBL4Ug9.cjs → middleware-BDc7xyOx.cjs} +90 -14
  44. package/dist/{middleware-D7VPt6Mr.mjs → middleware-BsDiZP9H.mjs} +31 -20
  45. package/dist/{middleware-iVyNebWP.cjs → middleware-D6bGiZn6.cjs} +1 -1
  46. package/dist/{middleware-cU-cDO0w.mjs → middleware-uaOjlLEG.mjs} +1 -1
  47. package/dist/{mod-BhU_H1I_.d.ts → mod-0MWo2Nu5.d.ts} +1 -1
  48. package/dist/{mod-DXY9JF28.d.cts → mod-D2JyWtW3.d.cts} +1 -1
  49. package/dist/{mod-q-NFLW6B.d.cts → mod-DNvnDTS7.d.cts} +1 -1
  50. package/dist/{mod-BVt6iTmH.d.ts → mod-DaiJDJdQ.d.ts} +1 -1
  51. package/dist/{mod-B0rWmfW5.d.cts → mod-DzPMtIV3.d.cts} +1 -1
  52. package/dist/{mod-DHO9lk3D.d.ts → mod-ej_rLx8h.d.ts} +1 -1
  53. package/dist/mod.cjs +5 -5
  54. package/dist/mod.d.cts +6 -6
  55. package/dist/mod.d.ts +6 -6
  56. package/dist/mod.js +5 -5
  57. package/dist/nodeinfo/client.test.mjs +28 -1
  58. package/dist/nodeinfo/handler.test.mjs +2 -2
  59. package/dist/nodeinfo/mod.cjs +1 -1
  60. package/dist/nodeinfo/mod.js +1 -1
  61. package/dist/nodeinfo/types.test.mjs +1 -1
  62. package/dist/otel/exporter.test.mjs +1 -1
  63. package/dist/{outgoing-jsonld-CfUItkGW.mjs → outgoing-jsonld-Dc2OXyB-.mjs} +1 -1
  64. package/dist/{owner-CnngXDNJ.d.ts → owner-B3hNQCG8.d.cts} +12 -1
  65. package/dist/{owner-DEvZuyOE.d.cts → owner-CiBPcC6F.d.ts} +12 -1
  66. package/dist/owner-DLbN_4jZ.mjs +175 -0
  67. package/dist/{proof-DMMFfNsL.cjs → proof-CTIG9UEI.cjs} +112 -58
  68. package/dist/{proof-BEwrQa7q.mjs → proof-CW3Zrxmd.mjs} +4 -4
  69. package/dist/{proof-nHHslj31.js → proof-CqyprEYB.js} +112 -58
  70. package/dist/{send-B1XNYk6n.mjs → send-B5QvdrZ1.mjs} +64 -6
  71. package/dist/sig/accept.test.mjs +1 -1
  72. package/dist/sig/http.test.mjs +4 -4
  73. package/dist/sig/key.test.mjs +170 -3
  74. package/dist/sig/ld.test.mjs +56 -4
  75. package/dist/sig/mod.cjs +2 -2
  76. package/dist/sig/mod.d.cts +3 -3
  77. package/dist/sig/mod.d.ts +3 -3
  78. package/dist/sig/mod.js +2 -2
  79. package/dist/sig/owner.test.mjs +105 -6
  80. package/dist/sig/proof.test.mjs +73 -5
  81. package/dist/{temporal-DaRJ05za.mjs → temporal-CFLRXAcm.mjs} +1 -1
  82. package/dist/{types-m8u9L86X.cjs → types-DWs-qZaY.cjs} +94 -2
  83. package/dist/{types-DTONwNqL.js → types-sXITXcWa.js} +78 -4
  84. package/dist/utils/docloader.test.mjs +28 -4
  85. package/dist/utils/kv-cache.test.mjs +1 -1
  86. package/dist/utils/mod.cjs +1 -1
  87. package/dist/utils/mod.d.cts +1 -1
  88. package/dist/utils/mod.d.ts +1 -1
  89. package/dist/utils/mod.js +1 -1
  90. package/package.json +5 -5
  91. package/dist/assert-DikXweDx.mjs +0 -23
  92. package/dist/owner-ByUYl7Xn.mjs +0 -120
  93. /package/dist/{accept-CPkZzmGN.mjs → accept-Dd__NiUL.mjs} +0 -0
  94. /package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-IojJ9SVl.mjs} +0 -0
  95. /package/dist/{collection-D-HqUuA2.mjs → collection-DnoYNwBC.mjs} +0 -0
  96. /package/dist/{keys-DGu1NFwu.mjs → keys-BAK-tUlf.mjs} +0 -0
  97. /package/dist/{kv-rV3vodCc.mjs → kv-CX8qfaad.mjs} +0 -0
  98. /package/dist/{kv-cache-DYsF2MhP.mjs → kv-cache-FsT1ljhs.mjs} +0 -0
  99. /package/dist/{negotiation-SQvQgUqe.mjs → negotiation-BHtZN6Y1.mjs} +0 -0
  100. /package/dist/{public-audience-Ci-r-4Q-.mjs → public-audience-CS1udGkN.mjs} +0 -0
  101. /package/dist/{retry-v_sGLH1d.mjs → retry-bMXBL97A.mjs} +0 -0
  102. /package/dist/{types-J53Kw7so.mjs → types-s4mCTGkT.mjs} +0 -0
@@ -3,6 +3,7 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { CryptographicKey, Multikey } from "@fedify/vocab";
5
5
  //#region src/federation/keycache.ts
6
+ const KEY_CACHE_GENERATION = "2";
6
7
  var KvKeyCache = class {
7
8
  kv;
8
9
  prefix;
@@ -23,13 +24,20 @@ var KvKeyCache = class {
23
24
  keyId.href
24
25
  ];
25
26
  }
27
+ #entryKey(keyId) {
28
+ return [
29
+ ...this.prefix,
30
+ KEY_CACHE_GENERATION,
31
+ keyId.href
32
+ ];
33
+ }
26
34
  async get(keyId) {
27
35
  const negativeExpiration = this.nullKeys.get(keyId.href);
28
36
  if (negativeExpiration != null) {
29
37
  if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
30
38
  this.nullKeys.delete(keyId.href);
31
39
  }
32
- const serialized = await this.kv.get([...this.prefix, keyId.href]);
40
+ const serialized = await this.kv.get(this.#entryKey(keyId));
33
41
  if (serialized === void 0) return void 0;
34
42
  if (serialized === null) {
35
43
  this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
@@ -41,7 +49,7 @@ var KvKeyCache = class {
41
49
  try {
42
50
  return await Multikey.fromJsonLd(serialized, this.options);
43
51
  } catch {
44
- await this.kv.delete([...this.prefix, keyId.href]);
52
+ await this.kv.delete(this.#entryKey(keyId));
45
53
  return;
46
54
  }
47
55
  }
@@ -49,12 +57,12 @@ var KvKeyCache = class {
49
57
  async set(keyId, key) {
50
58
  if (key == null) {
51
59
  this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
52
- await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
60
+ await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
53
61
  return;
54
62
  }
55
63
  this.nullKeys.delete(keyId.href);
56
64
  const serialized = await key.toJsonLd(this.options);
57
- await this.kv.set([...this.prefix, keyId.href], serialized);
65
+ await this.kv.set(this.#entryKey(keyId), serialized);
58
66
  }
59
67
  async getFetchError(keyId) {
60
68
  const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
@@ -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-CVNTMjI8.cjs");
4
+ const require_http = require("./http-BdbKIiay.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");
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { d as validateCryptoKey, t as doubleKnock } from "./http-dst14yhu.js";
3
+ import { f as validateCryptoKey, t as doubleKnock } from "./http-DaLdtguq.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
6
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -1,8 +1,8 @@
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-BdneH5EA.mjs";
5
- import { n as fetchKey, o as validateCryptoKey } from "./key-C6EM8eSE.mjs";
4
+ import { n as version, t as name } from "./deno-Cg_cZHcI.mjs";
5
+ import { r as fetchKey, s as validateCryptoKey } from "./key-CLRfRmA_.mjs";
6
6
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-dst14yhu.js";
6
- import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-nHHslj31.js";
7
- import { n as getNodeInfo, t as nodeInfoToJson } from "./types-DTONwNqL.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-Dg1p1urK.js";
5
+ import { a as verifyRequestDetailed, d as importJwk, f as validateCryptoKey, i as verifyRequest, m as formatAcceptSignature, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as name, y as version } from "./http-DaLdtguq.js";
6
+ import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-sXITXcWa.js";
7
+ import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-CqyprEYB.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-TPuR5wYg.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -13,7 +13,7 @@ import { cloneDeep, uniq } from "es-toolkit";
13
13
  import { Router } from "uri-template-router";
14
14
  import { parseTemplate } from "url-template";
15
15
  import { encodeHex } from "byte-encodings/hex";
16
- import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
16
+ import { FetchError, UrlError, getDocumentLoader, validatePublicUrl } from "@fedify/vocab-runtime";
17
17
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
18
18
  import jsonld from "@fedify/vocab-runtime/jsonld";
19
19
  import { lookupWebFinger } from "@fedify/webfinger";
@@ -914,6 +914,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
914
914
  }
915
915
  //#endregion
916
916
  //#region src/federation/keycache.ts
917
+ const KEY_CACHE_GENERATION = "2";
917
918
  var KvKeyCache = class {
918
919
  kv;
919
920
  prefix;
@@ -934,13 +935,20 @@ var KvKeyCache = class {
934
935
  keyId.href
935
936
  ];
936
937
  }
938
+ #entryKey(keyId) {
939
+ return [
940
+ ...this.prefix,
941
+ KEY_CACHE_GENERATION,
942
+ keyId.href
943
+ ];
944
+ }
937
945
  async get(keyId) {
938
946
  const negativeExpiration = this.nullKeys.get(keyId.href);
939
947
  if (negativeExpiration != null) {
940
948
  if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
941
949
  this.nullKeys.delete(keyId.href);
942
950
  }
943
- const serialized = await this.kv.get([...this.prefix, keyId.href]);
951
+ const serialized = await this.kv.get(this.#entryKey(keyId));
944
952
  if (serialized === void 0) return void 0;
945
953
  if (serialized === null) {
946
954
  this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
@@ -952,7 +960,7 @@ var KvKeyCache = class {
952
960
  try {
953
961
  return await Multikey.fromJsonLd(serialized, this.options);
954
962
  } catch {
955
- await this.kv.delete([...this.prefix, keyId.href]);
963
+ await this.kv.delete(this.#entryKey(keyId));
956
964
  return;
957
965
  }
958
966
  }
@@ -960,12 +968,12 @@ var KvKeyCache = class {
960
968
  async set(keyId, key) {
961
969
  if (key == null) {
962
970
  this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
963
- await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
971
+ await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
964
972
  return;
965
973
  }
966
974
  this.nullKeys.delete(keyId.href);
967
975
  const serialized = await key.toJsonLd(this.options);
968
- await this.kv.set([...this.prefix, keyId.href], serialized);
976
+ await this.kv.set(this.#entryKey(keyId), serialized);
969
977
  }
970
978
  async getFetchError(keyId) {
971
979
  const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
@@ -1686,8 +1694,16 @@ async function handleInboxInternal(request, parameters, span) {
1686
1694
  }
1687
1695
  let json;
1688
1696
  try {
1689
- json = await request.clone().json();
1697
+ json = JSON.parse(await readBoundedText(request.clone(), MAX_BODY_SIZE, request.url));
1690
1698
  } catch (error) {
1699
+ if (error instanceof BodyTooLargeError) {
1700
+ request.body?.cancel(error).catch(() => {});
1701
+ span.setStatus({
1702
+ code: SpanStatusCode.ERROR,
1703
+ message: String(error)
1704
+ });
1705
+ return new Response("Inbox body too large.", { status: 413 });
1706
+ }
1691
1707
  logger.error("Failed to parse JSON:\n{error}", {
1692
1708
  recipient,
1693
1709
  error
@@ -2704,18 +2720,31 @@ async function readLimitedResponseBody(response, maxBytes) {
2704
2720
  if (truncated) result += "… (truncated)";
2705
2721
  return result;
2706
2722
  }
2707
- async function sendActivityInternal({ activity, activityId, keys, inbox, headers, specDeterminer, tracerProvider }, span) {
2723
+ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPrivateAddress, headers, specDeterminer, tracerProvider }, span) {
2708
2724
  const logger = getLogger([
2709
2725
  "fedify",
2710
2726
  "federation",
2711
2727
  "outbox"
2712
2728
  ]);
2729
+ async function validateUrl(url) {
2730
+ if (!allowPrivateAddress) try {
2731
+ await validatePublicUrl(url);
2732
+ } catch (error) {
2733
+ if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
2734
+ url,
2735
+ error
2736
+ });
2737
+ throw error;
2738
+ }
2739
+ }
2740
+ await validateUrl(inbox.href);
2713
2741
  headers = new Headers(headers);
2714
2742
  headers.set("Content-Type", "application/activity+json");
2715
2743
  const request = new Request(inbox, {
2716
2744
  method: "POST",
2717
2745
  headers,
2718
- body: JSON.stringify(activity)
2746
+ body: JSON.stringify(activity),
2747
+ redirect: "manual"
2719
2748
  });
2720
2749
  let rsaKey = null;
2721
2750
  for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
@@ -2731,9 +2760,10 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, headers
2731
2760
  });
2732
2761
  let response;
2733
2762
  try {
2734
- response = rsaKey == null ? await fetch(request) : await doubleKnock(request, rsaKey, {
2763
+ response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await doubleKnock(request, rsaKey, {
2735
2764
  tracerProvider,
2736
- specDeterminer
2765
+ specDeterminer,
2766
+ validateRedirect: validateUrl
2737
2767
  });
2738
2768
  } catch (error) {
2739
2769
  logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
@@ -2765,6 +2795,49 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, headers
2765
2795
  "activitypub.activity.id": activityId ?? ""
2766
2796
  });
2767
2797
  }
2798
+ async function fetchWithValidatedRedirects(request, validateUrl) {
2799
+ let body = await request.clone().text();
2800
+ for (let redirects = 0;; redirects++) {
2801
+ const response = await fetch(request, { redirect: "manual" });
2802
+ const location = response.headers.get("Location");
2803
+ if (![
2804
+ 301,
2805
+ 302,
2806
+ 303,
2807
+ 307,
2808
+ 308
2809
+ ].includes(response.status) || location == null) return response;
2810
+ await response.body?.cancel();
2811
+ if (redirects >= 20) throw new TypeError("Too many redirects");
2812
+ const url = new URL(location, request.url);
2813
+ await validateUrl(url.href);
2814
+ const headers = new Headers(request.headers);
2815
+ if (url.origin !== new URL(request.url).origin) {
2816
+ headers.delete("Authorization");
2817
+ headers.delete("Proxy-Authorization");
2818
+ headers.delete("Cookie");
2819
+ headers.delete("Host");
2820
+ }
2821
+ let method = request.method;
2822
+ if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
2823
+ method = "GET";
2824
+ body = void 0;
2825
+ for (const name of [
2826
+ "Content-Encoding",
2827
+ "Content-Language",
2828
+ "Content-Length",
2829
+ "Content-Location",
2830
+ "Content-Type"
2831
+ ]) headers.delete(name);
2832
+ }
2833
+ request = new Request(url, {
2834
+ method,
2835
+ headers,
2836
+ body,
2837
+ redirect: "manual"
2838
+ });
2839
+ }
2840
+ }
2768
2841
  /**
2769
2842
  * An error that is thrown when an activity fails to send to a remote inbox.
2770
2843
  * It contains structured information about the failure, including the HTTP
@@ -3279,6 +3352,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3279
3352
  }
3280
3353
  try {
3281
3354
  await sendActivity({
3355
+ allowPrivateAddress: this.allowPrivateAddress,
3282
3356
  keys,
3283
3357
  activity: message.activity,
3284
3358
  activityId: message.activityId,
@@ -3673,6 +3747,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3673
3747
  });
3674
3748
  const promises = [];
3675
3749
  for (const inbox in inboxes) promises.push(sendActivity({
3750
+ allowPrivateAddress: this.allowPrivateAddress,
3676
3751
  keys,
3677
3752
  activity: jsonLd,
3678
3753
  activityId: activity.id?.href,
@@ -4855,6 +4930,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4855
4930
  else logger.debug("Forwarding activity immediately without queue since queue is not set.");
4856
4931
  const promises = [];
4857
4932
  for (const inbox in inboxes) promises.push(sendActivity({
4933
+ allowPrivateAddress: ctx.federation.allowPrivateAddress,
4858
4934
  keys,
4859
4935
  activity: ctx.activity,
4860
4936
  activityId: ctx.activityId,
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-CVNTMjI8.cjs");
6
- const require_proof = require("./proof-DMMFfNsL.cjs");
7
- const require_types = require("./types-m8u9L86X.cjs");
8
- const require_kv_cache = require("./kv-cache-CSs1SO_n.cjs");
5
+ const require_http = require("./http-BdbKIiay.cjs");
6
+ const require_types = require("./types-DWs-qZaY.cjs");
7
+ const require_proof = require("./proof-CTIG9UEI.cjs");
8
+ const require_kv_cache = require("./kv-cache-K6eHO_YH.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -212,7 +212,7 @@ var FederationBuilderImpl = class {
212
212
  this.collectionTypeIds = {};
213
213
  }
214
214
  async build(options) {
215
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-iVyNebWP.cjs"));
215
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-D6bGiZn6.cjs"));
216
216
  const f = new FederationImpl(options);
217
217
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
218
218
  f.router = this.router.clone();
@@ -915,6 +915,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
915
915
  }
916
916
  //#endregion
917
917
  //#region src/federation/keycache.ts
918
+ const KEY_CACHE_GENERATION = "2";
918
919
  var KvKeyCache = class {
919
920
  kv;
920
921
  prefix;
@@ -935,13 +936,20 @@ var KvKeyCache = class {
935
936
  keyId.href
936
937
  ];
937
938
  }
939
+ #entryKey(keyId) {
940
+ return [
941
+ ...this.prefix,
942
+ KEY_CACHE_GENERATION,
943
+ keyId.href
944
+ ];
945
+ }
938
946
  async get(keyId) {
939
947
  const negativeExpiration = this.nullKeys.get(keyId.href);
940
948
  if (negativeExpiration != null) {
941
949
  if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
942
950
  this.nullKeys.delete(keyId.href);
943
951
  }
944
- const serialized = await this.kv.get([...this.prefix, keyId.href]);
952
+ const serialized = await this.kv.get(this.#entryKey(keyId));
945
953
  if (serialized === void 0) return void 0;
946
954
  if (serialized === null) {
947
955
  this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
@@ -953,7 +961,7 @@ var KvKeyCache = class {
953
961
  try {
954
962
  return await _fedify_vocab.Multikey.fromJsonLd(serialized, this.options);
955
963
  } catch {
956
- await this.kv.delete([...this.prefix, keyId.href]);
964
+ await this.kv.delete(this.#entryKey(keyId));
957
965
  return;
958
966
  }
959
967
  }
@@ -961,12 +969,12 @@ var KvKeyCache = class {
961
969
  async set(keyId, key) {
962
970
  if (key == null) {
963
971
  this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
964
- await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
972
+ await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
965
973
  return;
966
974
  }
967
975
  this.nullKeys.delete(keyId.href);
968
976
  const serialized = await key.toJsonLd(this.options);
969
- await this.kv.set([...this.prefix, keyId.href], serialized);
977
+ await this.kv.set(this.#entryKey(keyId), serialized);
970
978
  }
971
979
  async getFetchError(keyId) {
972
980
  const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
@@ -1687,8 +1695,16 @@ async function handleInboxInternal(request, parameters, span) {
1687
1695
  }
1688
1696
  let json;
1689
1697
  try {
1690
- json = await request.clone().json();
1698
+ json = JSON.parse(await require_types.readBoundedText(request.clone(), require_types.MAX_BODY_SIZE, request.url));
1691
1699
  } catch (error) {
1700
+ if (error instanceof require_types.BodyTooLargeError) {
1701
+ request.body?.cancel(error).catch(() => {});
1702
+ span.setStatus({
1703
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1704
+ message: String(error)
1705
+ });
1706
+ return new Response("Inbox body too large.", { status: 413 });
1707
+ }
1692
1708
  logger.error("Failed to parse JSON:\n{error}", {
1693
1709
  recipient,
1694
1710
  error
@@ -2705,18 +2721,31 @@ async function readLimitedResponseBody(response, maxBytes) {
2705
2721
  if (truncated) result += "… (truncated)";
2706
2722
  return result;
2707
2723
  }
2708
- async function sendActivityInternal({ activity, activityId, keys, inbox, headers, specDeterminer, tracerProvider }, span) {
2724
+ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPrivateAddress, headers, specDeterminer, tracerProvider }, span) {
2709
2725
  const logger = (0, _logtape_logtape.getLogger)([
2710
2726
  "fedify",
2711
2727
  "federation",
2712
2728
  "outbox"
2713
2729
  ]);
2730
+ async function validateUrl(url) {
2731
+ if (!allowPrivateAddress) try {
2732
+ await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
2733
+ } catch (error) {
2734
+ if (error instanceof _fedify_vocab_runtime.UrlError) logger.error("Disallowed private URL: {url}", {
2735
+ url,
2736
+ error
2737
+ });
2738
+ throw error;
2739
+ }
2740
+ }
2741
+ await validateUrl(inbox.href);
2714
2742
  headers = new Headers(headers);
2715
2743
  headers.set("Content-Type", "application/activity+json");
2716
2744
  const request = new Request(inbox, {
2717
2745
  method: "POST",
2718
2746
  headers,
2719
- body: JSON.stringify(activity)
2747
+ body: JSON.stringify(activity),
2748
+ redirect: "manual"
2720
2749
  });
2721
2750
  let rsaKey = null;
2722
2751
  for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
@@ -2732,9 +2761,10 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, headers
2732
2761
  });
2733
2762
  let response;
2734
2763
  try {
2735
- response = rsaKey == null ? await fetch(request) : await require_http.doubleKnock(request, rsaKey, {
2764
+ response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await require_http.doubleKnock(request, rsaKey, {
2736
2765
  tracerProvider,
2737
- specDeterminer
2766
+ specDeterminer,
2767
+ validateRedirect: validateUrl
2738
2768
  });
2739
2769
  } catch (error) {
2740
2770
  logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
@@ -2766,6 +2796,49 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, headers
2766
2796
  "activitypub.activity.id": activityId ?? ""
2767
2797
  });
2768
2798
  }
2799
+ async function fetchWithValidatedRedirects(request, validateUrl) {
2800
+ let body = await request.clone().text();
2801
+ for (let redirects = 0;; redirects++) {
2802
+ const response = await fetch(request, { redirect: "manual" });
2803
+ const location = response.headers.get("Location");
2804
+ if (![
2805
+ 301,
2806
+ 302,
2807
+ 303,
2808
+ 307,
2809
+ 308
2810
+ ].includes(response.status) || location == null) return response;
2811
+ await response.body?.cancel();
2812
+ if (redirects >= 20) throw new TypeError("Too many redirects");
2813
+ const url = new URL(location, request.url);
2814
+ await validateUrl(url.href);
2815
+ const headers = new Headers(request.headers);
2816
+ if (url.origin !== new URL(request.url).origin) {
2817
+ headers.delete("Authorization");
2818
+ headers.delete("Proxy-Authorization");
2819
+ headers.delete("Cookie");
2820
+ headers.delete("Host");
2821
+ }
2822
+ let method = request.method;
2823
+ if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
2824
+ method = "GET";
2825
+ body = void 0;
2826
+ for (const name of [
2827
+ "Content-Encoding",
2828
+ "Content-Language",
2829
+ "Content-Length",
2830
+ "Content-Location",
2831
+ "Content-Type"
2832
+ ]) headers.delete(name);
2833
+ }
2834
+ request = new Request(url, {
2835
+ method,
2836
+ headers,
2837
+ body,
2838
+ redirect: "manual"
2839
+ });
2840
+ }
2841
+ }
2769
2842
  /**
2770
2843
  * An error that is thrown when an activity fails to send to a remote inbox.
2771
2844
  * It contains structured information about the failure, including the HTTP
@@ -3272,6 +3345,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3272
3345
  }
3273
3346
  try {
3274
3347
  await sendActivity({
3348
+ allowPrivateAddress: this.allowPrivateAddress,
3275
3349
  keys,
3276
3350
  activity: message.activity,
3277
3351
  activityId: message.activityId,
@@ -3666,6 +3740,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3666
3740
  });
3667
3741
  const promises = [];
3668
3742
  for (const inbox in inboxes) promises.push(sendActivity({
3743
+ allowPrivateAddress: this.allowPrivateAddress,
3669
3744
  keys,
3670
3745
  activity: jsonLd,
3671
3746
  activityId: activity.id?.href,
@@ -4848,6 +4923,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4848
4923
  else logger.debug("Forwarding activity immediately without queue since queue is not set.");
4849
4924
  const promises = [];
4850
4925
  for (const inbox in inboxes) promises.push(sendActivity({
4926
+ allowPrivateAddress: ctx.federation.allowPrivateAddress,
4851
4927
  keys,
4852
4928
  activity: ctx.activity,
4853
4929
  activityId: ctx.activityId,
@@ -2,25 +2,25 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-BdneH5EA.mjs";
6
- import { t as formatAcceptSignature } from "./accept-CPkZzmGN.mjs";
7
- import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-C6EM8eSE.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-DcjN1Tul.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-dW-3XcQa.mjs";
10
- import { n as kvCache } from "./kv-cache-DYsF2MhP.mjs";
11
- import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-Ci6Z4_tn.mjs";
12
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-ByUYl7Xn.mjs";
13
- import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-CfUItkGW.mjs";
14
- import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-BEwrQa7q.mjs";
15
- import { t as getNodeInfo } from "./client-dx126nMS.mjs";
16
- import { t as nodeInfoToJson } from "./types-J53Kw7so.mjs";
17
- import { t as FederationBuilderImpl } from "./builder-8d-PNxcc.mjs";
18
- import { t as buildCollectionSynchronizationHeader } from "./collection-D-HqUuA2.mjs";
19
- import { t as KvKeyCache } from "./keycache-EGATflN-.mjs";
20
- import { t as acceptsJsonLd } from "./negotiation-SQvQgUqe.mjs";
21
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-DaRJ05za.mjs";
22
- import { t as createExponentialBackoffPolicy } from "./retry-v_sGLH1d.mjs";
23
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-B1XNYk6n.mjs";
5
+ import { n as version, t as name } from "./deno-Cg_cZHcI.mjs";
6
+ import { t as formatAcceptSignature } from "./accept-Dd__NiUL.mjs";
7
+ import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-CLRfRmA_.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-B3V9unT4.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-BwXP5fDH.mjs";
10
+ import { n as kvCache } from "./kv-cache-FsT1ljhs.mjs";
11
+ import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-C0s4f-GU.mjs";
12
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-DLbN_4jZ.mjs";
13
+ import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-Dc2OXyB-.mjs";
14
+ import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-CW3Zrxmd.mjs";
15
+ import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-CQ7zd8tW.mjs";
16
+ import { t as nodeInfoToJson } from "./types-s4mCTGkT.mjs";
17
+ import { t as FederationBuilderImpl } from "./builder-uTlVksLw.mjs";
18
+ import { t as buildCollectionSynchronizationHeader } from "./collection-DnoYNwBC.mjs";
19
+ import { t as KvKeyCache } from "./keycache-C1aVprwy.mjs";
20
+ import { t as acceptsJsonLd } from "./negotiation-BHtZN6Y1.mjs";
21
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CFLRXAcm.mjs";
22
+ import { t as createExponentialBackoffPolicy } from "./retry-bMXBL97A.mjs";
23
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-B5QvdrZ1.mjs";
24
24
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
25
25
  import { lookupWebFinger } from "@fedify/webfinger";
26
26
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -824,8 +824,16 @@ async function handleInboxInternal(request, parameters, span) {
824
824
  }
825
825
  let json;
826
826
  try {
827
- json = await request.clone().json();
827
+ json = JSON.parse(await readBoundedText(request.clone(), MAX_BODY_SIZE, request.url));
828
828
  } catch (error) {
829
+ if (error instanceof BodyTooLargeError) {
830
+ request.body?.cancel(error).catch(() => {});
831
+ span.setStatus({
832
+ code: SpanStatusCode.ERROR,
833
+ message: String(error)
834
+ });
835
+ return new Response("Inbox body too large.", { status: 413 });
836
+ }
829
837
  logger.error("Failed to parse JSON:\n{error}", {
830
838
  recipient,
831
839
  error
@@ -2159,6 +2167,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2159
2167
  }
2160
2168
  try {
2161
2169
  await sendActivity({
2170
+ allowPrivateAddress: this.allowPrivateAddress,
2162
2171
  keys,
2163
2172
  activity: message.activity,
2164
2173
  activityId: message.activityId,
@@ -2553,6 +2562,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2553
2562
  });
2554
2563
  const promises = [];
2555
2564
  for (const inbox in inboxes) promises.push(sendActivity({
2565
+ allowPrivateAddress: this.allowPrivateAddress,
2556
2566
  keys,
2557
2567
  activity: jsonLd,
2558
2568
  activityId: activity.id?.href,
@@ -3735,6 +3745,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
3735
3745
  else logger.debug("Forwarding activity immediately without queue since queue is not set.");
3736
3746
  const promises = [];
3737
3747
  for (const inbox in inboxes) promises.push(sendActivity({
3748
+ allowPrivateAddress: ctx.federation.allowPrivateAddress,
3738
3749
  keys,
3739
3750
  activity: ctx.activity,
3740
3751
  activityId: ctx.activityId,
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-BVBL4Ug9.cjs");
3
+ const require_middleware = require("./middleware-BDc7xyOx.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-D7VPt6Mr.mjs";
4
+ import { n as FederationImpl } from "./middleware-BsDiZP9H.mjs";
5
5
  export { FederationImpl };
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as HttpMessageSignaturesSpecDeterminer } from "./http-BDZeS5om.js";
2
+ import { n as HttpMessageSignaturesSpecDeterminer } from "./http-Dl00HXr4.js";
3
3
  import { n as KvStore, t as KvKey } from "./kv-D6hNiMTK.js";
4
4
  import { TracerProvider } from "@opentelemetry/api";
5
5
  import { DocumentLoader, DocumentLoaderFactoryOptions } from "@fedify/vocab-runtime";
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { S as KeyCache } from "./http-C87EWkO0.cjs";
2
+ import { S as KeyCache } from "./http-DqZhyEso.cjs";
3
3
  import { CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1 } from "@fedify/vocab";
4
4
  import { DocumentLoader } from "@fedify/vocab-runtime";
5
5
  import { TracerProvider } from "@opentelemetry/api";
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { $ as ActorAliasMapper, St as WebFingerLinksDispatcher, et as ActorDispatcher, l as RequestContext, tt as ActorHandleMapper } from "./context-DVA8wHZ0.cjs";
2
+ import { $ as ActorAliasMapper, St as WebFingerLinksDispatcher, et as ActorDispatcher, l as RequestContext, tt as ActorHandleMapper } from "./context-Cls_M1HU.cjs";
3
3
  import { Span, Tracer } from "@opentelemetry/api";
4
4
 
5
5
  //#region src/federation/webfinger.d.ts
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { $ as ActorAliasMapper, St as WebFingerLinksDispatcher, et as ActorDispatcher, l as RequestContext, tt as ActorHandleMapper } from "./context-BU-1O90h.js";
2
+ import { $ as ActorAliasMapper, St as WebFingerLinksDispatcher, et as ActorDispatcher, l as RequestContext, tt as ActorHandleMapper } from "./context-Bt2jnbC7.js";
3
3
  import { Span, Tracer } from "@opentelemetry/api";
4
4
 
5
5
  //#region src/federation/webfinger.d.ts
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as HttpMessageSignaturesSpecDeterminer } from "./http-C87EWkO0.cjs";
2
+ import { n as HttpMessageSignaturesSpecDeterminer } from "./http-DqZhyEso.cjs";
3
3
  import { n as KvStore, t as KvKey } from "./kv-gJ8LYbxX.cjs";
4
4
  import { DocumentLoader, DocumentLoaderFactoryOptions } from "@fedify/vocab-runtime";
5
5
  import { TracerProvider } from "@opentelemetry/api";