@fedify/fedify 2.5.0-dev.2370 → 2.5.0-dev.2420

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 (93) hide show
  1. package/dist/{builder-DzGfERAV.mjs → builder-Cwc3Eu59.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/{compound-proof-hnZ7MNwN.mjs → compound-proof-Dtwrhs1I.mjs} +2 -2
  6. package/dist/{context-C2-UDo2c.d.ts → context-2wLkQrjb.d.ts} +11 -0
  7. package/dist/{context-BjD_CTxh.d.cts → context-DPocjp1l.d.cts} +11 -0
  8. package/dist/{deno-CkkDV5gw.mjs → deno-D4J5fszd.mjs} +1 -1
  9. package/dist/{docloader-D6eQ0vA8.mjs → docloader-ChK1DQkj.mjs} +4 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  13. package/dist/federation/handler.test.mjs +258 -6
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox-report.test.mjs +30 -5
  17. package/dist/federation/inbox.test.mjs +43 -0
  18. package/dist/federation/metrics.test.mjs +1 -1
  19. package/dist/federation/middleware.test.mjs +172 -10
  20. package/dist/federation/mod.cjs +1 -1
  21. package/dist/federation/mod.d.cts +2 -2
  22. package/dist/federation/mod.d.ts +2 -2
  23. package/dist/federation/mod.js +1 -1
  24. package/dist/federation/outgoing-proof.test.mjs +4 -5
  25. package/dist/federation/portable-actor.test.mjs +3 -3
  26. package/dist/federation/portable-chain.test.mjs +3 -3
  27. package/dist/federation/portable-collection.test.mjs +4 -4
  28. package/dist/federation/portable-delivery.test.mjs +2 -2
  29. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  30. package/dist/federation/portable-inbox.test.mjs +4 -4
  31. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  32. package/dist/federation/portable-route-activity.test.mjs +2 -2
  33. package/dist/federation/portable.test.mjs +3 -3
  34. package/dist/federation/send.test.mjs +3 -3
  35. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  36. package/dist/federation/tasks/tasks.test.mjs +2 -2
  37. package/dist/federation/temporal.test.mjs +1 -1
  38. package/dist/federation/webfinger.test.mjs +1 -1
  39. package/dist/{federation-BkIRrGXG.cjs → federation-DxQI9dSY.cjs} +125 -20
  40. package/dist/{http-DxljNiCC.mjs → http--kHQomKT.mjs} +4 -4
  41. package/dist/{http-C2gfnrKF.js → http-C8rIQ1pK.js} +110 -12
  42. package/dist/{http-CjpGhvxc.cjs → http-SBTDzPkh.cjs} +109 -11
  43. package/dist/inbox-BGD7K-Ra.mjs +188 -0
  44. package/dist/{kv-cache-DDwF4zee.mjs → kv-cache-B8LMKVe8.mjs} +1 -1
  45. package/dist/{kv-cache-nsVJF31j.js → kv-cache-DSUypZWS.js} +3 -1
  46. package/dist/{kv-cache-BVtotkZw.cjs → kv-cache-Dp_dDR1f.cjs} +3 -1
  47. package/dist/{key-8sZWp4lR.mjs → ld-BeBdBDAY.mjs} +760 -16
  48. package/dist/{metrics-DiQRjv6y.mjs → metrics-Dtb81JxK.mjs} +1 -1
  49. package/dist/{middleware-C_EdDn9c.js → middleware-DNjv5rBL.js} +125 -20
  50. package/dist/{middleware-BBnHLNpJ.mjs → middleware-DhI-75xh.mjs} +1 -1
  51. package/dist/{middleware-ClqRVObS.mjs → middleware-rQqAHzIo.mjs} +107 -180
  52. package/dist/{mod-BBPd4M_0.d.cts → mod-DKGoPoQC.d.cts} +1 -1
  53. package/dist/{mod-CUYBGKof.d.ts → mod-Dm-7RdhR.d.ts} +1 -1
  54. package/dist/mod.cjs +4 -4
  55. package/dist/mod.d.cts +2 -2
  56. package/dist/mod.d.ts +2 -2
  57. package/dist/mod.js +4 -4
  58. package/dist/nodeinfo/handler.test.mjs +1 -1
  59. package/dist/{owner-IiJfae6n.mjs → owner-u_-HQrgE.mjs} +3 -3
  60. package/dist/{portable-collection-BeS_vx1G.cjs → portable-collection-B-x4XwxP.cjs} +1 -1
  61. package/dist/{portable-collection-CUDbGQ5_.mjs → portable-collection-BKkTJQTd.mjs} +2 -2
  62. package/dist/{portable-collection-BtyE_2Rv.js → portable-collection-DReoGwZQ.js} +1 -1
  63. package/dist/{portable-key-ENr3Sc97.mjs → portable-key-Bkm6sXJB.mjs} +1 -1
  64. package/dist/{proof-CGtiU4Lz.mjs → proof-DOV4ZiAo.mjs} +3 -4
  65. package/dist/{send-CRYlnzg6.mjs → send-C_Fffu0I.mjs} +3 -3
  66. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  67. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  68. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  69. package/dist/sig/compound-proof.test.mjs +2 -2
  70. package/dist/sig/http.test.mjs +2 -2
  71. package/dist/sig/key.test.mjs +208 -3
  72. package/dist/sig/ld.test.mjs +1 -2
  73. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  74. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  75. package/dist/sig/mod.cjs +2 -2
  76. package/dist/sig/mod.js +2 -2
  77. package/dist/sig/owner.test.mjs +1 -1
  78. package/dist/sig/portable-collection.test.mjs +1 -1
  79. package/dist/sig/portable-interop.test.mjs +3 -3
  80. package/dist/sig/portable-key.test.mjs +5 -6
  81. package/dist/sig/proof.test.mjs +3 -3
  82. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  83. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  84. package/dist/sig/verification.test.mjs +3 -3
  85. package/dist/{temporal-CDbdX_g_.mjs → temporal-BRgOZRRr.mjs} +1 -1
  86. package/dist/testing/kv.test.mjs +1 -1
  87. package/dist/testing/mod.d.mts +11 -0
  88. package/dist/utils/docloader.test.mjs +112 -37
  89. package/dist/utils/kv-cache.test.mjs +1 -1
  90. package/dist/utils/mod.cjs +1 -1
  91. package/dist/utils/mod.js +1 -1
  92. package/package.json +7 -7
  93. package/dist/ld-DXaIENwZ.mjs +0 -658
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-CjpGhvxc.cjs");
4
+ const require_http = require("./http-SBTDzPkh.cjs");
5
5
  const require_types = require("./types-poIao40f.cjs");
6
- const require_portable_collection = require("./portable-collection-BeS_vx1G.cjs");
7
- const require_kv_cache = require("./kv-cache-BVtotkZw.cjs");
6
+ const require_portable_collection = require("./portable-collection-B-x4XwxP.cjs");
7
+ const require_kv_cache = require("./kv-cache-Dp_dDR1f.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -2807,12 +2807,41 @@ async function isInAudience(object, actor, options = {}) {
2807
2807
  //#endregion
2808
2808
  //#region src/federation/inbox.ts
2809
2809
  const Temporal$10 = require_http.Temporal;
2810
+ /**
2811
+ * Checks whether an activity's ID and all of its actor IDs have the same
2812
+ * [FEP-fe34] origin. Activities without an ID have no origin to authenticate.
2813
+ *
2814
+ * Origins are compared by `getAuthenticationOrigin()`, so a portable activity
2815
+ * and its portable actors, including compatible identifiers on any gateway,
2816
+ * are compared by their DIDs. An ID that has no supported origin, such as an
2817
+ * opaque URN, never matches.
2818
+ *
2819
+ * The caller is responsible for rejecting a missing primary actor ID before
2820
+ * relying on this check.
2821
+ *
2822
+ * [FEP-fe34]: https://w3id.org/fep/fe34
2823
+ * @internal
2824
+ */
2825
+ function actorsHaveSameOrigin(activity) {
2826
+ if (activity.id == null) return true;
2827
+ const origin = require_http.getAuthenticationOrigin(activity.id);
2828
+ return origin != null && activity.actorIds.every((actor) => require_http.getAuthenticationOrigin(actor) === origin);
2829
+ }
2810
2830
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
2811
2831
  const logger = (0, _logtape_logtape.getLogger)([
2812
2832
  "fedify",
2813
2833
  "federation",
2814
2834
  "inbox"
2815
2835
  ]);
2836
+ if (activity.actorId == null) {
2837
+ logger.error("Missing actor.", { activity: json });
2838
+ span.setStatus({
2839
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2840
+ message: "Missing actor."
2841
+ });
2842
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
2843
+ return "missingActor";
2844
+ }
2816
2845
  let cacheKey = null;
2817
2846
  if (activity.id != null) {
2818
2847
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href);
@@ -2848,15 +2877,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2848
2877
  return "alreadyProcessed";
2849
2878
  }
2850
2879
  }
2851
- if (activity.actorId == null) {
2852
- logger.error("Missing actor.", { activity: json });
2853
- span.setStatus({
2854
- code: _opentelemetry_api.SpanStatusCode.ERROR,
2855
- message: "Missing actor."
2856
- });
2857
- require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
2858
- return "missingActor";
2859
- }
2860
2880
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
2861
2881
  if (queue != null) {
2862
2882
  const carrier = {};
@@ -4778,13 +4798,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
4778
4798
  * @param parameters The parameters for handling the collection.
4779
4799
  * @returns A promise that resolves to an HTTP response.
4780
4800
  */
4781
- async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4782
- const spanName = name$1.trim().replace(/\s+/g, "_");
4801
+ async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4802
+ const spanName = name$2.trim().replace(/\s+/g, "_");
4783
4803
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
4784
4804
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
4785
4805
  const cursor = new URL(request.url).searchParams.get("cursor");
4786
4806
  const metricBase = {
4787
- kind: getCollectionMetricKind(name$1),
4807
+ kind: getCollectionMetricKind(name$2),
4788
4808
  page: cursor != null,
4789
4809
  dispatcher: "built_in"
4790
4810
  };
@@ -4830,7 +4850,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
4830
4850
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4831
4851
  return await onNotFound(request);
4832
4852
  }
4833
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
4853
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
4834
4854
  itemCount = items.length;
4835
4855
  span.setAttribute("fedify.collection.items", itemCount);
4836
4856
  return items;
@@ -4872,7 +4892,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
4872
4892
  } else {
4873
4893
  const uri = new URL(portable == null ? baseUri : pageBase);
4874
4894
  uri.searchParams.set("cursor", cursor);
4875
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
4895
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
4876
4896
  kind: _opentelemetry_api.SpanKind.SERVER,
4877
4897
  attributes: {
4878
4898
  "activitypub.collection.id": uri.href,
@@ -4888,7 +4908,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
4888
4908
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4889
4909
  return await onNotFound(request);
4890
4910
  }
4891
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
4911
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
4892
4912
  itemCount = items.length;
4893
4913
  span.setAttribute("fedify.collection.items", itemCount);
4894
4914
  return {
@@ -6041,11 +6061,53 @@ async function handleInboxInternal(request, parameters, span) {
6041
6061
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
6042
6062
  }
6043
6063
  }
6064
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
6065
+ observation.authentication = {
6066
+ status: "rejected",
6067
+ reason: {
6068
+ type: "activityOriginMismatch",
6069
+ activityId: new URL(activity.id.href),
6070
+ actorIds: activity.actorIds.map((id) => new URL(id.href))
6071
+ }
6072
+ };
6073
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
6074
+ activity: json,
6075
+ recipient,
6076
+ activityId: activity.id.href,
6077
+ actorIds: activity.actorIds.map((actor) => actor.href)
6078
+ });
6079
+ span.setStatus({
6080
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
6081
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
6082
+ });
6083
+ return new Response("The activity and its actors do not have the same origin.", {
6084
+ status: 401,
6085
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6086
+ });
6087
+ }
6044
6088
  observation.authentication = skipSignatureVerification ? { status: "skipped" } : {
6045
6089
  status: "verified",
6046
6090
  attempts: observation.verification.attempts.filter((attempt) => attempt.status === "verified")
6047
6091
  };
6048
6092
  observation.stage = "dispatch";
6093
+ if (activity.actorId == null) {
6094
+ observation.result = {
6095
+ disposition: "rejected",
6096
+ reason: "missingActor"
6097
+ };
6098
+ logger.error("Missing actor.", {
6099
+ activity: json,
6100
+ recipient
6101
+ });
6102
+ span.setStatus({
6103
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
6104
+ message: "Missing actor."
6105
+ });
6106
+ return new Response("Missing actor.", {
6107
+ status: 400,
6108
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6109
+ });
6110
+ }
6049
6111
  const routeResult = await routeActivity({
6050
6112
  context: ctx,
6051
6113
  json,
@@ -6266,8 +6328,8 @@ var CustomCollectionHandler = class {
6266
6328
  * @param CollectionPage The CollectionPage constructor.
6267
6329
  * @param filterPredicate Optional filter predicate for items.
6268
6330
  */
6269
- constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
6270
- this.name = name$2;
6331
+ constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
6332
+ this.name = name$1;
6271
6333
  this.values = values;
6272
6334
  this.context = context;
6273
6335
  this.callbacks = callbacks;
@@ -9393,6 +9455,35 @@ var FederationImpl = class extends FederationBuilderImpl {
9393
9455
  span.setAttribute("activitypub.activity.id", activity.id.href);
9394
9456
  listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
9395
9457
  }
9458
+ if (activity.actorId == null) {
9459
+ logger.error("Missing actor in queued incoming activity.", {
9460
+ activityId: activity.id?.href,
9461
+ activity: message.activity,
9462
+ recipient: message.identifier
9463
+ });
9464
+ listenerSpan.setStatus({
9465
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
9466
+ message: "Missing actor."
9467
+ });
9468
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
9469
+ listenerSpan.end();
9470
+ return;
9471
+ }
9472
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
9473
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
9474
+ activityId: activity.id.href,
9475
+ actorIds: activity.actorIds.map((actor) => actor.href),
9476
+ activity: message.activity,
9477
+ recipient: message.identifier
9478
+ });
9479
+ listenerSpan.setStatus({
9480
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
9481
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
9482
+ });
9483
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
9484
+ listenerSpan.end();
9485
+ return;
9486
+ }
9396
9487
  cacheKey = activity.id == null ? null : [
9397
9488
  ...this.kvPrefixes.activityIdempotence,
9398
9489
  context.origin,
@@ -11469,6 +11560,20 @@ var ContextImpl = class ContextImpl {
11469
11560
  recipient,
11470
11561
  activity: json
11471
11562
  });
11563
+ if (activity.actorId == null) {
11564
+ logger.debug("Activity is missing an actor; unable to verify.", {
11565
+ recipient,
11566
+ activity: json
11567
+ });
11568
+ return false;
11569
+ }
11570
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
11571
+ logger.debug("Activity has actors from different origins; unable to verify.", {
11572
+ recipient,
11573
+ activity: json
11574
+ });
11575
+ return false;
11576
+ }
11472
11577
  const routeResult = await routeActivity({
11473
11578
  context: this,
11474
11579
  json,
@@ -3,12 +3,12 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { m as parseKeyIdString } from "./portable-key-id-Ca30-hOa.mjs";
5
5
  import { a as verificationObservation, i as triedKey, n as observeCheck, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
6
- import { n as version, t as name } from "./deno-CkkDV5gw.mjs";
7
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DiQRjv6y.mjs";
6
+ import { n as version, t as name } from "./deno-D4J5fszd.mjs";
7
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Dtb81JxK.mjs";
8
8
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
9
9
  import { a as promoteKeyOwnerEvidence } from "./key-owner-evidence-APB6NpSk.mjs";
10
- import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-8sZWp4lR.mjs";
11
- import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-ENr3Sc97.mjs";
10
+ import { S as fetchKeyDetailed, T as validateCryptoKey, v as bypassKeyCacheReads } from "./ld-BeBdBDAY.mjs";
11
+ import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-Bkm6sXJB.mjs";
12
12
  import { getLogger } from "@logtape/logtape";
13
13
  import { CryptographicKey } from "@fedify/vocab";
14
14
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -3,7 +3,7 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import { getLogger } from "@logtape/logtape";
4
4
  import { Activity, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1, PUBLIC_COLLECTION, getTypeId, isActor } from "@fedify/vocab";
5
5
  import { SpanKind, SpanStatusCode, metrics, trace } from "@opentelemetry/api";
6
- import { FetchError, canonicalizePortableUri, encodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDidKeyVerificationMethod, parseGatewayUrl, parseIri, preloadedContexts, toCompatibleEf61Id } from "@fedify/vocab-runtime";
6
+ import { FetchError, UrlError, canonicalizePortableUri, encodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDidKeyVerificationMethod, parseGatewayUrl, parseIri, preloadedContexts, toCompatibleEf61Id } from "@fedify/vocab-runtime";
7
7
  import { getPortableGatewayCandidates, isCompatibleEf61Iri } from "@fedify/vocab-runtime/internal/portable-dereference";
8
8
  import { encodeHex } from "byte-encodings/hex";
9
9
  import jsonld from "@fedify/vocab-runtime/jsonld";
@@ -15,7 +15,7 @@ import serialize from "json-canon";
15
15
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
16
16
  //#region deno.json
17
17
  var name = "@fedify/fedify";
18
- var version = "2.5.0-dev.2370+e0cfd033";
18
+ var version = "2.5.0-dev.2420+4db1db92";
19
19
  //#endregion
20
20
  //#region src/sig/portable-key-id.ts
21
21
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -1430,6 +1430,65 @@ async function parseKeyOwnerEvidence(evidence, options = {}) {
1430
1430
  }
1431
1431
  //#endregion
1432
1432
  //#region src/sig/key.ts
1433
+ /** Classifies decoding failures without hiding unexpected loader errors. */
1434
+ function classifyKeyJsonLdError(error, seen = /* @__PURE__ */ new Set()) {
1435
+ if (!(error instanceof Error) || !error.name.startsWith("jsonld.")) return null;
1436
+ if (seen.has(error)) throw error;
1437
+ seen.add(error);
1438
+ const details = error.details;
1439
+ if (typeof details?.code !== "string") return null;
1440
+ if (details.code !== "loading remote context failed" || !("cause" in details) && !("cause" in error)) return "invalid";
1441
+ const cause = "cause" in details ? details.cause : error.cause;
1442
+ const nestedFailure = classifyKeyJsonLdError(cause, seen);
1443
+ if (nestedFailure != null) return nestedFailure;
1444
+ if (cause instanceof SyntaxError) return "invalid";
1445
+ if (cause instanceof FetchError || cause instanceof UrlError) return "fetch";
1446
+ if (cause instanceof Error && [
1447
+ "AbortError",
1448
+ "TimeoutError",
1449
+ "NetworkError"
1450
+ ].includes(cause.name)) return "fetch";
1451
+ if (cause instanceof TypeError) {
1452
+ const urlCode = cause.code;
1453
+ if (cause.name === "InvalidContextReferenceError") return "invalid";
1454
+ if (urlCode === "ERR_INVALID_URL" || cause.message === "Invalid URL string." || /^Invalid URL(?::|$)/.test(cause.message) || / cannot be parsed as a URL\.?$/.test(cause.message)) return typeof details.url === "string" && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(details.url) && !URL.canParse(details.url) ? "invalid" : "fetch";
1455
+ const networkCause = cause.cause;
1456
+ const code = cause.code ?? networkCause?.code;
1457
+ if (typeof code === "string" && [
1458
+ "ECONNREFUSED",
1459
+ "ECONNRESET",
1460
+ "ECONNABORTED",
1461
+ "ETIMEDOUT",
1462
+ "EHOSTUNREACH",
1463
+ "ENETUNREACH",
1464
+ "ENOTFOUND",
1465
+ "EAI_AGAIN",
1466
+ "UND_ERR_CONNECT_TIMEOUT",
1467
+ "UND_ERR_HEADERS_TIMEOUT",
1468
+ "UND_ERR_BODY_TIMEOUT",
1469
+ "UND_ERR_SOCKET"
1470
+ ].includes(code) || /^(fetch failed|Failed to fetch|error sending request|error reading a body from connection|Unable to connect|ConnectionRefused|Network connection lost|The socket connection was closed unexpectedly)/.test(cause.message)) return "fetch";
1471
+ }
1472
+ throw cause;
1473
+ }
1474
+ /** Carries a context transport failure through the key decoding boundary. */
1475
+ var KeyContextFetchError = class extends Error {
1476
+ error;
1477
+ constructor(error) {
1478
+ super("Failed to fetch a key's JSON-LD context.", { cause: error });
1479
+ this.error = error;
1480
+ }
1481
+ };
1482
+ function contextFetchError(error) {
1483
+ const seen = /* @__PURE__ */ new Set();
1484
+ while (error instanceof Error && !seen.has(error)) {
1485
+ seen.add(error);
1486
+ const details = error.details;
1487
+ if (!error.name.startsWith("jsonld.") || details?.code !== "loading remote context failed") break;
1488
+ error = "cause" in details ? details.cause : error.cause;
1489
+ }
1490
+ return new KeyContextFetchError(error);
1491
+ }
1433
1492
  /**
1434
1493
  * Checks if the given key is valid and supported. No-op if the key is valid,
1435
1494
  * otherwise throws an error.
@@ -1524,6 +1583,9 @@ async function importJwk(jwk, type) {
1524
1583
  * @internal
1525
1584
  */
1526
1585
  async function fetchActorDocument(actorId, options = {}) {
1586
+ return await fetchActorDocumentInternal(actorId, options, false);
1587
+ }
1588
+ async function fetchActorDocumentInternal(actorId, options, propagateContextFetchErrors) {
1527
1589
  const logger = getLogger([
1528
1590
  "fedify",
1529
1591
  "sig",
@@ -1558,9 +1620,12 @@ async function fetchActorDocument(actorId, options = {}) {
1558
1620
  baseUrl: documentUrl
1559
1621
  });
1560
1622
  } catch (error) {
1561
- if (!(error instanceof TypeError)) throw error;
1562
- logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
1623
+ const failure = classifyKeyJsonLdError(error);
1624
+ if (failure === "fetch" && propagateContextFetchErrors) throw contextFetchError(error);
1625
+ if (failure == null && !(error instanceof TypeError)) throw error;
1626
+ logger.debug("Failed to decode the actor document at {documentUrl}: {error}", {
1563
1627
  documentUrl: documentUrl.href,
1628
+ failure: failure ?? "invalid",
1564
1629
  error
1565
1630
  });
1566
1631
  return null;
@@ -1606,6 +1671,9 @@ async function fetchActorDocument(actorId, options = {}) {
1606
1671
  * @internal
1607
1672
  */
1608
1673
  async function verifyKeyOwnership(key, options = {}) {
1674
+ return await verifyKeyOwnershipInternal(key, options, false);
1675
+ }
1676
+ async function verifyKeyOwnershipInternal(key, options, propagateContextFetchErrors) {
1609
1677
  const logger = getLogger([
1610
1678
  "fedify",
1611
1679
  "sig",
@@ -1615,7 +1683,7 @@ async function verifyKeyOwnership(key, options = {}) {
1615
1683
  if (keyId == null) return null;
1616
1684
  const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
1617
1685
  if (claimedOwnerId == null) return null;
1618
- const owner = await fetchActorDocument(claimedOwnerId, options);
1686
+ const owner = await fetchActorDocumentInternal(claimedOwnerId, options, propagateContextFetchErrors);
1619
1687
  if (owner == null) {
1620
1688
  logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
1621
1689
  keyId: keyId.href,
@@ -1941,6 +2009,21 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
1941
2009
  tracerProvider
1942
2010
  });
1943
2011
  } catch (e) {
2012
+ const failure = classifyKeyJsonLdError(e);
2013
+ if (failure === "fetch") throw contextFetchError(e);
2014
+ if (failure != null) {
2015
+ logger.debug("Failed to decode key {keyId}: {error}", {
2016
+ keyId,
2017
+ failure,
2018
+ error: e
2019
+ });
2020
+ await keyCache?.set(cacheKey, null);
2021
+ await clearFetchErrorMetadata(cacheKey, keyCache);
2022
+ return {
2023
+ key: null,
2024
+ cached: false
2025
+ };
2026
+ }
1944
2027
  if (!(e instanceof TypeError)) throw e;
1945
2028
  try {
1946
2029
  object = await cls.fromJsonLd(document, {
@@ -1949,8 +2032,14 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
1949
2032
  tracerProvider
1950
2033
  });
1951
2034
  } catch (e) {
1952
- if (e instanceof TypeError) {
1953
- logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
2035
+ const failure = classifyKeyJsonLdError(e);
2036
+ if (failure === "fetch") throw contextFetchError(e);
2037
+ if (failure != null || e instanceof TypeError) {
2038
+ logger.debug("Failed to decode key {keyId}: {error}", {
2039
+ keyId,
2040
+ failure: failure ?? "invalid",
2041
+ error: e
2042
+ });
1954
2043
  await keyCache?.set(cacheKey, null);
1955
2044
  await clearFetchErrorMetadata(cacheKey, keyCache);
1956
2045
  return {
@@ -2089,16 +2178,17 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
2089
2178
  }
2090
2179
  if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
2091
2180
  else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
2092
- if (await verifyKeyOwnership(key, {
2181
+ if (await verifyKeyOwnershipInternal(key, {
2093
2182
  documentLoader,
2094
2183
  contextLoader,
2095
2184
  tracerProvider
2096
- }) == null) {
2185
+ }, true) == null) {
2097
2186
  logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
2098
2187
  keyId,
2099
2188
  claimedOwnerId: claimedOwnerId.href
2100
2189
  });
2101
2190
  await keyCache?.set(cacheKey, null);
2191
+ await clearFetchErrorMetadata(cacheKey, keyCache);
2102
2192
  return {
2103
2193
  key: null,
2104
2194
  cached: false
@@ -2175,9 +2265,17 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
2175
2265
  outcome = classified;
2176
2266
  return errored;
2177
2267
  }
2178
- const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
2179
- outcome = { result: resolved.key != null ? "fetched" : "invalid" };
2180
- return resolved;
2268
+ try {
2269
+ const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
2270
+ outcome = { result: resolved.key != null ? "fetched" : "invalid" };
2271
+ return resolved;
2272
+ } catch (error) {
2273
+ if (!(error instanceof KeyContextFetchError)) throw error;
2274
+ const classified = classifyFetchError(error.error);
2275
+ const errored = await onFetchError(error.error, cacheKey, keyId, keyCache, logger);
2276
+ outcome = classified;
2277
+ return errored;
2278
+ }
2181
2279
  } finally {
2182
2280
  recordKeyLookup(options.meterProvider, {
2183
2281
  durationMs: getDurationMs(start),
@@ -17,7 +17,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
17
17
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
18
18
  //#region deno.json
19
19
  var name = "@fedify/fedify";
20
- var version = "2.5.0-dev.2370+e0cfd033";
20
+ var version = "2.5.0-dev.2420+4db1db92";
21
21
  //#endregion
22
22
  //#region src/sig/portable-key-id.ts
23
23
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -4998,6 +4998,65 @@ async function parseKeyOwnerEvidence(evidence, options = {}) {
4998
4998
  //#endregion
4999
4999
  //#region src/sig/key.ts
5000
5000
  const Temporal$2 = Temporal$4;
5001
+ /** Classifies decoding failures without hiding unexpected loader errors. */
5002
+ function classifyKeyJsonLdError(error, seen = /* @__PURE__ */ new Set()) {
5003
+ if (!(error instanceof Error) || !error.name.startsWith("jsonld.")) return null;
5004
+ if (seen.has(error)) throw error;
5005
+ seen.add(error);
5006
+ const details = error.details;
5007
+ if (typeof details?.code !== "string") return null;
5008
+ if (details.code !== "loading remote context failed" || !("cause" in details) && !("cause" in error)) return "invalid";
5009
+ const cause = "cause" in details ? details.cause : error.cause;
5010
+ const nestedFailure = classifyKeyJsonLdError(cause, seen);
5011
+ if (nestedFailure != null) return nestedFailure;
5012
+ if (cause instanceof SyntaxError) return "invalid";
5013
+ if (cause instanceof _fedify_vocab_runtime.FetchError || cause instanceof _fedify_vocab_runtime.UrlError) return "fetch";
5014
+ if (cause instanceof Error && [
5015
+ "AbortError",
5016
+ "TimeoutError",
5017
+ "NetworkError"
5018
+ ].includes(cause.name)) return "fetch";
5019
+ if (cause instanceof TypeError) {
5020
+ const urlCode = cause.code;
5021
+ if (cause.name === "InvalidContextReferenceError") return "invalid";
5022
+ if (urlCode === "ERR_INVALID_URL" || cause.message === "Invalid URL string." || /^Invalid URL(?::|$)/.test(cause.message) || / cannot be parsed as a URL\.?$/.test(cause.message)) return typeof details.url === "string" && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(details.url) && !URL.canParse(details.url) ? "invalid" : "fetch";
5023
+ const networkCause = cause.cause;
5024
+ const code = cause.code ?? networkCause?.code;
5025
+ if (typeof code === "string" && [
5026
+ "ECONNREFUSED",
5027
+ "ECONNRESET",
5028
+ "ECONNABORTED",
5029
+ "ETIMEDOUT",
5030
+ "EHOSTUNREACH",
5031
+ "ENETUNREACH",
5032
+ "ENOTFOUND",
5033
+ "EAI_AGAIN",
5034
+ "UND_ERR_CONNECT_TIMEOUT",
5035
+ "UND_ERR_HEADERS_TIMEOUT",
5036
+ "UND_ERR_BODY_TIMEOUT",
5037
+ "UND_ERR_SOCKET"
5038
+ ].includes(code) || /^(fetch failed|Failed to fetch|error sending request|error reading a body from connection|Unable to connect|ConnectionRefused|Network connection lost|The socket connection was closed unexpectedly)/.test(cause.message)) return "fetch";
5039
+ }
5040
+ throw cause;
5041
+ }
5042
+ /** Carries a context transport failure through the key decoding boundary. */
5043
+ var KeyContextFetchError = class extends Error {
5044
+ error;
5045
+ constructor(error) {
5046
+ super("Failed to fetch a key's JSON-LD context.", { cause: error });
5047
+ this.error = error;
5048
+ }
5049
+ };
5050
+ function contextFetchError(error) {
5051
+ const seen = /* @__PURE__ */ new Set();
5052
+ while (error instanceof Error && !seen.has(error)) {
5053
+ seen.add(error);
5054
+ const details = error.details;
5055
+ if (!error.name.startsWith("jsonld.") || details?.code !== "loading remote context failed") break;
5056
+ error = "cause" in details ? details.cause : error.cause;
5057
+ }
5058
+ return new KeyContextFetchError(error);
5059
+ }
5001
5060
  /**
5002
5061
  * Checks if the given key is valid and supported. No-op if the key is valid,
5003
5062
  * otherwise throws an error.
@@ -5092,6 +5151,9 @@ async function importJwk(jwk, type) {
5092
5151
  * @internal
5093
5152
  */
5094
5153
  async function fetchActorDocument(actorId, options = {}) {
5154
+ return await fetchActorDocumentInternal(actorId, options, false);
5155
+ }
5156
+ async function fetchActorDocumentInternal(actorId, options, propagateContextFetchErrors) {
5095
5157
  const logger = (0, _logtape_logtape.getLogger)([
5096
5158
  "fedify",
5097
5159
  "sig",
@@ -5126,9 +5188,12 @@ async function fetchActorDocument(actorId, options = {}) {
5126
5188
  baseUrl: documentUrl
5127
5189
  });
5128
5190
  } catch (error) {
5129
- if (!(error instanceof TypeError)) throw error;
5130
- logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
5191
+ const failure = classifyKeyJsonLdError(error);
5192
+ if (failure === "fetch" && propagateContextFetchErrors) throw contextFetchError(error);
5193
+ if (failure == null && !(error instanceof TypeError)) throw error;
5194
+ logger.debug("Failed to decode the actor document at {documentUrl}: {error}", {
5131
5195
  documentUrl: documentUrl.href,
5196
+ failure: failure ?? "invalid",
5132
5197
  error
5133
5198
  });
5134
5199
  return null;
@@ -5174,6 +5239,9 @@ async function fetchActorDocument(actorId, options = {}) {
5174
5239
  * @internal
5175
5240
  */
5176
5241
  async function verifyKeyOwnership(key, options = {}) {
5242
+ return await verifyKeyOwnershipInternal(key, options, false);
5243
+ }
5244
+ async function verifyKeyOwnershipInternal(key, options, propagateContextFetchErrors) {
5177
5245
  const logger = (0, _logtape_logtape.getLogger)([
5178
5246
  "fedify",
5179
5247
  "sig",
@@ -5183,7 +5251,7 @@ async function verifyKeyOwnership(key, options = {}) {
5183
5251
  if (keyId == null) return null;
5184
5252
  const claimedOwnerId = key instanceof _fedify_vocab.CryptographicKey ? key.ownerId : key.controllerId;
5185
5253
  if (claimedOwnerId == null) return null;
5186
- const owner = await fetchActorDocument(claimedOwnerId, options);
5254
+ const owner = await fetchActorDocumentInternal(claimedOwnerId, options, propagateContextFetchErrors);
5187
5255
  if (owner == null) {
5188
5256
  logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
5189
5257
  keyId: keyId.href,
@@ -5509,6 +5577,21 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
5509
5577
  tracerProvider
5510
5578
  });
5511
5579
  } catch (e) {
5580
+ const failure = classifyKeyJsonLdError(e);
5581
+ if (failure === "fetch") throw contextFetchError(e);
5582
+ if (failure != null) {
5583
+ logger.debug("Failed to decode key {keyId}: {error}", {
5584
+ keyId,
5585
+ failure,
5586
+ error: e
5587
+ });
5588
+ await keyCache?.set(cacheKey, null);
5589
+ await clearFetchErrorMetadata(cacheKey, keyCache);
5590
+ return {
5591
+ key: null,
5592
+ cached: false
5593
+ };
5594
+ }
5512
5595
  if (!(e instanceof TypeError)) throw e;
5513
5596
  try {
5514
5597
  object = await cls.fromJsonLd(document, {
@@ -5517,8 +5600,14 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
5517
5600
  tracerProvider
5518
5601
  });
5519
5602
  } catch (e) {
5520
- if (e instanceof TypeError) {
5521
- logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
5603
+ const failure = classifyKeyJsonLdError(e);
5604
+ if (failure === "fetch") throw contextFetchError(e);
5605
+ if (failure != null || e instanceof TypeError) {
5606
+ logger.debug("Failed to decode key {keyId}: {error}", {
5607
+ keyId,
5608
+ failure: failure ?? "invalid",
5609
+ error: e
5610
+ });
5522
5611
  await keyCache?.set(cacheKey, null);
5523
5612
  await clearFetchErrorMetadata(cacheKey, keyCache);
5524
5613
  return {
@@ -5657,16 +5746,17 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
5657
5746
  }
5658
5747
  if (ownerDocument != null && claimedOwnerId == null) key = key instanceof _fedify_vocab.CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
5659
5748
  else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
5660
- if (await verifyKeyOwnership(key, {
5749
+ if (await verifyKeyOwnershipInternal(key, {
5661
5750
  documentLoader,
5662
5751
  contextLoader,
5663
5752
  tracerProvider
5664
- }) == null) {
5753
+ }, true) == null) {
5665
5754
  logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
5666
5755
  keyId,
5667
5756
  claimedOwnerId: claimedOwnerId.href
5668
5757
  });
5669
5758
  await keyCache?.set(cacheKey, null);
5759
+ await clearFetchErrorMetadata(cacheKey, keyCache);
5670
5760
  return {
5671
5761
  key: null,
5672
5762
  cached: false
@@ -5743,9 +5833,17 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
5743
5833
  outcome = classified;
5744
5834
  return errored;
5745
5835
  }
5746
- const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
5747
- outcome = { result: resolved.key != null ? "fetched" : "invalid" };
5748
- return resolved;
5836
+ try {
5837
+ const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
5838
+ outcome = { result: resolved.key != null ? "fetched" : "invalid" };
5839
+ return resolved;
5840
+ } catch (error) {
5841
+ if (!(error instanceof KeyContextFetchError)) throw error;
5842
+ const classified = classifyFetchError(error.error);
5843
+ const errored = await onFetchError(error.error, cacheKey, keyId, keyCache, logger);
5844
+ outcome = classified;
5845
+ return errored;
5846
+ }
5749
5847
  } finally {
5750
5848
  recordKeyLookup(options.meterProvider, {
5751
5849
  durationMs: getDurationMs(start),