@fedify/fedify 2.0.17 → 2.0.18

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 (62) hide show
  1. package/dist/{builder-Dgk0oJse.mjs → builder-CtrAeTRY.mjs} +3 -3
  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-BzRJ49gL.d.cts → context-CJZHWLmz.d.cts} +48 -6
  6. package/dist/{context-CdJ9wAQa.d.ts → context-DnsFj3yK.d.ts} +48 -6
  7. package/dist/{deno-Cb0U4mYY.mjs → deno-mPpS1a5z.mjs} +1 -1
  8. package/dist/{docloader-BtgBOjR2.mjs → docloader-B-SKc43w.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1358 -41
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/inbox.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +1588 -84
  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 +3 -3
  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/{http-C8fyMUjD.cjs → http-BL8JVR8k.cjs} +1 -1
  24. package/dist/{http-DOir4i5c.js → http-C3tabf9n.js} +1 -1
  25. package/dist/{http-CkpjqreM.mjs → http-CEsXGtbz.mjs} +2 -2
  26. package/dist/{inbox-VNZAN2rq.mjs → inbox-Tsb61Zs7.mjs} +7 -4
  27. package/dist/{key-jLdTQH2f.mjs → key-BaYP-_dg.mjs} +1 -1
  28. package/dist/{kv-cache-D7YHL8kZ.cjs → kv-cache-BsgxbwLm.cjs} +1 -1
  29. package/dist/{kv-cache-BT1ZahAh.js → kv-cache-Dbi0W6UJ.js} +1 -1
  30. package/dist/ld-CAaJNVwd.mjs +550 -0
  31. package/dist/{middleware-o4he3VG2.js → middleware-B3xIsf51.js} +396 -71
  32. package/dist/{middleware-ChZir5hz.mjs → middleware-CXy6C5p8.mjs} +1 -1
  33. package/dist/{middleware-BIaxjP-q.cjs → middleware-CmqvwFWM.cjs} +399 -73
  34. package/dist/{middleware-BmYsY7pr.cjs → middleware-DXrzwPDQ.cjs} +1 -1
  35. package/dist/{middleware-Bua1eJKj.mjs → middleware-qU3blEV5.mjs} +309 -76
  36. package/dist/{mod-CKkpvznG.d.cts → mod-43DJyETz.d.cts} +1 -1
  37. package/dist/{mod-Kzr52ns8.d.ts → mod-COgmn-UO.d.ts} +1 -1
  38. package/dist/mod.cjs +4 -4
  39. package/dist/mod.d.cts +2 -2
  40. package/dist/mod.d.ts +2 -2
  41. package/dist/mod.js +4 -4
  42. package/dist/nodeinfo/handler.test.mjs +1 -1
  43. package/dist/{owner-9JLKNjOK.mjs → owner-DdHZSQaN.mjs} +2 -2
  44. package/dist/{proof-CVNf-KlO.mjs → proof-Cr7l6UvL.mjs} +2 -2
  45. package/dist/{proof-DWK4MOe5.cjs → proof-DTHPJVvm.cjs} +345 -3
  46. package/dist/{proof-Bds_rOyf.js → proof-wXzMwWVt.js} +298 -4
  47. package/dist/{send-j6vHPA3l.mjs → send-ChZJgc6Y.mjs} +2 -2
  48. package/dist/sig/http.test.mjs +2 -2
  49. package/dist/sig/key.test.mjs +1 -1
  50. package/dist/sig/ld.test.mjs +558 -2
  51. package/dist/sig/mod.cjs +2 -2
  52. package/dist/sig/mod.js +2 -2
  53. package/dist/sig/owner.test.mjs +1 -1
  54. package/dist/sig/proof.test.mjs +1 -1
  55. package/dist/temporal-Bxr6zsNS.mjs +95 -0
  56. package/dist/testing/mod.d.mts +48 -6
  57. package/dist/utils/docloader.test.mjs +2 -2
  58. package/dist/utils/mod.cjs +1 -1
  59. package/dist/utils/mod.js +1 -1
  60. package/package.json +5 -5
  61. package/dist/ld-C48i1Mdp.mjs +0 -256
  62. /package/dist/{retry-Ddbq3AcK.mjs → retry-B_E3V_Dx.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 validateCryptoKey, i as exportJwk, l as name, r as verifyRequest, s as importJwk, t as doubleKnock, u as version } from "./http-DOir4i5c.js";
6
- import { a as doesActorOwnKey, d as signJsonLd, f as verifyJsonLd, l as detachSignature, n as signObject, o as getKeyOwner, r as verifyObject, u as hasSignature } from "./proof-Bds_rOyf.js";
5
+ import { c as validateCryptoKey, i as exportJwk, l as name, r as verifyRequest, s as importJwk, t as doubleKnock, u as version } from "./http-C3tabf9n.js";
6
+ import { _ as signJsonLd, a as doesActorOwnKey, c as assertSafeJsonLd, f as detachSignature, g as isInvalidUrlTypeError, h as isClearlyMalformedContextReference, m as hasSignature, n as signObject, o as getKeyOwner, p as getNormalizationContextLoader, r as verifyObject, s as InvalidContextReferenceError, u as compactJsonLd, v as verifyCompactJsonLd, x as wrapContextLoaderForJsonLd } from "./proof-wXzMwWVt.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-BT1ZahAh.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-Dbi0W6UJ.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -15,6 +15,7 @@ import { parseTemplate } from "url-template";
15
15
  import { encodeHex } from "byte-encodings/hex";
16
16
  import { getDocumentLoader } 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
+ import jsonld from "@fedify/vocab-runtime/jsonld";
18
19
  import { lookupWebFinger } from "@fedify/webfinger";
19
20
  import { domainToASCII } from "node:url";
20
21
  //#region src/federation/inbox.ts
@@ -51,7 +52,7 @@ var InboxListenerSet = class InboxListenerSet {
51
52
  return this.dispatchWithClass(activity)?.listener ?? null;
52
53
  }
53
54
  };
54
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
55
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
55
56
  const logger = getLogger([
56
57
  "fedify",
57
58
  "federation",
@@ -108,7 +109,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
108
109
  type: "inbox",
109
110
  id: crypto.randomUUID(),
110
111
  baseUrl: ctx.origin,
111
- activity: json,
112
+ activity: originalJson ?? json,
113
+ ...normalizedActivity == null ? {} : { normalizedActivity },
114
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
112
115
  identifier: recipient,
113
116
  attempt: 0,
114
117
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -152,7 +155,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
152
155
  const { class: cls, listener } = dispatched;
153
156
  span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
154
157
  try {
155
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
158
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
159
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, getTypeId(activity).href), activity);
156
160
  } catch (error) {
157
161
  try {
158
162
  await inboxErrorHandler?.(ctx, error);
@@ -959,7 +963,123 @@ function acceptsJsonLd(request) {
959
963
  return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
960
964
  }
961
965
  //#endregion
966
+ //#region src/federation/temporal.ts
967
+ function isPlainObject(value) {
968
+ return typeof value === "object" && value != null && !Array.isArray(value);
969
+ }
970
+ function normalizeDateTimeLiteral(value) {
971
+ return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
972
+ }
973
+ function isMalformedDateTimeLiteral(value) {
974
+ if (typeof value !== "string") return false;
975
+ try {
976
+ Temporal.Instant.from(normalizeDateTimeLiteral(value));
977
+ return false;
978
+ } catch {
979
+ return true;
980
+ }
981
+ }
982
+ function isMalformedDurationLiteral(value) {
983
+ if (typeof value !== "string") return false;
984
+ try {
985
+ Temporal.Duration.from(value);
986
+ return false;
987
+ } catch {
988
+ return true;
989
+ }
990
+ }
991
+ const TEMPORAL_DATE_TIME_IRIS = new Set([
992
+ "https://www.w3.org/ns/activitystreams#deleted",
993
+ "https://www.w3.org/ns/activitystreams#endTime",
994
+ "https://www.w3.org/ns/activitystreams#published",
995
+ "https://www.w3.org/ns/activitystreams#startTime",
996
+ "https://www.w3.org/ns/activitystreams#updated",
997
+ "http://purl.org/dc/terms/created",
998
+ "https://w3id.org/security#created"
999
+ ]);
1000
+ const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1001
+ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1002
+ const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1003
+ function hasMalformedExpandedDateTimeLiteral(value) {
1004
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1005
+ return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1006
+ }
1007
+ function hasMalformedExpandedQuestionClosedLiteral(value) {
1008
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1009
+ if (!isPlainObject(value) || !("@value" in value)) return false;
1010
+ const literal = value["@value"];
1011
+ if (typeof literal === "boolean") return false;
1012
+ if (typeof literal !== "string") return false;
1013
+ if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
1014
+ if (new Date(literal).toString() === "Invalid Date") return false;
1015
+ return isMalformedDateTimeLiteral(literal);
1016
+ }
1017
+ function hasMalformedExpandedDurationLiteral(value) {
1018
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1019
+ return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1020
+ }
1021
+ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1022
+ if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1023
+ if (!isPlainObject(value)) return false;
1024
+ if (visited.has(value)) return false;
1025
+ visited.add(value);
1026
+ if ("@value" in value) return false;
1027
+ for (const [key, child] of Object.entries(value)) {
1028
+ if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
1029
+ if (hasMalformedExpandedDateTimeLiteral(child)) return true;
1030
+ continue;
1031
+ }
1032
+ if (key === QUESTION_CLOSED_IRI) {
1033
+ if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
1034
+ continue;
1035
+ }
1036
+ if (TEMPORAL_DURATION_IRIS.has(key)) {
1037
+ if (hasMalformedExpandedDurationLiteral(child)) return true;
1038
+ continue;
1039
+ }
1040
+ if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
1041
+ }
1042
+ return false;
1043
+ }
1044
+ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1045
+ try {
1046
+ return hasMalformedKnownTemporalLiteralInternal(await jsonld.expand(value, {
1047
+ documentLoader: getNormalizationContextLoader(contextLoader),
1048
+ keepFreeFloatingNodes: true
1049
+ }), /* @__PURE__ */ new Set());
1050
+ } catch {
1051
+ return false;
1052
+ }
1053
+ }
1054
+ //#endregion
962
1055
  //#region src/federation/handler.ts
1056
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1057
+ function isRemoteContextLoadingFailure$1(error) {
1058
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1059
+ }
1060
+ function isPermanentRemoteContextError$1(error) {
1061
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1062
+ const details = error.details;
1063
+ if (details?.code === "invalid remote context") return true;
1064
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
1065
+ }
1066
+ function isInvalidJsonLdError(error) {
1067
+ if (!(error instanceof Error)) return false;
1068
+ const name = error.name;
1069
+ return name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1070
+ }
1071
+ function isValidationTypeError(error) {
1072
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
1073
+ }
1074
+ function isPermanentActivityParseError(error) {
1075
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
1076
+ }
1077
+ function hasHttpSignatureHeaders(request) {
1078
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
1079
+ }
1080
+ function hasObjectIntegrityProof(json) {
1081
+ return typeof json === "object" && json != null && "proof" in json;
1082
+ }
963
1083
  /**
964
1084
  * Handles an actor request.
965
1085
  * @template TContextData The context data to pass to the context.
@@ -1279,35 +1399,120 @@ async function handleInboxInternal(request, parameters, span) {
1279
1399
  });
1280
1400
  }
1281
1401
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1282
- let ldSigVerified;
1283
- try {
1284
- ldSigVerified = await verifyJsonLd(json, {
1285
- contextLoader: ctx.contextLoader,
1286
- documentLoader: ctx.documentLoader,
1287
- keyCache,
1288
- tracerProvider
1402
+ const jsonWithoutSig = detachSignature(json);
1403
+ const hasLdSignature = hasSignature(json);
1404
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
1405
+ let deferredLdSignatureError = void 0;
1406
+ const respondInvalidActivity = async (error) => {
1407
+ logger.error("Failed to parse activity:\n{error}", {
1408
+ recipient,
1409
+ activity: json,
1410
+ error
1289
1411
  });
1290
- } catch (error) {
1291
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1292
- logger.error("Failed to parse JSON-LD:\n{error}", {
1412
+ try {
1413
+ await inboxErrorHandler?.(ctx, error);
1414
+ } catch (error) {
1415
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1416
+ error,
1417
+ activity: json,
1418
+ recipient
1419
+ });
1420
+ }
1421
+ span.setStatus({
1422
+ code: SpanStatusCode.ERROR,
1423
+ message: `Failed to parse activity:\n${error}`
1424
+ });
1425
+ return new Response("Invalid activity.", {
1426
+ status: 400,
1427
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1428
+ });
1429
+ };
1430
+ let compactedJson = json;
1431
+ let compactedJsonWithoutSig = jsonWithoutSig;
1432
+ let ldSigVerified = false;
1433
+ if (hasLdSignature) {
1434
+ try {
1435
+ compactedJson = await compactJsonLd(json, ctx.contextLoader);
1436
+ } catch (error) {
1437
+ if (isInvalidJsonLdError(error)) {
1438
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1439
+ recipient,
1440
+ error
1441
+ });
1442
+ return new Response("Invalid JSON-LD.", {
1443
+ status: 400,
1444
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1445
+ });
1446
+ }
1447
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1448
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
1449
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
1293
1450
  recipient,
1294
1451
  error
1295
1452
  });
1296
- return new Response("Invalid JSON-LD.", {
1297
- status: 400,
1298
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1299
- });
1300
1453
  }
1301
- ldSigVerified = false;
1454
+ if (compactedJson !== json) {
1455
+ compactedJsonWithoutSig = detachSignature(compactedJson);
1456
+ try {
1457
+ ldSigVerified = await verifyCompactJsonLd(compactedJson, {
1458
+ contextLoader: ctx.contextLoader,
1459
+ documentLoader: ctx.documentLoader,
1460
+ keyCache,
1461
+ tracerProvider
1462
+ });
1463
+ } catch (error) {
1464
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1465
+ if (isInvalidJsonLdError(error)) {
1466
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1467
+ recipient,
1468
+ error
1469
+ });
1470
+ return new Response("Invalid JSON-LD.", {
1471
+ status: 400,
1472
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1473
+ });
1474
+ }
1475
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1476
+ if (!skipSignatureVerification) try {
1477
+ await Object$1.fromJsonLd(compactedJson, {
1478
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader),
1479
+ documentLoader: ctx.documentLoader,
1480
+ tracerProvider
1481
+ });
1482
+ } catch (parseError) {
1483
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
1484
+ if (isInvalidJsonLdError(parseError)) {
1485
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1486
+ recipient,
1487
+ error: parseError
1488
+ });
1489
+ return new Response("Invalid JSON-LD.", {
1490
+ status: 400,
1491
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1492
+ });
1493
+ }
1494
+ deferredLdSignatureError = parseError;
1495
+ }
1496
+ ldSigVerified = false;
1497
+ }
1498
+ }
1302
1499
  }
1303
- const jsonWithoutSig = detachSignature(json);
1304
1500
  let activity = null;
1305
1501
  if (ldSigVerified) {
1306
1502
  logger.debug("Linked Data Signatures are verified.", {
1307
1503
  recipient,
1308
1504
  json
1309
1505
  });
1310
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1506
+ try {
1507
+ activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
1508
+ ...ctx,
1509
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader)
1510
+ });
1511
+ } catch (error) {
1512
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1513
+ if (!isPermanentActivityParseError(error)) throw error;
1514
+ return await respondInvalidActivity(error);
1515
+ }
1311
1516
  } else {
1312
1517
  logger.debug("Linked Data Signatures are not verified.", {
1313
1518
  recipient,
@@ -1315,12 +1520,21 @@ async function handleInboxInternal(request, parameters, span) {
1315
1520
  });
1316
1521
  try {
1317
1522
  activity = await verifyObject(Activity, jsonWithoutSig, {
1318
- contextLoader: ctx.contextLoader,
1523
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
1319
1524
  documentLoader: ctx.documentLoader,
1320
1525
  keyCache,
1321
1526
  tracerProvider
1322
1527
  });
1323
1528
  } catch (error) {
1529
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1530
+ if (deferredLdSignatureError != null) {
1531
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
1532
+ recipient,
1533
+ error
1534
+ });
1535
+ activity = null;
1536
+ }
1537
+ if (!isPermanentActivityParseError(error)) throw error;
1324
1538
  logger.error("Failed to parse activity:\n{error}", {
1325
1539
  recipient,
1326
1540
  activity: json,
@@ -1364,6 +1578,7 @@ async function handleInboxInternal(request, parameters, span) {
1364
1578
  tracerProvider
1365
1579
  });
1366
1580
  if (key == null) {
1581
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1367
1582
  logger.error("Failed to verify the request's HTTP Signatures.", { recipient });
1368
1583
  span.setStatus({
1369
1584
  code: SpanStatusCode.ERROR,
@@ -1376,7 +1591,15 @@ async function handleInboxInternal(request, parameters, span) {
1376
1591
  } else logger.debug("HTTP Signatures are verified.", { recipient });
1377
1592
  httpSigKey = key;
1378
1593
  }
1379
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1594
+ try {
1595
+ activity = await Activity.fromJsonLd(jsonWithoutSig, {
1596
+ ...ctx,
1597
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
1598
+ });
1599
+ } catch (error) {
1600
+ if (!isPermanentActivityParseError(error)) throw error;
1601
+ return await respondInvalidActivity(error);
1602
+ }
1380
1603
  }
1381
1604
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1382
1605
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
@@ -1388,6 +1611,7 @@ async function handleInboxInternal(request, parameters, span) {
1388
1611
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
1389
1612
  });
1390
1613
  if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
1614
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1391
1615
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1392
1616
  activity: json,
1393
1617
  recipient,
@@ -1406,10 +1630,14 @@ async function handleInboxInternal(request, parameters, span) {
1406
1630
  const routeResult = await routeActivity({
1407
1631
  context: ctx,
1408
1632
  json,
1633
+ originalJson: json,
1634
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
1635
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1409
1636
  activity,
1410
1637
  recipient,
1411
1638
  inboxListeners,
1412
1639
  inboxContextFactory,
1640
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1413
1641
  inboxErrorHandler,
1414
1642
  kv,
1415
1643
  kvPrefixes,
@@ -2257,6 +2485,18 @@ var middleware_exports = /* @__PURE__ */ __exportAll({
2257
2485
  KvSpecDeterminer: () => KvSpecDeterminer,
2258
2486
  createFederation: () => createFederation
2259
2487
  });
2488
+ function isRemoteContextLoadingFailure(error) {
2489
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
2490
+ }
2491
+ function isPermanentRemoteContextError(error) {
2492
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
2493
+ const details = error.details;
2494
+ if (details?.code === "invalid remote context") return true;
2495
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
2496
+ }
2497
+ function isPermanentInboxParseError(error) {
2498
+ 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));
2499
+ }
2260
2500
  /**
2261
2501
  * Create a new {@link Federation} instance.
2262
2502
  * @param parameters Parameters for initializing the instance.
@@ -2634,7 +2874,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2634
2874
  }
2635
2875
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
2636
2876
  }
2637
- async #listenInboxMessage(ctxData, message, span) {
2877
+ async #listenInboxMessage(ctxData, message, _span) {
2638
2878
  const logger = getLogger([
2639
2879
  "fedify",
2640
2880
  "federation",
@@ -2647,60 +2887,28 @@ var FederationImpl = class extends FederationBuilderImpl {
2647
2887
  const identity = await this.sharedInboxKeyDispatcher(context);
2648
2888
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
2649
2889
  }
2650
- const activity = await Activity.fromJsonLd(message.activity, context);
2651
- span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
2652
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2653
- const cacheKey = activity.id == null ? null : [
2654
- ...this.kvPrefixes.activityIdempotence,
2655
- context.origin,
2656
- activity.id.href
2657
- ];
2658
- if (cacheKey != null) {
2659
- if (await this.kv.get(cacheKey) === true) {
2660
- logger.debug("Activity {activityId} has already been processed.", {
2661
- activityId: activity.id?.href,
2662
- activity: message.activity,
2663
- recipient: message.identifier
2664
- });
2665
- return;
2666
- }
2667
- }
2668
2890
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
2669
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
2670
- if (dispatched == null) {
2671
- logger.error("Unsupported activity type:\n{activity}", {
2672
- activityId: activity.id?.href,
2673
- activity: message.activity,
2674
- recipient: message.identifier,
2675
- trial: message.attempt
2676
- });
2677
- span.setStatus({
2678
- code: SpanStatusCode.ERROR,
2679
- message: `Unsupported activity type: ${getTypeId(activity).href}`
2680
- });
2681
- span.end();
2682
- return;
2683
- }
2684
- const { class: cls, listener } = dispatched;
2685
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
2686
- try {
2687
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
2688
- } catch (error) {
2891
+ let activity = null;
2892
+ let cacheKey = null;
2893
+ const reportInboxError = async (error) => {
2689
2894
  try {
2690
2895
  await this.inboxErrorHandler?.(context, error);
2691
2896
  } catch (error) {
2692
2897
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
2693
2898
  error,
2694
2899
  trial: message.attempt,
2695
- activityId: activity.id?.href,
2900
+ activityId: activity?.id?.href,
2696
2901
  activity: message.activity,
2697
2902
  recipient: message.identifier
2698
2903
  });
2699
2904
  }
2905
+ };
2906
+ const handleRetriableFailure = async (error) => {
2907
+ await reportInboxError(error);
2700
2908
  if (this.inboxQueue?.nativeRetrial) {
2701
2909
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
2702
2910
  error,
2703
- activityId: activity.id?.href,
2911
+ activityId: activity?.id?.href,
2704
2912
  activity: message.activity,
2705
2913
  recipient: message.identifier
2706
2914
  });
@@ -2719,17 +2927,25 @@ var FederationImpl = class extends FederationBuilderImpl {
2719
2927
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
2720
2928
  error,
2721
2929
  attempt: message.attempt,
2722
- activityId: activity.id?.href,
2930
+ activityId: activity?.id?.href,
2723
2931
  activity: message.activity,
2724
2932
  recipient: message.identifier
2725
2933
  });
2934
+ if (this.inboxQueue == null) {
2935
+ span.setStatus({
2936
+ code: SpanStatusCode.ERROR,
2937
+ message: String(error)
2938
+ });
2939
+ span.end();
2940
+ throw error;
2941
+ }
2726
2942
  await this.inboxQueue?.enqueue({
2727
2943
  ...message,
2728
2944
  attempt: message.attempt + 1
2729
2945
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
2730
2946
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
2731
2947
  error,
2732
- activityId: activity.id?.href,
2948
+ activityId: activity?.id?.href,
2733
2949
  activity: message.activity,
2734
2950
  recipient: message.identifier
2735
2951
  });
@@ -2738,11 +2954,108 @@ var FederationImpl = class extends FederationBuilderImpl {
2738
2954
  message: String(error)
2739
2955
  });
2740
2956
  span.end();
2957
+ };
2958
+ let dispatched;
2959
+ let parseInput = void 0;
2960
+ let parseContextLoader = context.contextLoader;
2961
+ try {
2962
+ const hasSignatureField = hasSignature(message.activity);
2963
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
2964
+ const parseContext = hasSignatureField ? {
2965
+ ...context,
2966
+ contextLoader: getNormalizationContextLoader(context.contextLoader)
2967
+ } : {
2968
+ ...context,
2969
+ contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
2970
+ };
2971
+ parseContextLoader = parseContext.contextLoader;
2972
+ let normalizedActivity;
2973
+ if (shouldParseFromNormalizedSignedPayload) {
2974
+ normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
2975
+ assertSafeJsonLd(normalizedActivity);
2976
+ }
2977
+ parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
2978
+ activity = await Activity.fromJsonLd(parseInput, parseContext);
2979
+ span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
2980
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2981
+ cacheKey = activity.id == null ? null : [
2982
+ ...this.kvPrefixes.activityIdempotence,
2983
+ context.origin,
2984
+ activity.id.href
2985
+ ];
2986
+ if (cacheKey != null) {
2987
+ if (await this.kv.get(cacheKey) === true) {
2988
+ logger.debug("Activity {activityId} has already been processed.", {
2989
+ activityId: activity.id?.href,
2990
+ activity: message.activity,
2991
+ recipient: message.identifier
2992
+ });
2993
+ span.end();
2994
+ return;
2995
+ }
2996
+ }
2997
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
2998
+ } catch (error) {
2999
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3000
+ await reportInboxError(error);
3001
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3002
+ error,
3003
+ trial: message.attempt,
3004
+ activityId: null,
3005
+ activity: message.activity,
3006
+ recipient: message.identifier
3007
+ });
3008
+ span.setStatus({
3009
+ code: SpanStatusCode.ERROR,
3010
+ message: String(error)
3011
+ });
3012
+ span.end();
3013
+ return;
3014
+ }
3015
+ if (isPermanentInboxParseError(error)) {
3016
+ await reportInboxError(error);
3017
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3018
+ error,
3019
+ trial: message.attempt,
3020
+ activityId: activity?.id?.href,
3021
+ activity: message.activity,
3022
+ recipient: message.identifier
3023
+ });
3024
+ span.setStatus({
3025
+ code: SpanStatusCode.ERROR,
3026
+ message: String(error)
3027
+ });
3028
+ span.end();
3029
+ return;
3030
+ }
3031
+ await handleRetriableFailure(error);
3032
+ return;
3033
+ }
3034
+ if (dispatched == null) {
3035
+ logger.error("Unsupported activity type:\n{activity}", {
3036
+ activityId: activity.id?.href,
3037
+ activity: message.activity,
3038
+ recipient: message.identifier,
3039
+ trial: message.attempt
3040
+ });
3041
+ span.setStatus({
3042
+ code: SpanStatusCode.ERROR,
3043
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
3044
+ });
3045
+ span.end();
3046
+ return;
3047
+ }
3048
+ const { class: cls, listener } = dispatched;
3049
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3050
+ try {
3051
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
3052
+ } catch (error) {
3053
+ await handleRetriableFailure(error);
2741
3054
  return;
2742
3055
  }
2743
3056
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
2744
3057
  logger.info("Activity {activityId} has been processed.", {
2745
- activityId: activity.id?.href,
3058
+ activityId: activity?.id?.href,
2746
3059
  activity: message.activity,
2747
3060
  recipient: message.identifier
2748
3061
  });
@@ -3088,16 +3401,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3088
3401
  onNotFound
3089
3402
  });
3090
3403
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
3091
- case "sharedInbox":
3404
+ case "sharedInbox": {
3092
3405
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3093
3406
  const identity = await this.sharedInboxKeyDispatcher(context);
3094
3407
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3095
3408
  }
3096
3409
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3410
+ const inboxContextFactory = context.toInboxContext.bind(context);
3411
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3097
3412
  return await handleInbox(request, {
3098
3413
  recipient: route.values.identifier ?? null,
3099
3414
  context,
3100
- inboxContextFactory: context.toInboxContext.bind(context),
3415
+ inboxContextFactory,
3101
3416
  kv: this.kv,
3102
3417
  kvPrefixes: this.kvPrefixes,
3103
3418
  queue: this.inboxQueue,
@@ -3110,6 +3425,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3110
3425
  tracerProvider: this.tracerProvider,
3111
3426
  idempotencyStrategy: this.idempotencyStrategy
3112
3427
  });
3428
+ }
3113
3429
  case "following": return await handleCollection(request, {
3114
3430
  name: "following",
3115
3431
  identifier: route.values.identifier,
@@ -3785,6 +4101,7 @@ var ContextImpl = class ContextImpl {
3785
4101
  const routeResult = await routeActivity({
3786
4102
  context: this,
3787
4103
  json,
4104
+ ldSignatureVerified: false,
3788
4105
  activity,
3789
4106
  recipient,
3790
4107
  inboxListeners: this.federation.inboxListeners,
@@ -3883,6 +4200,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
3883
4200
  };
3884
4201
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
3885
4202
  recipient;
4203
+ /**
4204
+ * The original received activity payload.
4205
+ *
4206
+ * Fedify may normalize a Linked Data Signature payload internally for safe
4207
+ * parsing, but forwarding must keep the sender's payload unchanged so
4208
+ * third-party signatures/proofs remain intact.
4209
+ * @internal
4210
+ */
3886
4211
  activity;
3887
4212
  activityId;
3888
4213
  activityType;
@@ -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-Bua1eJKj.mjs";
4
+ import { n as FederationImpl } from "./middleware-qU3blEV5.mjs";
5
5
  export { FederationImpl };