@fedify/fedify 2.3.12-dev.2365 → 2.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/{assert_rejects-DN60FHPX.mjs → assert_rejects-D1HYAik3.mjs} +2 -20
  2. package/dist/{assert_throws-BOkhLGYc.mjs → assert_throws-BbSMEFKo.mjs} +19 -1
  3. package/dist/{builder-BOLaWLNy.mjs → builder-BagNDcQw.mjs} +2 -2
  4. package/dist/compat/transformers.test.mjs +2 -2
  5. package/dist/{deno-DSS6EkPl.mjs → deno-XpcjbHwj.mjs} +1 -1
  6. package/dist/{docloader-Ds5k4A6d.mjs → docloader-QDpF5t9M.mjs} +4 -2
  7. package/dist/federation/builder.test.mjs +3 -3
  8. package/dist/federation/circuit-breaker.test.mjs +3 -3
  9. package/dist/federation/collection.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +262 -9
  11. package/dist/federation/idempotency.test.mjs +3 -3
  12. package/dist/federation/inbox.test.mjs +25 -1
  13. package/dist/federation/keycache.test.mjs +1 -1
  14. package/dist/federation/kv.test.mjs +1 -1
  15. package/dist/federation/metrics.test.mjs +3 -3
  16. package/dist/federation/middleware.test.mjs +177 -15
  17. package/dist/federation/mod.cjs +1 -1
  18. package/dist/federation/mod.js +1 -1
  19. package/dist/federation/mq.test.mjs +4 -3
  20. package/dist/federation/negotiation.test.mjs +4 -4
  21. package/dist/federation/retry.test.mjs +1 -1
  22. package/dist/federation/router.test.mjs +3 -4
  23. package/dist/federation/send.test.mjs +7 -6
  24. package/dist/federation/temporal.test.mjs +4 -4
  25. package/dist/federation/webfinger.test.mjs +2 -2
  26. package/dist/{http-D9h6mQdq.mjs → http-BRrpIDS1.mjs} +3 -3
  27. package/dist/{http-DdaLQHim.cjs → http-DwPnG87d.cjs} +109 -11
  28. package/dist/{http-LMsqGo8j.js → http-GHVGCeb5.js} +110 -12
  29. package/dist/inbox-BzOuCPLY.mjs +180 -0
  30. package/dist/{key-vnmAGZ3_.mjs → key-PdEu1lDF.mjs} +111 -13
  31. package/dist/{kv-cache-Dz7_SbVI.js → kv-cache-BmqdF048.js} +3 -1
  32. package/dist/{kv-cache-CQuscHxa.mjs → kv-cache-Clx3SwMp.mjs} +1 -1
  33. package/dist/{kv-cache-ClCz-B0L.cjs → kv-cache-poF6Tqnz.cjs} +3 -1
  34. package/dist/{ld-BRqKkOSf.mjs → ld-D6UlzNnG.mjs} +3 -3
  35. package/dist/{metrics-Z0UkrR_1.mjs → metrics-62qolVUY.mjs} +1 -1
  36. package/dist/{middleware-C_kPmXE6.mjs → middleware-C3Q3CoR8.mjs} +1 -1
  37. package/dist/{middleware-RTcKhytV.mjs → middleware-CfbFdEO-.mjs} +96 -194
  38. package/dist/{middleware-DanzltB1.cjs → middleware-DkrXqZO_.cjs} +106 -34
  39. package/dist/{middleware-B-CIN962.js → middleware-xw9fBZoB.js} +106 -34
  40. package/dist/mod.cjs +4 -4
  41. package/dist/mod.js +4 -4
  42. package/dist/nodeinfo/client.test.mjs +2 -2
  43. package/dist/nodeinfo/handler.test.mjs +2 -2
  44. package/dist/nodeinfo/types.test.mjs +2 -2
  45. package/dist/otel/exporter.test.mjs +1 -1
  46. package/dist/{owner-B0rmuoQB.mjs → owner-DWnLg-vk.mjs} +2 -2
  47. package/dist/{proof-Bau45ZUZ.js → proof-CUSzBztF.js} +1 -1
  48. package/dist/{proof-CKczQTG0.cjs → proof-DF6CU-SU.cjs} +1 -1
  49. package/dist/{proof-B7mAFVed.mjs → proof-mf6h7n4_.mjs} +3 -3
  50. package/dist/{send-BFukz_kE.mjs → send-LJUzNpVF.mjs} +3 -3
  51. package/dist/sig/http.test.mjs +6 -6
  52. package/dist/sig/key.test.mjs +213 -7
  53. package/dist/sig/ld.test.mjs +5 -5
  54. package/dist/sig/mod.cjs +2 -2
  55. package/dist/sig/mod.js +2 -2
  56. package/dist/sig/owner.test.mjs +4 -4
  57. package/dist/sig/proof.test.mjs +5 -4
  58. package/dist/{std__assert-BvGuO4xO.mjs → std__assert-BjJn2daJ.mjs} +2 -2
  59. package/dist/{temporal-n6j0MNF9.mjs → temporal-_3yIqdvy.mjs} +1 -1
  60. package/dist/utils/docloader.test.mjs +114 -39
  61. package/dist/utils/kv-cache.test.mjs +1 -1
  62. package/dist/utils/mod.cjs +1 -1
  63. package/dist/utils/mod.js +1 -1
  64. package/package.json +7 -7
  65. /package/dist/{assert-jSdZMYam.mjs → assert-CEO06XCw.mjs} +0 -0
  66. /package/dist/{circuit-breaker-test-utils-Dm5MM6l9.mjs → circuit-breaker-test-utils-CAdwjgMi.mjs} +0 -0
  67. /package/dist/{negotiation-CDW-_gUU.mjs → negotiation-DDstyBvc.mjs} +0 -0
  68. /package/dist/{retry-_VvV0h9f.mjs → retry-CXg_MBI-.mjs} +0 -0
@@ -3,23 +3,23 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-vrlUxr60.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
- import { n as assertExists } from "../std__assert-BvGuO4xO.mjs";
7
- import { r as assertFalse, t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
- import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
9
- import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
6
+ import { n as assertExists } from "../std__assert-BjJn2daJ.mjs";
7
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-BbSMEFKo.mjs";
8
+ import { t as assertRejects } from "../assert_rejects-D1HYAik3.mjs";
9
+ import { n as assertInstanceOf, t as assert } from "../assert-CEO06XCw.mjs";
10
10
  import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
11
11
  import { t as assertStrictEquals } from "../assert_strict_equals-XEgZAlrj.mjs";
12
- import { g as recordInboxActivity } from "../metrics-Z0UkrR_1.mjs";
13
- import { l as verifyRequest, s as signRequest } from "../http-D9h6mQdq.mjs";
12
+ import { g as recordInboxActivity } from "../metrics-62qolVUY.mjs";
13
+ import { l as verifyRequest, s as signRequest } from "../http-BRrpIDS1.mjs";
14
14
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
15
- import { t as getAuthenticatedDocumentLoader } from "../docloader-Ds5k4A6d.mjs";
15
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-QDpF5t9M.mjs";
16
16
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
17
- import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-BRqKkOSf.mjs";
18
- import { t as doesActorOwnKey } from "../owner-B0rmuoQB.mjs";
19
- import { i as verifyObject, r as signObject } from "../proof-B7mAFVed.mjs";
20
- import { g as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-RTcKhytV.mjs";
17
+ import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-D6UlzNnG.mjs";
18
+ import { t as doesActorOwnKey } from "../owner-DWnLg-vk.mjs";
19
+ import { i as verifyObject, r as signObject } from "../proof-mf6h7n4_.mjs";
20
+ import { g as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-CfbFdEO-.mjs";
21
21
  import { t as CircuitBreaker } from "../circuit-breaker-D1fxthDI.mjs";
22
- import { t as markCircuitBreakerLegacySweepDone } from "../circuit-breaker-test-utils-Dm5MM6l9.mjs";
22
+ import { t as markCircuitBreakerLegacySweepDone } from "../circuit-breaker-test-utils-CAdwjgMi.mjs";
23
23
  import { configure, reset } from "@logtape/logtape";
24
24
  import { RouterError } from "@fedify/uri-template";
25
25
  import * as vocab from "@fedify/vocab";
@@ -2916,7 +2916,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2916
2916
  activity: {
2917
2917
  "@context": "https://www.w3.org/ns/activitystreams",
2918
2918
  type: "Create",
2919
- id: "https://example.com/activities/queued-processed",
2919
+ id: "https://remote.example/activities/queued-processed",
2920
2920
  actor: "https://remote.example/users/alice",
2921
2921
  object: {
2922
2922
  type: "Note",
@@ -2934,6 +2934,50 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2934
2934
  assertEquals(inboxLifecycle[0].attributes["activitypub.processing.result"], "processed");
2935
2935
  assertEquals(inboxLifecycle[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
2936
2936
  });
2937
+ await t.step("records activitypub.inbox.activity rejected for a queued activity whose ID has another origin than its actor", async () => {
2938
+ const kv = new MemoryKvStore();
2939
+ const [meterProvider, recorder] = createTestMeterProvider();
2940
+ const federation = new FederationImpl({
2941
+ kv,
2942
+ meterProvider,
2943
+ queue: {
2944
+ enqueue(_message, _options) {
2945
+ return Promise.resolve();
2946
+ },
2947
+ listen(_handler, _options) {
2948
+ return Promise.resolve();
2949
+ }
2950
+ }
2951
+ });
2952
+ let dispatched = false;
2953
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
2954
+ dispatched = true;
2955
+ });
2956
+ await federation.processQueuedTask(void 0, {
2957
+ type: "inbox",
2958
+ id: crypto.randomUUID(),
2959
+ baseUrl: "https://example.com",
2960
+ activity: {
2961
+ "@context": "https://www.w3.org/ns/activitystreams",
2962
+ type: "Create",
2963
+ id: "https://victim.example/activities/queued-forged",
2964
+ actor: "https://remote.example/users/alice",
2965
+ object: {
2966
+ type: "Note",
2967
+ content: "Hello world"
2968
+ }
2969
+ },
2970
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2971
+ attempt: 0,
2972
+ identifier: null,
2973
+ traceContext: {}
2974
+ });
2975
+ assertFalse(dispatched);
2976
+ const inboxLifecycle = recorder.getMeasurements("activitypub.inbox.activity");
2977
+ assertEquals(inboxLifecycle.length, 1);
2978
+ assertEquals(inboxLifecycle[0].attributes["activitypub.processing.result"], "rejected");
2979
+ assertEquals(inboxLifecycle[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
2980
+ });
2937
2981
  await t.step("records activitypub.inbox.activity retried on transient listener failure", async () => {
2938
2982
  const kv = new MemoryKvStore();
2939
2983
  const [meterProvider, recorder] = createTestMeterProvider();
@@ -2959,7 +3003,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2959
3003
  activity: {
2960
3004
  "@context": "https://www.w3.org/ns/activitystreams",
2961
3005
  type: "Create",
2962
- id: "https://example.com/activities/queued-retried",
3006
+ id: "https://remote.example/activities/queued-retried",
2963
3007
  actor: "https://remote.example/users/alice",
2964
3008
  object: {
2965
3009
  type: "Note",
@@ -3002,7 +3046,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
3002
3046
  activity: {
3003
3047
  "@context": "https://www.w3.org/ns/activitystreams",
3004
3048
  type: "Create",
3005
- id: "https://example.com/activities/queued-abandoned",
3049
+ id: "https://remote.example/activities/queued-abandoned",
3006
3050
  actor: "https://remote.example/users/alice",
3007
3051
  object: {
3008
3052
  type: "Note",
@@ -6938,6 +6982,124 @@ test("ContextImpl.routeActivity() marks queued signed activities as non-LDS", as
6938
6982
  assertEquals(inboxMessage.ldSignatureVerified, false);
6939
6983
  assertEquals(inboxMessage.normalizedActivity, void 0);
6940
6984
  });
6985
+ for (const queued of [false, true]) test(`ContextImpl.routeActivity() rejects a proof-signed cross-origin ID (queued: ${queued})`, async () => {
6986
+ const messages = [];
6987
+ const queue = {
6988
+ enqueue(message) {
6989
+ messages.push(message);
6990
+ return Promise.resolve();
6991
+ },
6992
+ async listen() {}
6993
+ };
6994
+ const federation = createFederation({
6995
+ kv: new MemoryKvStore(),
6996
+ documentLoaderFactory: () => mockDocumentLoader,
6997
+ contextLoaderFactory: () => mockDocumentLoader,
6998
+ queue: queued ? {
6999
+ inbox: queue,
7000
+ outbox: queue
7001
+ } : void 0,
7002
+ manuallyStartQueue: true
7003
+ });
7004
+ let listenerCalls = 0;
7005
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
7006
+ listenerCalls++;
7007
+ });
7008
+ const signed = await signObject(new Create({
7009
+ id: new URL("https://victim.example/activities/1"),
7010
+ actor: ed25519Multikey.controllerId
7011
+ }), ed25519PrivateKey, ed25519Multikey.id, {
7012
+ contextLoader: mockDocumentLoader,
7013
+ documentLoader: mockDocumentLoader
7014
+ });
7015
+ assertFalse(await federation.createContext(new URL("https://local.example/")).routeActivity(null, signed));
7016
+ assertEquals(listenerCalls, 0);
7017
+ assertEquals(messages, []);
7018
+ });
7019
+ test("ContextImpl.routeActivity() rejects a missing actor before routing", async () => {
7020
+ const federation = createFederation({
7021
+ kv: new MemoryKvStore(),
7022
+ documentLoaderFactory: () => mockDocumentLoader,
7023
+ contextLoaderFactory: () => mockDocumentLoader
7024
+ });
7025
+ let listenerCalls = 0;
7026
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
7027
+ listenerCalls++;
7028
+ });
7029
+ assertFalse(await federation.createContext(new URL("https://local.example/")).routeActivity(null, new Create({ id: new URL("https://remote.example/activities/1") })));
7030
+ assertEquals(listenerCalls, 0);
7031
+ });
7032
+ test("FederationImpl.processQueuedTask() rejects untrusted activity origins before KV access", async () => {
7033
+ class TrackingKvStore extends MemoryKvStore {
7034
+ reads = 0;
7035
+ writes = 0;
7036
+ get(key) {
7037
+ this.reads++;
7038
+ return super.get(key);
7039
+ }
7040
+ set(key, value, options) {
7041
+ this.writes++;
7042
+ return super.set(key, value, options);
7043
+ }
7044
+ }
7045
+ for (const activity of [{
7046
+ "@context": "https://www.w3.org/ns/activitystreams",
7047
+ id: "https://victim.example/activities/1",
7048
+ type: "Create",
7049
+ actor: "https://attacker.example/actor"
7050
+ }, {
7051
+ "@context": "https://www.w3.org/ns/activitystreams",
7052
+ id: "https://victim.example/activities/2",
7053
+ type: "Create"
7054
+ }]) {
7055
+ const kv = new TrackingKvStore();
7056
+ const federation = new FederationImpl({ kv });
7057
+ let listenerCalls = 0;
7058
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
7059
+ listenerCalls++;
7060
+ });
7061
+ await federation.processQueuedTask(void 0, {
7062
+ type: "inbox",
7063
+ id: crypto.randomUUID(),
7064
+ baseUrl: "https://local.example",
7065
+ activity,
7066
+ started: (/* @__PURE__ */ new Date()).toISOString(),
7067
+ attempt: 0,
7068
+ identifier: null,
7069
+ traceContext: {}
7070
+ });
7071
+ assertEquals(listenerCalls, 0);
7072
+ assertEquals(kv.reads, 0);
7073
+ assertEquals(kv.writes, 0);
7074
+ }
7075
+ const kv = new TrackingKvStore();
7076
+ const federation = new FederationImpl({
7077
+ kv,
7078
+ skipSignatureVerification: true
7079
+ });
7080
+ let listenerCalls = 0;
7081
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
7082
+ listenerCalls++;
7083
+ });
7084
+ await federation.processQueuedTask(void 0, {
7085
+ type: "inbox",
7086
+ id: crypto.randomUUID(),
7087
+ baseUrl: "https://local.example",
7088
+ activity: {
7089
+ "@context": "https://www.w3.org/ns/activitystreams",
7090
+ id: "https://victim.example/activities/opt-out",
7091
+ type: "Create",
7092
+ actor: "https://attacker.example/actor"
7093
+ },
7094
+ started: (/* @__PURE__ */ new Date()).toISOString(),
7095
+ attempt: 0,
7096
+ identifier: null,
7097
+ traceContext: {}
7098
+ });
7099
+ assertEquals(listenerCalls, 1);
7100
+ assertEquals(kv.reads, 1);
7101
+ assertEquals(kv.writes, 1);
7102
+ });
6941
7103
  test("ContextImpl.getCollectionUri()", () => {
6942
7104
  const federation = new FederationImpl({ kv: new MemoryKvStore() });
6943
7105
  const base = "https://example.com";
@@ -2,7 +2,7 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("../chunk-DDcVe30Y.cjs");
5
- const require_middleware = require("../middleware-DanzltB1.cjs");
5
+ const require_middleware = require("../middleware-DkrXqZO_.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
7
7
  let _fedify_uri_template = require("@fedify/uri-template");
8
8
  let es_toolkit = require("es-toolkit");
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { a as SendActivityError, c as buildCollectionSynchronizationHeader, d as normalizeCircuitBreakerOptions, f as parseCircuitBreakerKvState, i as createExponentialBackoffPolicy, l as digest, o as respondWithObject, p as createFederationBuilder, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as CircuitBreaker } from "../middleware-B-CIN962.js";
3
+ import { a as SendActivityError, c as buildCollectionSynchronizationHeader, d as normalizeCircuitBreakerOptions, f as parseCircuitBreakerKvState, i as createExponentialBackoffPolicy, l as digest, o as respondWithObject, p as createFederationBuilder, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as CircuitBreaker } from "../middleware-xw9fBZoB.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { Router as Router$1, RouterError as RouterError$1, assertPath, isPath } from "@fedify/uri-template";
6
6
  import { isEqual } from "es-toolkit";
@@ -2,9 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BvGuO4xO.mjs";
6
- import { n as assertGreaterOrEqual, r as assertFalse } from "../assert_rejects-DN60FHPX.mjs";
7
- import { t as assert } from "../assert-jSdZMYam.mjs";
5
+ import "../std__assert-BjJn2daJ.mjs";
6
+ import { r as assertFalse } from "../assert_throws-BbSMEFKo.mjs";
7
+ import { n as assertGreaterOrEqual } from "../assert_rejects-D1HYAik3.mjs";
8
+ import { t as assert } from "../assert-CEO06XCw.mjs";
8
9
  import { test } from "@fedify/fixture";
9
10
  import { delay } from "es-toolkit";
10
11
  //#region src/federation/mq.ts
@@ -1,10 +1,10 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import "../std__assert-BvGuO4xO.mjs";
5
- import { r as assertFalse } from "../assert_rejects-DN60FHPX.mjs";
6
- import { t as assert } from "../assert-jSdZMYam.mjs";
7
- import { t as acceptsJsonLd } from "../negotiation-CDW-_gUU.mjs";
4
+ import "../std__assert-BjJn2daJ.mjs";
5
+ import { r as assertFalse } from "../assert_throws-BbSMEFKo.mjs";
6
+ import { t as assert } from "../assert-CEO06XCw.mjs";
7
+ import { t as acceptsJsonLd } from "../negotiation-DDstyBvc.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/negotiation.test.ts
10
10
  test("acceptsJsonLd()", () => {
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { l as AssertionError, t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
5
  import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
6
- import { t as createExponentialBackoffPolicy } from "../retry-_VvV0h9f.mjs";
6
+ import { t as createExponentialBackoffPolicy } from "../retry-CXg_MBI-.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  //#region src/federation/retry.test.ts
9
9
  test("createExponentialBackoffPolicy()", () => {
@@ -2,10 +2,9 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BvGuO4xO.mjs";
6
- import { r as assertFalse } from "../assert_rejects-DN60FHPX.mjs";
7
- import { t as assert } from "../assert-jSdZMYam.mjs";
8
- import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
5
+ import "../std__assert-BjJn2daJ.mjs";
6
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-BbSMEFKo.mjs";
7
+ import { t as assert } from "../assert-CEO06XCw.mjs";
9
8
  import { getLogger } from "@logtape/logtape";
10
9
  import { Router, RouterError, assertPath, isPath } from "@fedify/uri-template";
11
10
  import { test } from "@fedify/fixture";
@@ -3,14 +3,15 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-vrlUxr60.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
- import "../std__assert-BvGuO4xO.mjs";
7
- import { n as assertGreaterOrEqual, r as assertFalse, t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
- import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
6
+ import "../std__assert-BjJn2daJ.mjs";
7
+ import { r as assertFalse } from "../assert_throws-BbSMEFKo.mjs";
8
+ import { n as assertGreaterOrEqual, t as assertRejects } from "../assert_rejects-D1HYAik3.mjs";
9
+ import { n as assertInstanceOf, t as assert } from "../assert-CEO06XCw.mjs";
9
10
  import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
10
- import { l as verifyRequest } from "../http-D9h6mQdq.mjs";
11
+ import { l as verifyRequest } from "../http-BRrpIDS1.mjs";
11
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
12
- import { t as doesActorOwnKey } from "../owner-B0rmuoQB.mjs";
13
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-BFukz_kE.mjs";
13
+ import { t as doesActorOwnKey } from "../owner-DWnLg-vk.mjs";
14
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-LJUzNpVF.mjs";
14
15
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
15
16
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
16
17
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -1,10 +1,10 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import "../std__assert-BvGuO4xO.mjs";
5
- import { r as assertFalse } from "../assert_rejects-DN60FHPX.mjs";
6
- import { t as assert } from "../assert-jSdZMYam.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-n6j0MNF9.mjs";
4
+ import "../std__assert-BjJn2daJ.mjs";
5
+ import { r as assertFalse } from "../assert_throws-BbSMEFKo.mjs";
6
+ import { t as assert } from "../assert-CEO06XCw.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-_3yIqdvy.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/temporal.test.ts
10
10
  test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
@@ -3,10 +3,10 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { r as createRequestContext } from "../context-CxbCIptr.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
- import "../std__assert-BvGuO4xO.mjs";
6
+ import "../std__assert-BjJn2daJ.mjs";
7
7
  import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
8
8
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
9
- import { o as createFederation, s as handleWebFinger } from "../middleware-RTcKhytV.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-CfbFdEO-.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  import { createTestMeterProvider, test } from "@fedify/fixture";
12
12
  //#region src/federation/webfinger.test.ts
@@ -1,10 +1,10 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-DSS6EkPl.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Z0UkrR_1.mjs";
4
+ import { n as version, t as name } from "./deno-XpcjbHwj.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-62qolVUY.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-Dd__NiUL.mjs";
7
- import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-vnmAGZ3_.mjs";
7
+ import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-PdEu1lDF.mjs";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { CryptographicKey } from "@fedify/vocab";
10
10
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
11
11
  let byte_encodings_base64 = require("byte-encodings/base64");
12
12
  //#region deno.json
13
13
  var name = "@fedify/fedify";
14
- var version = "2.3.12-dev.2365+89254121";
14
+ var version = "2.3.13";
15
15
  //#endregion
16
16
  //#region src/sig/accept.ts
17
17
  /**
@@ -961,6 +961,65 @@ function getDurationMs(start) {
961
961
  }
962
962
  //#endregion
963
963
  //#region src/sig/key.ts
964
+ /** Classifies decoding failures without hiding unexpected loader errors. */
965
+ function classifyKeyJsonLdError(error, seen = /* @__PURE__ */ new Set()) {
966
+ if (!(error instanceof Error) || !error.name.startsWith("jsonld.")) return null;
967
+ if (seen.has(error)) throw error;
968
+ seen.add(error);
969
+ const details = error.details;
970
+ if (typeof details?.code !== "string") return null;
971
+ if (details.code !== "loading remote context failed" || !("cause" in details) && !("cause" in error)) return "invalid";
972
+ const cause = "cause" in details ? details.cause : error.cause;
973
+ const nestedFailure = classifyKeyJsonLdError(cause, seen);
974
+ if (nestedFailure != null) return nestedFailure;
975
+ if (cause instanceof SyntaxError) return "invalid";
976
+ if (cause instanceof _fedify_vocab_runtime.FetchError || cause instanceof _fedify_vocab_runtime.UrlError) return "fetch";
977
+ if (cause instanceof Error && [
978
+ "AbortError",
979
+ "TimeoutError",
980
+ "NetworkError"
981
+ ].includes(cause.name)) return "fetch";
982
+ if (cause instanceof TypeError) {
983
+ const urlCode = cause.code;
984
+ if (cause.name === "InvalidContextReferenceError") return "invalid";
985
+ 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";
986
+ const networkCause = cause.cause;
987
+ const code = cause.code ?? networkCause?.code;
988
+ if (typeof code === "string" && [
989
+ "ECONNREFUSED",
990
+ "ECONNRESET",
991
+ "ECONNABORTED",
992
+ "ETIMEDOUT",
993
+ "EHOSTUNREACH",
994
+ "ENETUNREACH",
995
+ "ENOTFOUND",
996
+ "EAI_AGAIN",
997
+ "UND_ERR_CONNECT_TIMEOUT",
998
+ "UND_ERR_HEADERS_TIMEOUT",
999
+ "UND_ERR_BODY_TIMEOUT",
1000
+ "UND_ERR_SOCKET"
1001
+ ].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";
1002
+ }
1003
+ throw cause;
1004
+ }
1005
+ /** Carries a context transport failure through the key decoding boundary. */
1006
+ var KeyContextFetchError = class extends Error {
1007
+ error;
1008
+ constructor(error) {
1009
+ super("Failed to fetch a key's JSON-LD context.", { cause: error });
1010
+ this.error = error;
1011
+ }
1012
+ };
1013
+ function contextFetchError(error) {
1014
+ const seen = /* @__PURE__ */ new Set();
1015
+ while (error instanceof Error && !seen.has(error)) {
1016
+ seen.add(error);
1017
+ const details = error.details;
1018
+ if (!error.name.startsWith("jsonld.") || details?.code !== "loading remote context failed") break;
1019
+ error = "cause" in details ? details.cause : error.cause;
1020
+ }
1021
+ return new KeyContextFetchError(error);
1022
+ }
964
1023
  /**
965
1024
  * Checks if the given key is valid and supported. No-op if the key is valid,
966
1025
  * otherwise throws an error.
@@ -1055,6 +1114,9 @@ async function importJwk(jwk, type) {
1055
1114
  * @internal
1056
1115
  */
1057
1116
  async function fetchActorDocument(actorId, options = {}) {
1117
+ return await fetchActorDocumentInternal(actorId, options, false);
1118
+ }
1119
+ async function fetchActorDocumentInternal(actorId, options, propagateContextFetchErrors) {
1058
1120
  const logger = (0, _logtape_logtape.getLogger)([
1059
1121
  "fedify",
1060
1122
  "sig",
@@ -1085,9 +1147,12 @@ async function fetchActorDocument(actorId, options = {}) {
1085
1147
  baseUrl: documentUrl
1086
1148
  });
1087
1149
  } catch (error) {
1088
- if (!(error instanceof TypeError)) throw error;
1089
- logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
1150
+ const failure = classifyKeyJsonLdError(error);
1151
+ if (failure === "fetch" && propagateContextFetchErrors) throw contextFetchError(error);
1152
+ if (failure == null && !(error instanceof TypeError)) throw error;
1153
+ logger.debug("Failed to decode the actor document at {documentUrl}: {error}", {
1090
1154
  documentUrl: documentUrl.href,
1155
+ failure: failure ?? "invalid",
1091
1156
  error
1092
1157
  });
1093
1158
  return null;
@@ -1126,6 +1191,9 @@ async function fetchActorDocument(actorId, options = {}) {
1126
1191
  * @internal
1127
1192
  */
1128
1193
  async function verifyKeyOwnership(key, options = {}) {
1194
+ return await verifyKeyOwnershipInternal(key, options, false);
1195
+ }
1196
+ async function verifyKeyOwnershipInternal(key, options, propagateContextFetchErrors) {
1129
1197
  const logger = (0, _logtape_logtape.getLogger)([
1130
1198
  "fedify",
1131
1199
  "sig",
@@ -1135,7 +1203,7 @@ async function verifyKeyOwnership(key, options = {}) {
1135
1203
  if (keyId == null) return null;
1136
1204
  const claimedOwnerId = key instanceof _fedify_vocab.CryptographicKey ? key.ownerId : key.controllerId;
1137
1205
  if (claimedOwnerId == null) return null;
1138
- const owner = await fetchActorDocument(claimedOwnerId, options);
1206
+ const owner = await fetchActorDocumentInternal(claimedOwnerId, options, propagateContextFetchErrors);
1139
1207
  if (owner == null) {
1140
1208
  logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
1141
1209
  keyId: keyId.href,
@@ -1287,6 +1355,21 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
1287
1355
  tracerProvider
1288
1356
  });
1289
1357
  } catch (e) {
1358
+ const failure = classifyKeyJsonLdError(e);
1359
+ if (failure === "fetch") throw contextFetchError(e);
1360
+ if (failure != null) {
1361
+ logger.debug("Failed to decode key {keyId}: {error}", {
1362
+ keyId,
1363
+ failure,
1364
+ error: e
1365
+ });
1366
+ await keyCache?.set(cacheKey, null);
1367
+ await clearFetchErrorMetadata(cacheKey, keyCache);
1368
+ return {
1369
+ key: null,
1370
+ cached: false
1371
+ };
1372
+ }
1290
1373
  if (!(e instanceof TypeError)) throw e;
1291
1374
  try {
1292
1375
  object = await cls.fromJsonLd(document, {
@@ -1295,8 +1378,14 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
1295
1378
  tracerProvider
1296
1379
  });
1297
1380
  } catch (e) {
1298
- if (e instanceof TypeError) {
1299
- logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
1381
+ const failure = classifyKeyJsonLdError(e);
1382
+ if (failure === "fetch") throw contextFetchError(e);
1383
+ if (failure != null || e instanceof TypeError) {
1384
+ logger.debug("Failed to decode key {keyId}: {error}", {
1385
+ keyId,
1386
+ failure: failure ?? "invalid",
1387
+ error: e
1388
+ });
1300
1389
  await keyCache?.set(cacheKey, null);
1301
1390
  await clearFetchErrorMetadata(cacheKey, keyCache);
1302
1391
  return {
@@ -1388,16 +1477,17 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
1388
1477
  const claimedOwnerId = key instanceof _fedify_vocab.CryptographicKey ? key.ownerId : key.controllerId;
1389
1478
  if (ownerDocument != null && claimedOwnerId == null) key = key instanceof _fedify_vocab.CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
1390
1479
  else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
1391
- if (await verifyKeyOwnership(key, {
1480
+ if (await verifyKeyOwnershipInternal(key, {
1392
1481
  documentLoader,
1393
1482
  contextLoader,
1394
1483
  tracerProvider
1395
- }) == null) {
1484
+ }, true) == null) {
1396
1485
  logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
1397
1486
  keyId,
1398
1487
  claimedOwnerId: claimedOwnerId.href
1399
1488
  });
1400
1489
  await keyCache?.set(cacheKey, null);
1490
+ await clearFetchErrorMetadata(cacheKey, keyCache);
1401
1491
  return {
1402
1492
  key: null,
1403
1493
  cached: false
@@ -1448,9 +1538,17 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
1448
1538
  outcome = classified;
1449
1539
  return errored;
1450
1540
  }
1451
- const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
1452
- outcome = { result: resolved.key != null ? "fetched" : "invalid" };
1453
- return resolved;
1541
+ try {
1542
+ const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
1543
+ outcome = { result: resolved.key != null ? "fetched" : "invalid" };
1544
+ return resolved;
1545
+ } catch (error) {
1546
+ if (!(error instanceof KeyContextFetchError)) throw error;
1547
+ const classified = classifyFetchError(error.error);
1548
+ const errored = await onFetchError(error.error, cacheKey, keyId, keyCache, logger);
1549
+ outcome = classified;
1550
+ return errored;
1551
+ }
1454
1552
  } finally {
1455
1553
  recordKeyLookup(options.meterProvider, {
1456
1554
  durationMs: getDurationMs(start),