@fedify/fedify 2.2.3-dev.1098 → 2.2.4

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 (60) hide show
  1. package/dist/{builder-mqtih91o.mjs → builder-Dg2hGWk5.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-BPMgyX7m.d.ts → context-BU-1O90h.d.ts} +48 -6
  6. package/dist/{context-DwkhwUX9.d.cts → context-DVA8wHZ0.d.cts} +48 -6
  7. package/dist/{deno-CziVFvS6.mjs → deno-_3m2phl-.mjs} +1 -1
  8. package/dist/{docloader-fI9DeYyB.mjs → docloader-Cq_3E56G.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1363 -43
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/middleware.test.mjs +1584 -80
  13. package/dist/federation/mod.cjs +1 -1
  14. package/dist/federation/mod.d.cts +2 -2
  15. package/dist/federation/mod.d.ts +2 -2
  16. package/dist/federation/mod.js +1 -1
  17. package/dist/federation/retry.test.mjs +1 -1
  18. package/dist/federation/send.test.mjs +3 -3
  19. package/dist/federation/temporal.test.d.mts +2 -0
  20. package/dist/federation/temporal.test.mjs +71 -0
  21. package/dist/federation/webfinger.test.mjs +1 -1
  22. package/dist/{http-D8qsXrUS.js → http-BauEA3uU.js} +1 -1
  23. package/dist/{http-BDCGf4Ac.mjs → http-C6206Ne5.mjs} +2 -2
  24. package/dist/{http-kPc328Pc.cjs → http-CzvQu1wC.cjs} +1 -1
  25. package/dist/{key-D3TgMhcs.mjs → key-ckqyhgo3.mjs} +1 -1
  26. package/dist/{kv-cache-zxW74Wfd.cjs → kv-cache-10y07lRd.cjs} +1 -1
  27. package/dist/{kv-cache-D_eVhctK.js → kv-cache-DLwx2oCr.js} +1 -1
  28. package/dist/ld-B3BrUVFK.mjs +573 -0
  29. package/dist/{middleware-2gmMVy8b.mjs → middleware-Bn8hcuAb.mjs} +314 -78
  30. package/dist/{middleware-BuOXw_hM.cjs → middleware-CBNvWoWG.cjs} +1 -1
  31. package/dist/{middleware-xR9KxICq.cjs → middleware-Cppn0oGi.cjs} +399 -73
  32. package/dist/{middleware-gXlDLkok.js → middleware-DkSEt8CX.js} +396 -71
  33. package/dist/{middleware-CfaiRKQ9.mjs → middleware-_5uCEul-.mjs} +1 -1
  34. package/dist/{mod-CNAHY39V.d.ts → mod-BVt6iTmH.d.ts} +1 -1
  35. package/dist/{mod-Bi6WOdti.d.cts → mod-q-NFLW6B.d.cts} +1 -1
  36. package/dist/mod.cjs +4 -4
  37. package/dist/mod.d.cts +2 -2
  38. package/dist/mod.d.ts +2 -2
  39. package/dist/mod.js +4 -4
  40. package/dist/nodeinfo/handler.test.mjs +1 -1
  41. package/dist/{owner-DBSV2TSl.mjs → owner-BIrHaRRj.mjs} +2 -2
  42. package/dist/{proof-tz91vdtN.mjs → proof-B9P5A7RZ.mjs} +2 -2
  43. package/dist/{proof-CZDkoeWG.cjs → proof-Bxo0UtfN.cjs} +351 -3
  44. package/dist/{proof-z93OkIov.js → proof-erB_wSQi.js} +298 -4
  45. package/dist/{send-CNjG31rJ.mjs → send-7FY-qDY3.mjs} +2 -2
  46. package/dist/sig/http.test.mjs +2 -2
  47. package/dist/sig/key.test.mjs +1 -1
  48. package/dist/sig/ld.test.mjs +558 -2
  49. package/dist/sig/mod.cjs +2 -2
  50. package/dist/sig/mod.js +2 -2
  51. package/dist/sig/owner.test.mjs +1 -1
  52. package/dist/sig/proof.test.mjs +1 -1
  53. package/dist/temporal-CuaJdDfw.mjs +95 -0
  54. package/dist/testing/mod.d.mts +48 -6
  55. package/dist/utils/docloader.test.mjs +2 -2
  56. package/dist/utils/mod.cjs +1 -1
  57. package/dist/utils/mod.js +1 -1
  58. package/package.json +5 -5
  59. package/dist/ld-D_u8mdpv.mjs +0 -279
  60. /package/dist/{retry-bMXBL97A.mjs → retry-v_sGLH1d.mjs} +0 -0
@@ -1,11 +1,11 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- require("./chunk-DDcVe30Y.cjs");
3
+ const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-kPc328Pc.cjs");
6
- const require_proof = require("./proof-CZDkoeWG.cjs");
5
+ const require_http = require("./http-CzvQu1wC.cjs");
6
+ const require_proof = require("./proof-Bxo0UtfN.cjs");
7
7
  const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-zxW74Wfd.cjs");
8
+ const require_kv_cache = require("./kv-cache-10y07lRd.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");
@@ -15,6 +15,8 @@ let url_template = require("url-template");
15
15
  let byte_encodings_hex = require("byte-encodings/hex");
16
16
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
17
17
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
18
+ let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
19
+ _fedify_vocab_runtime_jsonld = require_chunk.__toESM(_fedify_vocab_runtime_jsonld);
18
20
  let _fedify_webfinger = require("@fedify/webfinger");
19
21
  let node_url = require("node:url");
20
22
  //#region src/federation/activity-listener.ts
@@ -210,7 +212,7 @@ var FederationBuilderImpl = class {
210
212
  this.collectionTypeIds = {};
211
213
  }
212
214
  async build(options) {
213
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-BuOXw_hM.cjs"));
215
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-CBNvWoWG.cjs"));
214
216
  const f = new FederationImpl(options);
215
217
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
216
218
  f.router = this.router.clone();
@@ -770,7 +772,7 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
770
772
  }
771
773
  //#endregion
772
774
  //#region src/federation/inbox.ts
773
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
775
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
774
776
  const logger = (0, _logtape_logtape.getLogger)([
775
777
  "fedify",
776
778
  "federation",
@@ -827,7 +829,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
827
829
  type: "inbox",
828
830
  id: crypto.randomUUID(),
829
831
  baseUrl: ctx.origin,
830
- activity: json,
832
+ activity: originalJson ?? json,
833
+ ...normalizedActivity == null ? {} : { normalizedActivity },
834
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
831
835
  identifier: recipient,
832
836
  attempt: 0,
833
837
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -871,7 +875,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
871
875
  const { class: cls, listener } = dispatched;
872
876
  span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
873
877
  try {
874
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, (0, _fedify_vocab.getTypeId)(activity).href), activity);
878
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
879
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, (0, _fedify_vocab.getTypeId)(activity).href), activity);
875
880
  } catch (error) {
876
881
  try {
877
882
  await inboxErrorHandler?.(ctx, error);
@@ -1062,7 +1067,123 @@ function acceptsJsonLd(request) {
1062
1067
  return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
1063
1068
  }
1064
1069
  //#endregion
1070
+ //#region src/federation/temporal.ts
1071
+ function isPlainObject(value) {
1072
+ return typeof value === "object" && value != null && !Array.isArray(value);
1073
+ }
1074
+ function normalizeDateTimeLiteral(value) {
1075
+ return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
1076
+ }
1077
+ function isMalformedDateTimeLiteral(value) {
1078
+ if (typeof value !== "string") return false;
1079
+ try {
1080
+ Temporal.Instant.from(normalizeDateTimeLiteral(value));
1081
+ return false;
1082
+ } catch {
1083
+ return true;
1084
+ }
1085
+ }
1086
+ function isMalformedDurationLiteral(value) {
1087
+ if (typeof value !== "string") return false;
1088
+ try {
1089
+ Temporal.Duration.from(value);
1090
+ return false;
1091
+ } catch {
1092
+ return true;
1093
+ }
1094
+ }
1095
+ const TEMPORAL_DATE_TIME_IRIS = new Set([
1096
+ "https://www.w3.org/ns/activitystreams#deleted",
1097
+ "https://www.w3.org/ns/activitystreams#endTime",
1098
+ "https://www.w3.org/ns/activitystreams#published",
1099
+ "https://www.w3.org/ns/activitystreams#startTime",
1100
+ "https://www.w3.org/ns/activitystreams#updated",
1101
+ "http://purl.org/dc/terms/created",
1102
+ "https://w3id.org/security#created"
1103
+ ]);
1104
+ const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1105
+ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1106
+ const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1107
+ function hasMalformedExpandedDateTimeLiteral(value) {
1108
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1109
+ return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1110
+ }
1111
+ function hasMalformedExpandedQuestionClosedLiteral(value) {
1112
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1113
+ if (!isPlainObject(value) || !("@value" in value)) return false;
1114
+ const literal = value["@value"];
1115
+ if (typeof literal === "boolean") return false;
1116
+ if (typeof literal !== "string") return false;
1117
+ if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
1118
+ if (new Date(literal).toString() === "Invalid Date") return false;
1119
+ return isMalformedDateTimeLiteral(literal);
1120
+ }
1121
+ function hasMalformedExpandedDurationLiteral(value) {
1122
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1123
+ return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1124
+ }
1125
+ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1126
+ if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1127
+ if (!isPlainObject(value)) return false;
1128
+ if (visited.has(value)) return false;
1129
+ visited.add(value);
1130
+ if ("@value" in value) return false;
1131
+ for (const [key, child] of Object.entries(value)) {
1132
+ if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
1133
+ if (hasMalformedExpandedDateTimeLiteral(child)) return true;
1134
+ continue;
1135
+ }
1136
+ if (key === QUESTION_CLOSED_IRI) {
1137
+ if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
1138
+ continue;
1139
+ }
1140
+ if (TEMPORAL_DURATION_IRIS.has(key)) {
1141
+ if (hasMalformedExpandedDurationLiteral(child)) return true;
1142
+ continue;
1143
+ }
1144
+ if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
1145
+ }
1146
+ return false;
1147
+ }
1148
+ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1149
+ try {
1150
+ return hasMalformedKnownTemporalLiteralInternal(await _fedify_vocab_runtime_jsonld.default.expand(value, {
1151
+ documentLoader: require_proof.getNormalizationContextLoader(contextLoader),
1152
+ keepFreeFloatingNodes: true
1153
+ }), /* @__PURE__ */ new Set());
1154
+ } catch {
1155
+ return false;
1156
+ }
1157
+ }
1158
+ //#endregion
1065
1159
  //#region src/federation/handler.ts
1160
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1161
+ function isRemoteContextLoadingFailure$1(error) {
1162
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1163
+ }
1164
+ function isPermanentRemoteContextError$1(error) {
1165
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1166
+ const details = error.details;
1167
+ if (details?.code === "invalid remote context") return true;
1168
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
1169
+ }
1170
+ function isInvalidJsonLdError(error) {
1171
+ if (!(error instanceof Error)) return false;
1172
+ const name = error.name;
1173
+ return name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1174
+ }
1175
+ function isValidationTypeError(error) {
1176
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
1177
+ }
1178
+ function isPermanentActivityParseError(error) {
1179
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
1180
+ }
1181
+ function hasHttpSignatureHeaders(request) {
1182
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
1183
+ }
1184
+ function hasObjectIntegrityProof(json) {
1185
+ return typeof json === "object" && json != null && "proof" in json;
1186
+ }
1066
1187
  /**
1067
1188
  * Handles an actor request.
1068
1189
  * @template TContextData The context data to pass to the context.
@@ -1589,28 +1710,104 @@ async function handleInboxInternal(request, parameters, span) {
1589
1710
  });
1590
1711
  }
1591
1712
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1592
- let ldSigVerified;
1593
- try {
1594
- ldSigVerified = await require_proof.verifyJsonLd(json, {
1595
- contextLoader: ctx.contextLoader,
1596
- documentLoader: ctx.documentLoader,
1597
- keyCache,
1598
- tracerProvider
1713
+ const jsonWithoutSig = require_proof.detachSignature(json);
1714
+ const hasLdSignature = require_proof.hasSignature(json);
1715
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
1716
+ let deferredLdSignatureError = void 0;
1717
+ const respondInvalidActivity = async (error) => {
1718
+ logger.error("Failed to parse activity:\n{error}", {
1719
+ recipient,
1720
+ activity: json,
1721
+ error
1599
1722
  });
1600
- } catch (error) {
1601
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1602
- logger.error("Failed to parse JSON-LD:\n{error}", {
1723
+ try {
1724
+ await inboxErrorHandler?.(ctx, error);
1725
+ } catch (error) {
1726
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1727
+ error,
1728
+ activity: json,
1729
+ recipient
1730
+ });
1731
+ }
1732
+ span.setStatus({
1733
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1734
+ message: `Failed to parse activity:\n${error}`
1735
+ });
1736
+ return new Response("Invalid activity.", {
1737
+ status: 400,
1738
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1739
+ });
1740
+ };
1741
+ let compactedJson = json;
1742
+ let compactedJsonWithoutSig = jsonWithoutSig;
1743
+ let ldSigVerified = false;
1744
+ if (hasLdSignature) {
1745
+ try {
1746
+ compactedJson = await require_proof.compactJsonLd(json, ctx.contextLoader);
1747
+ } catch (error) {
1748
+ if (isInvalidJsonLdError(error)) {
1749
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1750
+ recipient,
1751
+ error
1752
+ });
1753
+ return new Response("Invalid JSON-LD.", {
1754
+ status: 400,
1755
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1756
+ });
1757
+ }
1758
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1759
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
1760
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
1603
1761
  recipient,
1604
1762
  error
1605
1763
  });
1606
- return new Response("Invalid JSON-LD.", {
1607
- status: 400,
1608
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1609
- });
1610
1764
  }
1611
- ldSigVerified = false;
1765
+ if (compactedJson !== json) {
1766
+ compactedJsonWithoutSig = require_proof.detachSignature(compactedJson);
1767
+ try {
1768
+ ldSigVerified = await require_proof.verifyCompactJsonLd(compactedJson, {
1769
+ contextLoader: ctx.contextLoader,
1770
+ documentLoader: ctx.documentLoader,
1771
+ keyCache,
1772
+ tracerProvider
1773
+ });
1774
+ } catch (error) {
1775
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1776
+ if (isInvalidJsonLdError(error)) {
1777
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1778
+ recipient,
1779
+ error
1780
+ });
1781
+ return new Response("Invalid JSON-LD.", {
1782
+ status: 400,
1783
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1784
+ });
1785
+ }
1786
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1787
+ if (!skipSignatureVerification) try {
1788
+ await _fedify_vocab.Object.fromJsonLd(compactedJson, {
1789
+ contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader),
1790
+ documentLoader: ctx.documentLoader,
1791
+ tracerProvider
1792
+ });
1793
+ } catch (parseError) {
1794
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
1795
+ if (isInvalidJsonLdError(parseError)) {
1796
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1797
+ recipient,
1798
+ error: parseError
1799
+ });
1800
+ return new Response("Invalid JSON-LD.", {
1801
+ status: 400,
1802
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1803
+ });
1804
+ }
1805
+ deferredLdSignatureError = parseError;
1806
+ }
1807
+ ldSigVerified = false;
1808
+ }
1809
+ }
1612
1810
  }
1613
- const jsonWithoutSig = require_proof.detachSignature(json);
1614
1811
  let activity = null;
1615
1812
  let activityVerified = false;
1616
1813
  if (ldSigVerified) {
@@ -1618,7 +1815,16 @@ async function handleInboxInternal(request, parameters, span) {
1618
1815
  recipient,
1619
1816
  json
1620
1817
  });
1621
- activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1818
+ try {
1819
+ activity = await _fedify_vocab.Activity.fromJsonLd(compactedJsonWithoutSig, {
1820
+ ...ctx,
1821
+ contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader)
1822
+ });
1823
+ } catch (error) {
1824
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1825
+ if (!isPermanentActivityParseError(error)) throw error;
1826
+ return await respondInvalidActivity(error);
1827
+ }
1622
1828
  activityVerified = true;
1623
1829
  } else {
1624
1830
  logger.debug("Linked Data Signatures are not verified.", {
@@ -1627,12 +1833,21 @@ async function handleInboxInternal(request, parameters, span) {
1627
1833
  });
1628
1834
  try {
1629
1835
  activity = await require_proof.verifyObject(_fedify_vocab.Activity, jsonWithoutSig, {
1630
- contextLoader: ctx.contextLoader,
1836
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader),
1631
1837
  documentLoader: ctx.documentLoader,
1632
1838
  keyCache,
1633
1839
  tracerProvider
1634
1840
  });
1635
1841
  } catch (error) {
1842
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1843
+ if (deferredLdSignatureError != null) {
1844
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
1845
+ recipient,
1846
+ error
1847
+ });
1848
+ activity = null;
1849
+ }
1850
+ if (!isPermanentActivityParseError(error)) throw error;
1636
1851
  logger.error("Failed to parse activity:\n{error}", {
1637
1852
  recipient,
1638
1853
  activity: json,
@@ -1680,6 +1895,7 @@ async function handleInboxInternal(request, parameters, span) {
1680
1895
  tracerProvider
1681
1896
  });
1682
1897
  if (verification.verified === false) {
1898
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1683
1899
  const reason = verification.reason;
1684
1900
  logger.error("Failed to verify the request's HTTP Signatures.", {
1685
1901
  recipient,
@@ -1755,7 +1971,15 @@ async function handleInboxInternal(request, parameters, span) {
1755
1971
  }
1756
1972
  httpSigKey = verification.key;
1757
1973
  }
1758
- activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1974
+ try {
1975
+ activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, {
1976
+ ...ctx,
1977
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader)
1978
+ });
1979
+ } catch (error) {
1980
+ if (!isPermanentActivityParseError(error)) throw error;
1981
+ return await respondInvalidActivity(error);
1982
+ }
1759
1983
  }
1760
1984
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1761
1985
  span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
@@ -1767,6 +1991,7 @@ async function handleInboxInternal(request, parameters, span) {
1767
1991
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
1768
1992
  });
1769
1993
  if (httpSigKey != null && !await require_proof.doesActorOwnKey(activity, httpSigKey, ctx)) {
1994
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1770
1995
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1771
1996
  activity: json,
1772
1997
  recipient,
@@ -1791,10 +2016,14 @@ async function handleInboxInternal(request, parameters, span) {
1791
2016
  const routeResult = await routeActivity({
1792
2017
  context: ctx,
1793
2018
  json,
2019
+ originalJson: json,
2020
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
2021
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1794
2022
  activity,
1795
2023
  recipient,
1796
2024
  inboxListeners,
1797
2025
  inboxContextFactory,
2026
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1798
2027
  inboxErrorHandler,
1799
2028
  kv,
1800
2029
  kvPrefixes,
@@ -2710,6 +2939,18 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
2710
2939
  }
2711
2940
  //#endregion
2712
2941
  //#region src/federation/middleware.ts
2942
+ function isRemoteContextLoadingFailure(error) {
2943
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
2944
+ }
2945
+ function isPermanentRemoteContextError(error) {
2946
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
2947
+ const details = error.details;
2948
+ if (details?.code === "invalid remote context") return true;
2949
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
2950
+ }
2951
+ function isPermanentInboxParseError(error) {
2952
+ return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
2953
+ }
2713
2954
  /**
2714
2955
  * Create a new {@link Federation} instance.
2715
2956
  * @param parameters Parameters for initializing the instance.
@@ -3091,7 +3332,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3091
3332
  }
3092
3333
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
3093
3334
  }
3094
- async #listenInboxMessage(ctxData, message, span) {
3335
+ async #listenInboxMessage(ctxData, message, _span) {
3095
3336
  const logger = (0, _logtape_logtape.getLogger)([
3096
3337
  "fedify",
3097
3338
  "federation",
@@ -3104,60 +3345,28 @@ var FederationImpl = class extends FederationBuilderImpl {
3104
3345
  const identity = await this.sharedInboxKeyDispatcher(context);
3105
3346
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3106
3347
  }
3107
- const activity = await _fedify_vocab.Activity.fromJsonLd(message.activity, context);
3108
- span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
3109
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3110
- const cacheKey = activity.id == null ? null : [
3111
- ...this.kvPrefixes.activityIdempotence,
3112
- context.origin,
3113
- activity.id.href
3114
- ];
3115
- if (cacheKey != null) {
3116
- if (await this.kv.get(cacheKey) === true) {
3117
- logger.debug("Activity {activityId} has already been processed.", {
3118
- activityId: activity.id?.href,
3119
- activity: message.activity,
3120
- recipient: message.identifier
3121
- });
3122
- return;
3123
- }
3124
- }
3125
3348
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.SpanKind.INTERNAL }, async (span) => {
3126
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
3127
- if (dispatched == null) {
3128
- logger.error("Unsupported activity type:\n{activity}", {
3129
- activityId: activity.id?.href,
3130
- activity: message.activity,
3131
- recipient: message.identifier,
3132
- trial: message.attempt
3133
- });
3134
- span.setStatus({
3135
- code: _opentelemetry_api.SpanStatusCode.ERROR,
3136
- message: `Unsupported activity type: ${(0, _fedify_vocab.getTypeId)(activity).href}`
3137
- });
3138
- span.end();
3139
- return;
3140
- }
3141
- const { class: cls, listener } = dispatched;
3142
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3143
- try {
3144
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href), activity);
3145
- } catch (error) {
3349
+ let activity = null;
3350
+ let cacheKey = null;
3351
+ const reportInboxError = async (error) => {
3146
3352
  try {
3147
3353
  await this.inboxErrorHandler?.(context, error);
3148
3354
  } catch (error) {
3149
3355
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
3150
3356
  error,
3151
3357
  trial: message.attempt,
3152
- activityId: activity.id?.href,
3358
+ activityId: activity?.id?.href,
3153
3359
  activity: message.activity,
3154
3360
  recipient: message.identifier
3155
3361
  });
3156
3362
  }
3363
+ };
3364
+ const handleRetriableFailure = async (error) => {
3365
+ await reportInboxError(error);
3157
3366
  if (this.inboxQueue?.nativeRetrial) {
3158
3367
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
3159
3368
  error,
3160
- activityId: activity.id?.href,
3369
+ activityId: activity?.id?.href,
3161
3370
  activity: message.activity,
3162
3371
  recipient: message.identifier
3163
3372
  });
@@ -3176,17 +3385,25 @@ var FederationImpl = class extends FederationBuilderImpl {
3176
3385
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
3177
3386
  error,
3178
3387
  attempt: message.attempt,
3179
- activityId: activity.id?.href,
3388
+ activityId: activity?.id?.href,
3180
3389
  activity: message.activity,
3181
3390
  recipient: message.identifier
3182
3391
  });
3392
+ if (this.inboxQueue == null) {
3393
+ span.setStatus({
3394
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3395
+ message: String(error)
3396
+ });
3397
+ span.end();
3398
+ throw error;
3399
+ }
3183
3400
  await this.inboxQueue?.enqueue({
3184
3401
  ...message,
3185
3402
  attempt: message.attempt + 1
3186
3403
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3187
3404
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3188
3405
  error,
3189
- activityId: activity.id?.href,
3406
+ activityId: activity?.id?.href,
3190
3407
  activity: message.activity,
3191
3408
  recipient: message.identifier
3192
3409
  });
@@ -3195,11 +3412,108 @@ var FederationImpl = class extends FederationBuilderImpl {
3195
3412
  message: String(error)
3196
3413
  });
3197
3414
  span.end();
3415
+ };
3416
+ let dispatched;
3417
+ let parseInput = void 0;
3418
+ let parseContextLoader = context.contextLoader;
3419
+ try {
3420
+ const hasSignatureField = require_proof.hasSignature(message.activity);
3421
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
3422
+ const parseContext = hasSignatureField ? {
3423
+ ...context,
3424
+ contextLoader: require_proof.getNormalizationContextLoader(context.contextLoader)
3425
+ } : {
3426
+ ...context,
3427
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(context.contextLoader)
3428
+ };
3429
+ parseContextLoader = parseContext.contextLoader;
3430
+ let normalizedActivity;
3431
+ if (shouldParseFromNormalizedSignedPayload) {
3432
+ normalizedActivity = message.normalizedActivity ?? await require_proof.compactJsonLd(message.activity, context.contextLoader);
3433
+ require_proof.assertSafeJsonLd(normalizedActivity);
3434
+ }
3435
+ parseInput = shouldParseFromNormalizedSignedPayload ? require_proof.detachSignature(normalizedActivity) : hasSignatureField ? require_proof.detachSignature(message.activity) : message.activity;
3436
+ activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
3437
+ span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
3438
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3439
+ cacheKey = activity.id == null ? null : [
3440
+ ...this.kvPrefixes.activityIdempotence,
3441
+ context.origin,
3442
+ activity.id.href
3443
+ ];
3444
+ if (cacheKey != null) {
3445
+ if (await this.kv.get(cacheKey) === true) {
3446
+ logger.debug("Activity {activityId} has already been processed.", {
3447
+ activityId: activity.id?.href,
3448
+ activity: message.activity,
3449
+ recipient: message.identifier
3450
+ });
3451
+ span.end();
3452
+ return;
3453
+ }
3454
+ }
3455
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
3456
+ } catch (error) {
3457
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3458
+ await reportInboxError(error);
3459
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3460
+ error,
3461
+ trial: message.attempt,
3462
+ activityId: null,
3463
+ activity: message.activity,
3464
+ recipient: message.identifier
3465
+ });
3466
+ span.setStatus({
3467
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3468
+ message: String(error)
3469
+ });
3470
+ span.end();
3471
+ return;
3472
+ }
3473
+ if (isPermanentInboxParseError(error)) {
3474
+ await reportInboxError(error);
3475
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3476
+ error,
3477
+ trial: message.attempt,
3478
+ activityId: activity?.id?.href,
3479
+ activity: message.activity,
3480
+ recipient: message.identifier
3481
+ });
3482
+ span.setStatus({
3483
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3484
+ message: String(error)
3485
+ });
3486
+ span.end();
3487
+ return;
3488
+ }
3489
+ await handleRetriableFailure(error);
3490
+ return;
3491
+ }
3492
+ if (dispatched == null) {
3493
+ logger.error("Unsupported activity type:\n{activity}", {
3494
+ activityId: activity.id?.href,
3495
+ activity: message.activity,
3496
+ recipient: message.identifier,
3497
+ trial: message.attempt
3498
+ });
3499
+ span.setStatus({
3500
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3501
+ message: `Unsupported activity type: ${(0, _fedify_vocab.getTypeId)(activity).href}`
3502
+ });
3503
+ span.end();
3504
+ return;
3505
+ }
3506
+ const { class: cls, listener } = dispatched;
3507
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3508
+ try {
3509
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href), activity);
3510
+ } catch (error) {
3511
+ await handleRetriableFailure(error);
3198
3512
  return;
3199
3513
  }
3200
3514
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
3201
3515
  logger.info("Activity {activityId} has been processed.", {
3202
- activityId: activity.id?.href,
3516
+ activityId: activity?.id?.href,
3203
3517
  activity: message.activity,
3204
3518
  recipient: message.identifier
3205
3519
  });
@@ -3569,16 +3883,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3569
3883
  onNotFound
3570
3884
  });
3571
3885
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
3572
- case "sharedInbox":
3886
+ case "sharedInbox": {
3573
3887
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3574
3888
  const identity = await this.sharedInboxKeyDispatcher(context);
3575
3889
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3576
3890
  }
3577
3891
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3892
+ const inboxContextFactory = context.toInboxContext.bind(context);
3893
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3578
3894
  return await handleInbox(request, {
3579
3895
  recipient: route.values.identifier ?? null,
3580
3896
  context,
3581
- inboxContextFactory: context.toInboxContext.bind(context),
3897
+ inboxContextFactory,
3582
3898
  kv: this.kv,
3583
3899
  kvPrefixes: this.kvPrefixes,
3584
3900
  queue: this.inboxQueue,
@@ -3593,6 +3909,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3593
3909
  tracerProvider: this.tracerProvider,
3594
3910
  idempotencyStrategy: this.idempotencyStrategy
3595
3911
  });
3912
+ }
3596
3913
  case "following": return await handleCollection(request, {
3597
3914
  name: "following",
3598
3915
  identifier: route.values.identifier,
@@ -4293,6 +4610,7 @@ var ContextImpl = class ContextImpl {
4293
4610
  const routeResult = await routeActivity({
4294
4611
  context: this,
4295
4612
  json,
4613
+ ldSignatureVerified: false,
4296
4614
  activity,
4297
4615
  recipient,
4298
4616
  inboxListeners: this.federation.inboxListeners,
@@ -4573,6 +4891,14 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4573
4891
  }
4574
4892
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4575
4893
  recipient;
4894
+ /**
4895
+ * The original received activity payload.
4896
+ *
4897
+ * Fedify may normalize a Linked Data Signature payload internally for safe
4898
+ * parsing, but forwarding must keep the sender's payload unchanged so
4899
+ * third-party signatures/proofs remain intact.
4900
+ * @internal
4901
+ */
4576
4902
  activity;
4577
4903
  activityId;
4578
4904
  activityType;