@fedify/fedify 2.3.0-dev.1145 → 2.3.0-dev.1150

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/{builder-ShiR1K6b.mjs → builder-Bjm1Jq9n.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/{context-DI2gRbyN.d.cts → context-CRXCkTM6.d.cts} +48 -6
  6. package/dist/{context-DCtsSHDv.d.ts → context-MgCh7YGu.d.ts} +48 -6
  7. package/dist/{deno-h0TWFuEz.mjs → deno-CKFE6Uya.mjs} +1 -1
  8. package/dist/{docloader-BdDN0Aqx.mjs → docloader-B-ZE1cZf.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1363 -44
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +1667 -163
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.d.cts +2 -2
  16. package/dist/federation/mod.d.ts +2 -2
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/retry.test.mjs +1 -1
  19. package/dist/federation/send.test.mjs +8 -8
  20. package/dist/federation/temporal.test.d.mts +2 -0
  21. package/dist/federation/temporal.test.mjs +71 -0
  22. package/dist/federation/webfinger.test.mjs +1 -1
  23. package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
  24. package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
  25. package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
  26. package/dist/{http-7kAB7PVx.cjs → http-DQYEA7AZ.cjs} +1 -1
  27. package/dist/{http-B2hxA7dO.js → http-WbS1gKzr.js} +1 -1
  28. package/dist/{http-QzW9IWfs.mjs → http-vHCgbhTg.mjs} +3 -3
  29. package/dist/{key-Dh2OK1XQ.mjs → key-N0zP_oJA.mjs} +2 -2
  30. package/dist/{kv-cache-b22dNkjt.js → kv-cache-DM2O-Yjy.js} +1 -1
  31. package/dist/{kv-cache-DCPp-MT0.cjs → kv-cache-Dsg_bi4N.cjs} +1 -1
  32. package/dist/{kv-cache-EZRIPZXD.mjs → kv-cache-GXXZEemD.mjs} +1 -1
  33. package/dist/{ld-eZbar1rr.mjs → ld-BwKhquPx.mjs} +302 -6
  34. package/dist/{metrics-E0hAHtLZ.mjs → metrics-7Vy9FvEw.mjs} +1 -1
  35. package/dist/{middleware-BrGIM_Ra.js → middleware-BscgvU-m.js} +428 -99
  36. package/dist/{middleware-BUl1BH4x.cjs → middleware-D_iXrYHJ.cjs} +429 -99
  37. package/dist/{middleware-mToCR2tG.mjs → middleware-Db1_qAFG.mjs} +1 -1
  38. package/dist/{middleware-CyJDCmNg.mjs → middleware-ZuUcO0t1.mjs} +348 -108
  39. package/dist/{mod-CI9fduEi.d.cts → mod-C7HOzGqH.d.cts} +1 -1
  40. package/dist/{mod-CkRiJHGA.d.ts → mod-CpQHB3Ys.d.ts} +1 -1
  41. package/dist/mod.cjs +4 -4
  42. package/dist/mod.d.cts +2 -2
  43. package/dist/mod.d.ts +2 -2
  44. package/dist/mod.js +4 -4
  45. package/dist/nodeinfo/handler.test.mjs +1 -1
  46. package/dist/{owner-ByO_Fw6U.mjs → owner-FD0H_vpj.mjs} +2 -2
  47. package/dist/{proof-jVqClF49.cjs → proof-CYK8T8IS.cjs} +353 -3
  48. package/dist/{proof-BkRyFchv.js → proof-I3EokKN-.js} +300 -4
  49. package/dist/{proof-CSo0S8OK.mjs → proof-V_lafPmA.mjs} +3 -3
  50. package/dist/{send-jzrTV1FU.mjs → send-Cc2_10tF.mjs} +3 -3
  51. package/dist/sig/http.test.mjs +2 -2
  52. package/dist/sig/key.test.mjs +1 -1
  53. package/dist/sig/ld.test.mjs +558 -2
  54. package/dist/sig/mod.cjs +2 -2
  55. package/dist/sig/mod.js +2 -2
  56. package/dist/sig/owner.test.mjs +1 -1
  57. package/dist/sig/proof.test.mjs +1 -1
  58. package/dist/temporal-BkmBfs__.mjs +95 -0
  59. package/dist/testing/mod.d.mts +48 -6
  60. package/dist/utils/docloader.test.mjs +2 -2
  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 +7 -7
  65. /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
  66. /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
  67. /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
  68. /package/dist/{retry-v_sGLH1d.mjs → retry-_VvV0h9f.mjs} +0 -0
@@ -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 { C as recordWebFingerHandle, O as name, S as recordOutboxEnqueue, a as verifyRequestDetailed, b as recordInboxActivity, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as version, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, w as formatAcceptSignature, x as recordOutboxActivity, y as recordFanoutRecipients } from "./http-B2hxA7dO.js";
6
- import { c as getKeyOwner, d as detachSignature, f as hasSignatureLike, i as verifyObject, m as verifyJsonLd, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, p as signJsonLd, r as signObject, s as doesActorOwnKey } from "./proof-BkRyFchv.js";
5
+ import { C as recordWebFingerHandle, O as name, S as recordOutboxEnqueue, a as verifyRequestDetailed, b as recordInboxActivity, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as version, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, w as formatAcceptSignature, x as recordOutboxActivity, y as recordFanoutRecipients } from "./http-WbS1gKzr.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-I3EokKN-.js";
7
7
  import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-b22dNkjt.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DM2O-Yjy.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
11
11
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
@@ -14,6 +14,7 @@ import { encodeHex } from "byte-encodings/hex";
14
14
  import { uniq } from "es-toolkit";
15
15
  import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
16
16
  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";
17
+ import jsonld from "@fedify/vocab-runtime/jsonld";
17
18
  import { lookupWebFinger } from "@fedify/webfinger";
18
19
  import { domainToASCII } from "node:url";
19
20
  //#region src/federation/activity-listener.ts
@@ -691,7 +692,7 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
691
692
  }
692
693
  //#endregion
693
694
  //#region src/federation/inbox.ts
694
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
695
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
695
696
  const logger = getLogger([
696
697
  "fedify",
697
698
  "federation",
@@ -750,7 +751,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
750
751
  type: "inbox",
751
752
  id: crypto.randomUUID(),
752
753
  baseUrl: ctx.origin,
753
- activity: json,
754
+ activity: originalJson ?? json,
755
+ ...normalizedActivity == null ? {} : { normalizedActivity },
756
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
754
757
  identifier: recipient,
755
758
  attempt: 0,
756
759
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -804,7 +807,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
804
807
  const activityType = getTypeId(activity).href;
805
808
  const started = performance.now();
806
809
  try {
807
- await listener(inboxContextFactory(recipient, json, activity.id?.href, activityType), activity);
810
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
811
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
808
812
  } finally {
809
813
  getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
810
814
  }
@@ -1029,7 +1033,123 @@ function acceptsJsonLd(request) {
1029
1033
  return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
1030
1034
  }
1031
1035
  //#endregion
1036
+ //#region src/federation/temporal.ts
1037
+ function isPlainObject(value) {
1038
+ return typeof value === "object" && value != null && !Array.isArray(value);
1039
+ }
1040
+ function normalizeDateTimeLiteral(value) {
1041
+ return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
1042
+ }
1043
+ function isMalformedDateTimeLiteral(value) {
1044
+ if (typeof value !== "string") return false;
1045
+ try {
1046
+ Temporal.Instant.from(normalizeDateTimeLiteral(value));
1047
+ return false;
1048
+ } catch {
1049
+ return true;
1050
+ }
1051
+ }
1052
+ function isMalformedDurationLiteral(value) {
1053
+ if (typeof value !== "string") return false;
1054
+ try {
1055
+ Temporal.Duration.from(value);
1056
+ return false;
1057
+ } catch {
1058
+ return true;
1059
+ }
1060
+ }
1061
+ const TEMPORAL_DATE_TIME_IRIS = new Set([
1062
+ "https://www.w3.org/ns/activitystreams#deleted",
1063
+ "https://www.w3.org/ns/activitystreams#endTime",
1064
+ "https://www.w3.org/ns/activitystreams#published",
1065
+ "https://www.w3.org/ns/activitystreams#startTime",
1066
+ "https://www.w3.org/ns/activitystreams#updated",
1067
+ "http://purl.org/dc/terms/created",
1068
+ "https://w3id.org/security#created"
1069
+ ]);
1070
+ const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1071
+ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1072
+ const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1073
+ function hasMalformedExpandedDateTimeLiteral(value) {
1074
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1075
+ return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1076
+ }
1077
+ function hasMalformedExpandedQuestionClosedLiteral(value) {
1078
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1079
+ if (!isPlainObject(value) || !("@value" in value)) return false;
1080
+ const literal = value["@value"];
1081
+ if (typeof literal === "boolean") return false;
1082
+ if (typeof literal !== "string") return false;
1083
+ if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
1084
+ if (new Date(literal).toString() === "Invalid Date") return false;
1085
+ return isMalformedDateTimeLiteral(literal);
1086
+ }
1087
+ function hasMalformedExpandedDurationLiteral(value) {
1088
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1089
+ return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1090
+ }
1091
+ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1092
+ if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1093
+ if (!isPlainObject(value)) return false;
1094
+ if (visited.has(value)) return false;
1095
+ visited.add(value);
1096
+ if ("@value" in value) return false;
1097
+ for (const [key, child] of Object.entries(value)) {
1098
+ if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
1099
+ if (hasMalformedExpandedDateTimeLiteral(child)) return true;
1100
+ continue;
1101
+ }
1102
+ if (key === QUESTION_CLOSED_IRI) {
1103
+ if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
1104
+ continue;
1105
+ }
1106
+ if (TEMPORAL_DURATION_IRIS.has(key)) {
1107
+ if (hasMalformedExpandedDurationLiteral(child)) return true;
1108
+ continue;
1109
+ }
1110
+ if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
1111
+ }
1112
+ return false;
1113
+ }
1114
+ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1115
+ try {
1116
+ return hasMalformedKnownTemporalLiteralInternal(await jsonld.expand(value, {
1117
+ documentLoader: getNormalizationContextLoader(contextLoader),
1118
+ keepFreeFloatingNodes: true
1119
+ }), /* @__PURE__ */ new Set());
1120
+ } catch {
1121
+ return false;
1122
+ }
1123
+ }
1124
+ //#endregion
1032
1125
  //#region src/federation/handler.ts
1126
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1127
+ function isRemoteContextLoadingFailure$1(error) {
1128
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1129
+ }
1130
+ function isPermanentRemoteContextError$1(error) {
1131
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1132
+ const details = error.details;
1133
+ if (details?.code === "invalid remote context") return true;
1134
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
1135
+ }
1136
+ function isInvalidJsonLdError(error) {
1137
+ if (!(error instanceof Error)) return false;
1138
+ const name = error.name;
1139
+ return name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1140
+ }
1141
+ function isValidationTypeError(error) {
1142
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
1143
+ }
1144
+ function isPermanentActivityParseError(error) {
1145
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
1146
+ }
1147
+ function hasHttpSignatureHeaders(request) {
1148
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
1149
+ }
1150
+ function hasObjectIntegrityProof(json) {
1151
+ return typeof json === "object" && json != null && "proof" in json;
1152
+ }
1033
1153
  /**
1034
1154
  * Handles an actor request.
1035
1155
  * @template TContextData The context data to pass to the context.
@@ -1101,8 +1221,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
1101
1221
  * @param parameters The parameters for handling the collection.
1102
1222
  * @returns A promise that resolves to an HTTP response.
1103
1223
  */
1104
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1105
- const spanName = name$2.trim().replace(/\s+/g, "_");
1224
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1225
+ const spanName = name$1.trim().replace(/\s+/g, "_");
1106
1226
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
1107
1227
  const tracer = tracerProvider.getTracer(name, version);
1108
1228
  const cursor = new URL(request.url).searchParams.get("cursor");
@@ -1144,7 +1264,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1144
1264
  collection = new OrderedCollection({
1145
1265
  id: baseUri,
1146
1266
  totalItems: totalItems == null ? null : Number(totalItems),
1147
- items: filterCollectionItems(itemsOrResponse, name$2, filterPredicate)
1267
+ items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
1148
1268
  });
1149
1269
  } else {
1150
1270
  const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
@@ -1165,7 +1285,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1165
1285
  } else {
1166
1286
  const uri = new URL(baseUri);
1167
1287
  uri.searchParams.set("cursor", cursor);
1168
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
1288
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1169
1289
  kind: SpanKind.SERVER,
1170
1290
  attributes: {
1171
1291
  "activitypub.collection.id": uri.href,
@@ -1209,7 +1329,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1209
1329
  id: uri,
1210
1330
  prev,
1211
1331
  next,
1212
- items: filterCollectionItems(items, name$2, filterPredicate),
1332
+ items: filterCollectionItems(items, name$1, filterPredicate),
1213
1333
  partOf
1214
1334
  });
1215
1335
  }
@@ -1556,29 +1676,105 @@ async function handleInboxInternal(request, parameters, span) {
1556
1676
  });
1557
1677
  }
1558
1678
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1559
- let ldSigVerified;
1560
- try {
1561
- ldSigVerified = await verifyJsonLd(json, {
1562
- contextLoader: ctx.contextLoader,
1563
- documentLoader: ctx.documentLoader,
1564
- keyCache,
1565
- meterProvider,
1566
- tracerProvider
1679
+ const jsonWithoutSig = detachSignature(json);
1680
+ const hasLdSignature = hasSignature(json);
1681
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
1682
+ let deferredLdSignatureError = void 0;
1683
+ const respondInvalidActivity = async (error) => {
1684
+ logger.error("Failed to parse activity:\n{error}", {
1685
+ recipient,
1686
+ activity: json,
1687
+ error
1567
1688
  });
1568
- } catch (error) {
1569
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1570
- logger.error("Failed to parse JSON-LD:\n{error}", {
1689
+ try {
1690
+ await inboxErrorHandler?.(ctx, error);
1691
+ } catch (error) {
1692
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1693
+ error,
1694
+ activity: json,
1695
+ recipient
1696
+ });
1697
+ }
1698
+ span.setStatus({
1699
+ code: SpanStatusCode.ERROR,
1700
+ message: `Failed to parse activity:\n${error}`
1701
+ });
1702
+ return new Response("Invalid activity.", {
1703
+ status: 400,
1704
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1705
+ });
1706
+ };
1707
+ let compactedJson = json;
1708
+ let compactedJsonWithoutSig = jsonWithoutSig;
1709
+ let ldSigVerified = false;
1710
+ if (hasLdSignature) {
1711
+ try {
1712
+ compactedJson = await compactJsonLd(json, ctx.contextLoader);
1713
+ } catch (error) {
1714
+ if (isInvalidJsonLdError(error)) {
1715
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1716
+ recipient,
1717
+ error
1718
+ });
1719
+ return new Response("Invalid JSON-LD.", {
1720
+ status: 400,
1721
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1722
+ });
1723
+ }
1724
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1725
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
1726
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
1571
1727
  recipient,
1572
1728
  error
1573
1729
  });
1574
- return new Response("Invalid JSON-LD.", {
1575
- status: 400,
1576
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1577
- });
1578
1730
  }
1579
- ldSigVerified = false;
1731
+ if (compactedJson !== json) {
1732
+ compactedJsonWithoutSig = detachSignature(compactedJson);
1733
+ try {
1734
+ ldSigVerified = await verifyCompactJsonLd(compactedJson, {
1735
+ contextLoader: ctx.contextLoader,
1736
+ documentLoader: ctx.documentLoader,
1737
+ keyCache,
1738
+ meterProvider,
1739
+ tracerProvider
1740
+ });
1741
+ } catch (error) {
1742
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1743
+ if (isInvalidJsonLdError(error)) {
1744
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1745
+ recipient,
1746
+ error
1747
+ });
1748
+ return new Response("Invalid JSON-LD.", {
1749
+ status: 400,
1750
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1751
+ });
1752
+ }
1753
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1754
+ if (!skipSignatureVerification) try {
1755
+ await Object$1.fromJsonLd(compactedJson, {
1756
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader),
1757
+ documentLoader: ctx.documentLoader,
1758
+ tracerProvider
1759
+ });
1760
+ } catch (parseError) {
1761
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
1762
+ if (isInvalidJsonLdError(parseError)) {
1763
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1764
+ recipient,
1765
+ error: parseError
1766
+ });
1767
+ return new Response("Invalid JSON-LD.", {
1768
+ status: 400,
1769
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1770
+ });
1771
+ }
1772
+ deferredLdSignatureError = parseError;
1773
+ }
1774
+ ldSigVerified = false;
1775
+ }
1776
+ }
1580
1777
  }
1581
- const jsonWithoutSig = detachSignature(json);
1582
1778
  let activity = null;
1583
1779
  let activityVerified = false;
1584
1780
  if (ldSigVerified) {
@@ -1586,7 +1782,16 @@ async function handleInboxInternal(request, parameters, span) {
1586
1782
  recipient,
1587
1783
  json
1588
1784
  });
1589
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1785
+ try {
1786
+ activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
1787
+ ...ctx,
1788
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader)
1789
+ });
1790
+ } catch (error) {
1791
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1792
+ if (!isPermanentActivityParseError(error)) throw error;
1793
+ return await respondInvalidActivity(error);
1794
+ }
1590
1795
  activityVerified = true;
1591
1796
  } else {
1592
1797
  logger.debug("Linked Data Signatures are not verified.", {
@@ -1595,13 +1800,22 @@ async function handleInboxInternal(request, parameters, span) {
1595
1800
  });
1596
1801
  try {
1597
1802
  activity = await verifyObject(Activity, jsonWithoutSig, {
1598
- contextLoader: ctx.contextLoader,
1803
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
1599
1804
  documentLoader: ctx.documentLoader,
1600
1805
  keyCache,
1601
1806
  meterProvider,
1602
1807
  tracerProvider
1603
1808
  });
1604
1809
  } catch (error) {
1810
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1811
+ if (deferredLdSignatureError != null) {
1812
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
1813
+ recipient,
1814
+ error
1815
+ });
1816
+ activity = null;
1817
+ }
1818
+ if (!isPermanentActivityParseError(error)) throw error;
1605
1819
  logger.error("Failed to parse activity:\n{error}", {
1606
1820
  recipient,
1607
1821
  activity: json,
@@ -1650,6 +1864,7 @@ async function handleInboxInternal(request, parameters, span) {
1650
1864
  tracerProvider
1651
1865
  });
1652
1866
  if (verification.verified === false) {
1867
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1653
1868
  const reason = verification.reason;
1654
1869
  const remoteHost = "keyId" in reason && reason.keyId != null ? getRemoteHost(reason.keyId) : void 0;
1655
1870
  getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure(reason.type, remoteHost);
@@ -1727,7 +1942,15 @@ async function handleInboxInternal(request, parameters, span) {
1727
1942
  }
1728
1943
  httpSigKey = verification.key;
1729
1944
  }
1730
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1945
+ try {
1946
+ activity = await Activity.fromJsonLd(jsonWithoutSig, {
1947
+ ...ctx,
1948
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
1949
+ });
1950
+ } catch (error) {
1951
+ if (!isPermanentActivityParseError(error)) throw error;
1952
+ return await respondInvalidActivity(error);
1953
+ }
1731
1954
  }
1732
1955
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1733
1956
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
@@ -1739,6 +1962,7 @@ async function handleInboxInternal(request, parameters, span) {
1739
1962
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
1740
1963
  });
1741
1964
  if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
1965
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1742
1966
  getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("actorKeyMismatch", httpSigKey.id == null ? void 0 : getRemoteHost(httpSigKey.id));
1743
1967
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1744
1968
  activity: json,
@@ -1765,10 +1989,14 @@ async function handleInboxInternal(request, parameters, span) {
1765
1989
  const routeResult = await routeActivity({
1766
1990
  context: ctx,
1767
1991
  json,
1992
+ originalJson: json,
1993
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
1994
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1768
1995
  activity,
1769
1996
  recipient,
1770
1997
  inboxListeners,
1771
1998
  inboxContextFactory,
1999
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1772
2000
  inboxErrorHandler,
1773
2001
  kv,
1774
2002
  kvPrefixes,
@@ -1895,8 +2123,8 @@ var CustomCollectionHandler = class {
1895
2123
  * @param CollectionPage The CollectionPage constructor.
1896
2124
  * @param filterPredicate Optional filter predicate for items.
1897
2125
  */
1898
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1899
- this.name = name$1;
2126
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
2127
+ this.name = name$2;
1900
2128
  this.values = values;
1901
2129
  this.context = context;
1902
2130
  this.callbacks = callbacks;
@@ -2782,6 +3010,18 @@ var middleware_exports = /* @__PURE__ */ __exportAll({
2782
3010
  OutboxContextImpl: () => OutboxContextImpl,
2783
3011
  createFederation: () => createFederation
2784
3012
  });
3013
+ function isRemoteContextLoadingFailure(error) {
3014
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
3015
+ }
3016
+ function isPermanentRemoteContextError(error) {
3017
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
3018
+ const details = error.details;
3019
+ if (details?.code === "invalid remote context") return true;
3020
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
3021
+ }
3022
+ function isPermanentInboxParseError(error) {
3023
+ return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
3024
+ }
2785
3025
  /**
2786
3026
  * Create a new {@link Federation} instance.
2787
3027
  * @param parameters Parameters for initializing the instance.
@@ -3284,78 +3524,37 @@ var FederationImpl = class extends FederationBuilderImpl {
3284
3524
  const identity = await this.sharedInboxKeyDispatcher(context);
3285
3525
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3286
3526
  }
3287
- const activity = await Activity.fromJsonLd(message.activity, context);
3288
- const activityType = getTypeId(activity).href;
3289
- span.setAttribute("activitypub.activity.type", activityType);
3290
- onActivityType?.(activityType);
3291
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3292
- const cacheKey = activity.id == null ? null : [
3293
- ...this.kvPrefixes.activityIdempotence,
3294
- context.origin,
3295
- activity.id.href
3296
- ];
3297
- if (cacheKey != null) {
3298
- if (await this.kv.get(cacheKey) === true) {
3299
- logger.debug("Activity {activityId} has already been processed.", {
3300
- activityId: activity.id?.href,
3301
- activity: message.activity,
3302
- recipient: message.identifier
3303
- });
3304
- recordInboxActivity(this.meterProvider, "rejected", activityType);
3305
- return;
3306
- }
3307
- }
3308
- await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
3309
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
3310
- if (dispatched == null) {
3311
- logger.error("Unsupported activity type:\n{activity}", {
3312
- activityId: activity.id?.href,
3313
- activity: message.activity,
3314
- recipient: message.identifier,
3315
- trial: message.attempt
3316
- });
3317
- span.setStatus({
3318
- code: SpanStatusCode.ERROR,
3319
- message: `Unsupported activity type: ${activityType}`
3320
- });
3321
- recordInboxActivity(this.meterProvider, "rejected", activityType);
3322
- span.end();
3323
- return;
3324
- }
3325
- const { class: cls, listener } = dispatched;
3326
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3327
- try {
3328
- const started = performance.now();
3329
- try {
3330
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
3331
- } finally {
3332
- getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
3333
- }
3334
- recordInboxActivity(this.meterProvider, "processed", activityType);
3335
- } catch (error) {
3527
+ await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (listenerSpan) => {
3528
+ let activity = null;
3529
+ let cacheKey = null;
3530
+ let activityType;
3531
+ const reportInboxError = async (error) => {
3336
3532
  try {
3337
3533
  await this.inboxErrorHandler?.(context, error);
3338
3534
  } catch (error) {
3339
3535
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
3340
3536
  error,
3341
3537
  trial: message.attempt,
3342
- activityId: activity.id?.href,
3538
+ activityId: activity?.id?.href,
3343
3539
  activity: message.activity,
3344
3540
  recipient: message.identifier
3345
3541
  });
3346
3542
  }
3543
+ };
3544
+ const handleRetriableFailure = async (error) => {
3545
+ await reportInboxError(error);
3347
3546
  if (this.inboxQueue?.nativeRetrial) {
3348
3547
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
3349
3548
  error,
3350
- activityId: activity.id?.href,
3549
+ activityId: activity?.id?.href,
3351
3550
  activity: message.activity,
3352
3551
  recipient: message.identifier
3353
3552
  });
3354
- span.setStatus({
3553
+ listenerSpan.setStatus({
3355
3554
  code: SpanStatusCode.ERROR,
3356
3555
  message: String(error)
3357
3556
  });
3358
- span.end();
3557
+ listenerSpan.end();
3359
3558
  throw error;
3360
3559
  }
3361
3560
  const delay = this.inboxRetryPolicy({
@@ -3366,20 +3565,27 @@ var FederationImpl = class extends FederationBuilderImpl {
3366
3565
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
3367
3566
  error,
3368
3567
  attempt: message.attempt,
3369
- activityId: activity.id?.href,
3568
+ activityId: activity?.id?.href,
3370
3569
  activity: message.activity,
3371
3570
  recipient: message.identifier
3372
3571
  });
3572
+ if (this.inboxQueue == null) {
3573
+ listenerSpan.setStatus({
3574
+ code: SpanStatusCode.ERROR,
3575
+ message: String(error)
3576
+ });
3577
+ listenerSpan.end();
3578
+ throw error;
3579
+ }
3373
3580
  const retryMessage = {
3374
3581
  ...message,
3375
3582
  attempt: message.attempt + 1
3376
3583
  };
3377
- const { inboxQueue } = this;
3378
- if (inboxQueue != null) {
3379
- await inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3584
+ await this.inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3585
+ if (activityType != null) {
3380
3586
  getFederationMetrics(this.meterProvider).recordQueueTaskEnqueued({
3381
3587
  role: "inbox",
3382
- queue: inboxQueue,
3588
+ queue: this.inboxQueue,
3383
3589
  activityType
3384
3590
  }, retryMessage.attempt);
3385
3591
  recordInboxActivity(this.meterProvider, "retried", activityType);
@@ -3387,26 +3593,137 @@ var FederationImpl = class extends FederationBuilderImpl {
3387
3593
  } else {
3388
3594
  logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3389
3595
  error,
3390
- activityId: activity.id?.href,
3596
+ activityId: activity?.id?.href,
3391
3597
  activity: message.activity,
3392
3598
  recipient: message.identifier
3393
3599
  });
3394
- recordInboxActivity(this.meterProvider, "abandoned", activityType);
3600
+ if (activityType != null) recordInboxActivity(this.meterProvider, "abandoned", activityType);
3395
3601
  }
3396
- span.setStatus({
3602
+ listenerSpan.setStatus({
3397
3603
  code: SpanStatusCode.ERROR,
3398
3604
  message: String(error)
3399
3605
  });
3400
- span.end();
3606
+ listenerSpan.end();
3607
+ };
3608
+ let dispatched;
3609
+ let parseInput = void 0;
3610
+ let parseContextLoader = context.contextLoader;
3611
+ try {
3612
+ const hasSignatureField = hasSignature(message.activity);
3613
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
3614
+ const parseContext = hasSignatureField ? {
3615
+ ...context,
3616
+ contextLoader: getNormalizationContextLoader(context.contextLoader)
3617
+ } : {
3618
+ ...context,
3619
+ contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
3620
+ };
3621
+ parseContextLoader = parseContext.contextLoader;
3622
+ let normalizedActivity;
3623
+ if (shouldParseFromNormalizedSignedPayload) {
3624
+ normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
3625
+ assertSafeJsonLd(normalizedActivity);
3626
+ }
3627
+ parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
3628
+ activity = await Activity.fromJsonLd(parseInput, parseContext);
3629
+ activityType = getTypeId(activity).href;
3630
+ span.setAttribute("activitypub.activity.type", activityType);
3631
+ listenerSpan.setAttribute("activitypub.activity.type", activityType);
3632
+ onActivityType?.(activityType);
3633
+ if (activity.id != null) {
3634
+ span.setAttribute("activitypub.activity.id", activity.id.href);
3635
+ listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
3636
+ }
3637
+ cacheKey = activity.id == null ? null : [
3638
+ ...this.kvPrefixes.activityIdempotence,
3639
+ context.origin,
3640
+ activity.id.href
3641
+ ];
3642
+ if (cacheKey != null) {
3643
+ if (await this.kv.get(cacheKey) === true) {
3644
+ logger.debug("Activity {activityId} has already been processed.", {
3645
+ activityId: activity.id?.href,
3646
+ activity: message.activity,
3647
+ recipient: message.identifier
3648
+ });
3649
+ recordInboxActivity(this.meterProvider, "rejected", activityType);
3650
+ listenerSpan.end();
3651
+ return;
3652
+ }
3653
+ }
3654
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
3655
+ } catch (error) {
3656
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3657
+ await reportInboxError(error);
3658
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3659
+ error,
3660
+ trial: message.attempt,
3661
+ activityId: null,
3662
+ activity: message.activity,
3663
+ recipient: message.identifier
3664
+ });
3665
+ listenerSpan.setStatus({
3666
+ code: SpanStatusCode.ERROR,
3667
+ message: String(error)
3668
+ });
3669
+ listenerSpan.end();
3670
+ return;
3671
+ }
3672
+ if (isPermanentInboxParseError(error)) {
3673
+ await reportInboxError(error);
3674
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3675
+ error,
3676
+ trial: message.attempt,
3677
+ activityId: activity?.id?.href,
3678
+ activity: message.activity,
3679
+ recipient: message.identifier
3680
+ });
3681
+ listenerSpan.setStatus({
3682
+ code: SpanStatusCode.ERROR,
3683
+ message: String(error)
3684
+ });
3685
+ listenerSpan.end();
3686
+ return;
3687
+ }
3688
+ await handleRetriableFailure(error);
3689
+ return;
3690
+ }
3691
+ if (dispatched == null) {
3692
+ logger.error("Unsupported activity type:\n{activity}", {
3693
+ activityId: activity.id?.href,
3694
+ activity: message.activity,
3695
+ recipient: message.identifier,
3696
+ trial: message.attempt
3697
+ });
3698
+ listenerSpan.setStatus({
3699
+ code: SpanStatusCode.ERROR,
3700
+ message: `Unsupported activity type: ${activityType}`
3701
+ });
3702
+ recordInboxActivity(this.meterProvider, "rejected", activityType);
3703
+ listenerSpan.end();
3704
+ return;
3705
+ }
3706
+ const { class: cls, listener } = dispatched;
3707
+ listenerSpan.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3708
+ try {
3709
+ const started = performance.now();
3710
+ try {
3711
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
3712
+ } finally {
3713
+ getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
3714
+ }
3715
+ recordInboxActivity(this.meterProvider, "processed", activityType);
3716
+ } catch (error) {
3717
+ await handleRetriableFailure(error);
3401
3718
  return;
3402
3719
  }
3403
3720
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
3404
3721
  logger.info("Activity {activityId} has been processed.", {
3405
- activityId: activity.id?.href,
3722
+ activityId: activity?.id?.href,
3406
3723
  activity: message.activity,
3407
3724
  recipient: message.identifier
3408
3725
  });
3409
- span.end();
3726
+ listenerSpan.end();
3410
3727
  });
3411
3728
  }
3412
3729
  startQueue(contextData, options = {}) {
@@ -3787,16 +4104,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3787
4104
  onNotFound
3788
4105
  });
3789
4106
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
3790
- case "sharedInbox":
4107
+ case "sharedInbox": {
3791
4108
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3792
4109
  const identity = await this.sharedInboxKeyDispatcher(context);
3793
4110
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3794
4111
  }
3795
4112
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
4113
+ const inboxContextFactory = context.toInboxContext.bind(context);
4114
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3796
4115
  return await handleInbox(request, {
3797
4116
  recipient: route.values.identifier ?? null,
3798
4117
  context,
3799
- inboxContextFactory: context.toInboxContext.bind(context),
4118
+ inboxContextFactory,
3800
4119
  kv: this.kv,
3801
4120
  kvPrefixes: this.kvPrefixes,
3802
4121
  queue: this.inboxQueue,
@@ -3812,6 +4131,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3812
4131
  tracerProvider: this.tracerProvider,
3813
4132
  idempotencyStrategy: this.idempotencyStrategy
3814
4133
  });
4134
+ }
3815
4135
  case "following": return await handleCollection(request, {
3816
4136
  name: "following",
3817
4137
  identifier: route.values.identifier,
@@ -4545,6 +4865,7 @@ var ContextImpl = class ContextImpl {
4545
4865
  const routeResult = await routeActivity({
4546
4866
  context: this,
4547
4867
  json,
4868
+ ldSignatureVerified: false,
4548
4869
  activity,
4549
4870
  recipient,
4550
4871
  inboxListeners: this.federation.inboxListeners,
@@ -4833,6 +5154,14 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4833
5154
  }
4834
5155
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4835
5156
  recipient;
5157
+ /**
5158
+ * The original received activity payload.
5159
+ *
5160
+ * Fedify may normalize a Linked Data Signature payload internally for safe
5161
+ * parsing, but forwarding must keep the sender's payload unchanged so
5162
+ * third-party signatures/proofs remain intact.
5163
+ * @internal
5164
+ */
4836
5165
  activity;
4837
5166
  activityId;
4838
5167
  activityType;