@fedify/fedify 2.4.0-dev.2084 → 2.4.0-dev.2099

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 (84) hide show
  1. package/dist/{builder-BGlpUocK.mjs → builder-Bno54o_-.mjs} +34 -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/{compound-proof-B6Aj2kGY.mjs → compound-proof-X3SxXVbc.mjs} +191 -13
  6. package/dist/{context-CZjZYXFC.d.cts → context-C43spTGI.d.cts} +5 -2
  7. package/dist/{context-BSywYspi.d.ts → context-MORIQJIh.d.ts} +5 -2
  8. package/dist/{deno-Dn-py9Zy.mjs → deno-dxdvl2XZ.mjs} +1 -1
  9. package/dist/{docloader-BUq5bYJr.mjs → docloader-eBxQDsk9.mjs} +2 -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 +3 -3
  13. package/dist/federation/handler.test.mjs +5 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/metrics.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +7 -7
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +2 -2
  20. package/dist/federation/mod.d.ts +2 -2
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/outgoing-proof.test.mjs +254 -5
  23. package/dist/federation/portable-gateway-keys.test.mjs +72 -11
  24. package/dist/federation/portable-inbox.test.mjs +487 -10
  25. package/dist/federation/portable.test.mjs +182 -4
  26. package/dist/federation/send.test.mjs +3 -3
  27. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  28. package/dist/federation/tasks/tasks.test.mjs +2 -2
  29. package/dist/federation/temporal.test.mjs +1 -1
  30. package/dist/federation/webfinger.test.mjs +89 -1
  31. package/dist/{federation-BLGx8JUH.cjs → federation-C4Ef3JKa.cjs} +1487 -1070
  32. package/dist/{http-B5q1CHtL.cjs → http-C8cZTE3Y.cjs} +1224 -985
  33. package/dist/{http-4PCCvhsU.js → http-D-rbr_Sy.js} +1663 -1460
  34. package/dist/{http-B0VRNipE.mjs → http-DsgawTd8.mjs} +82 -7
  35. package/dist/{key-BJrp524f.mjs → key-DYfmjnmp.mjs} +39 -99
  36. package/dist/{kv-cache-1KUmAPH7.js → kv-cache-DIxwMeNi.js} +1 -1
  37. package/dist/{kv-cache-Dhhhrryv.cjs → kv-cache-Dvd-Q-E2.cjs} +1 -1
  38. package/dist/{kv-cache-C4C3LyYW.mjs → kv-cache-EvOgrFsS.mjs} +1 -1
  39. package/dist/{ld-BmFz5NLA.mjs → ld-B00wWHzc.mjs} +3 -3
  40. package/dist/{metrics-Beod6tzO.mjs → metrics-BP3vdBpb.mjs} +1 -1
  41. package/dist/{middleware-BKeQYPm0.mjs → middleware-Br1_d-Ga.mjs} +606 -389
  42. package/dist/{middleware-D_9pNYe9.js → middleware-CcXMe400.js} +1602 -1185
  43. package/dist/{middleware-B7CNsyqt.mjs → middleware-CeBVBa61.mjs} +1 -1
  44. package/dist/{mod-B2h2KY0q.d.ts → mod-BL5BQHCq.d.ts} +1 -1
  45. package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
  46. package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
  47. package/dist/{mod-JzkaEXsi.d.cts → mod-DMI4DbOo.d.cts} +1 -1
  48. package/dist/mod.cjs +5 -5
  49. package/dist/mod.d.cts +3 -3
  50. package/dist/mod.d.ts +3 -3
  51. package/dist/mod.js +5 -5
  52. package/dist/nodeinfo/handler.test.mjs +1 -1
  53. package/dist/{owner-CsKVgMzk.mjs → owner-BJPDRDRq.mjs} +5 -4
  54. package/dist/{owner-BQFZtFV4.cjs → owner-DSzH7wro.cjs} +2 -2
  55. package/dist/{owner-_pbbgLJK.js → owner-U12fDpQW.js} +2 -2
  56. package/dist/{portable-key-B4R-CiIV.mjs → portable-key-D0mWbV9G.mjs} +60 -41
  57. package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
  58. package/dist/{proof-ozG4HfcQ.mjs → proof-ChjBwnep.mjs} +59 -18
  59. package/dist/{send-ClPt8nYu.mjs → send-yFX0E-ye.mjs} +3 -3
  60. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  61. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  62. package/dist/sig/compound-proof-verification.test.mjs +160 -3
  63. package/dist/sig/compound-proof.test.mjs +2 -2
  64. package/dist/sig/http.test.mjs +72 -2
  65. package/dist/sig/key.test.mjs +1 -1
  66. package/dist/sig/ld.test.mjs +2 -2
  67. package/dist/sig/mod.cjs +3 -3
  68. package/dist/sig/mod.d.cts +1 -1
  69. package/dist/sig/mod.d.ts +1 -1
  70. package/dist/sig/mod.js +3 -3
  71. package/dist/sig/owner.test.mjs +1 -1
  72. package/dist/sig/portable-collection.test.mjs +69 -5
  73. package/dist/sig/portable-key.test.mjs +176 -10
  74. package/dist/sig/proof.test.mjs +107 -4
  75. package/dist/{sig-k3C8sF7g.cjs → sig-B689Nj7y.cjs} +19 -3
  76. package/dist/{sig-DhdomAN0.js → sig-C85KqAcx.js} +19 -3
  77. package/dist/{temporal-Ba0EAZAI.mjs → temporal-Dl9x-Qw4.mjs} +1 -1
  78. package/dist/testing/kv.test.mjs +1 -1
  79. package/dist/testing/mod.d.mts +5 -2
  80. package/dist/utils/docloader.test.mjs +2 -2
  81. package/dist/utils/kv-cache.test.mjs +1 -1
  82. package/dist/utils/mod.cjs +1 -1
  83. package/dist/utils/mod.js +1 -1
  84. package/package.json +7 -7
@@ -1,11 +1,11 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Beod6tzO.mjs";
4
+ import { n as version, t as name } from "./deno-dxdvl2XZ.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BP3vdBpb.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
7
- import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-BJrp524f.mjs";
8
- import { n as verifyPortableGatewayKeyDocument } from "./portable-key-B4R-CiIV.mjs";
7
+ import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-DYfmjnmp.mjs";
8
+ import { n as verifyPortableGatewayKeyDocument } from "./portable-key-D0mWbV9G.mjs";
9
9
  import { getLogger } from "@logtape/logtape";
10
10
  import { CryptographicKey } from "@fedify/vocab";
11
11
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -13,7 +13,7 @@ import { FetchError } from "@fedify/vocab-runtime";
13
13
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
14
14
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
15
15
  import { encodeHex } from "byte-encodings/hex";
16
- import { Item, decodeDict, encodeItem } from "structured-field-values";
16
+ import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
17
17
  //#region src/sig/http.ts
18
18
  const DEFAULT_MAX_REDIRECTION = 20;
19
19
  const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
@@ -202,6 +202,81 @@ function parseRfc9421Signature(signature) {
202
202
  for (const [key, value] of Object.entries(dict)) if (value.value instanceof Uint8Array) result[key] = value.value;
203
203
  return result;
204
204
  }
205
+ /**
206
+ * Parses the `Signature` header of draft-cavage HTTP Signatures into its
207
+ * fields, e.g., `keyId` and `headers`.
208
+ */
209
+ function parseDraftSignature(signature) {
210
+ return Object.fromEntries(signature.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
211
+ }
212
+ /**
213
+ * Lists the HTTP Signatures that a request claims to carry, without verifying
214
+ * any of them. Signatures whose key IDs are not URLs are left out.
215
+ * @param request The request.
216
+ * @returns The signatures; RFC 9421 ones if the request has
217
+ * a `Signature-Input` header, or a draft-cavage one otherwise.
218
+ * @internal
219
+ */
220
+ function listRequestSignatures(request) {
221
+ const signatureInput = request.headers.get("Signature-Input");
222
+ if (signatureInput != null) {
223
+ const signatures = [];
224
+ const inputs = parseRfc9421SignatureInput(signatureInput);
225
+ for (const [label, input] of Object.entries(inputs)) {
226
+ const keyId = parseKeyId(input.keyId);
227
+ if (keyId == null) continue;
228
+ signatures.push({
229
+ keyId,
230
+ label,
231
+ components: input.components
232
+ });
233
+ }
234
+ return signatures;
235
+ }
236
+ const signature = request.headers.get("Signature");
237
+ if (signature == null) return [];
238
+ const values = parseDraftSignature(signature);
239
+ const keyId = parseKeyId(values.keyId);
240
+ if (keyId == null) return [];
241
+ return [{
242
+ keyId,
243
+ label: null,
244
+ components: (values.headers ?? "").split(/\s+/g).filter((name) => name !== "").map((name) => ({
245
+ value: name,
246
+ params: {}
247
+ }))
248
+ }];
249
+ }
250
+ /**
251
+ * Makes a copy of a request that carries only one of its RFC 9421 signatures,
252
+ * so that verifying the copy verifies that signature and no other.
253
+ * A draft-cavage signature is the only one in its request, so the request is
254
+ * just copied.
255
+ * @param request The request, whose body must not be consumed.
256
+ * @param signature One of the signatures that {@link listRequestSignatures}
257
+ * lists for the request.
258
+ * @returns The copy of the request.
259
+ * @internal
260
+ */
261
+ function selectRequestSignature(request, signature) {
262
+ const copy = request.clone();
263
+ if (signature.label == null) return copy;
264
+ const { label } = signature;
265
+ const pick = (header) => {
266
+ if (header == null) return "";
267
+ let dict;
268
+ try {
269
+ dict = decodeDict(header);
270
+ } catch {
271
+ return "";
272
+ }
273
+ return Object.hasOwn(dict, label) ? encodeDict({ [label]: dict[label] }) : "";
274
+ };
275
+ const headers = new Headers(copy.headers);
276
+ headers.set("Signature-Input", pick(headers.get("Signature-Input")));
277
+ headers.set("Signature", pick(headers.get("Signature")));
278
+ return new Request(copy, { headers });
279
+ }
205
280
  async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer, rfc9421Options) {
206
281
  if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
207
282
  const url = new URL(request.url);
@@ -433,7 +508,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
433
508
  logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
434
509
  return noSignatureResult();
435
510
  }
436
- const sigValues = Object.fromEntries(sigHeader.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
511
+ const sigValues = parseDraftSignature(sigHeader);
437
512
  const parsedKeyId = parseKeyId(sigValues.keyId);
438
513
  const dateHeader = request.headers.get("Date");
439
514
  if (dateHeader == null) {
@@ -1106,4 +1181,4 @@ function timingSafeEqual(a, b) {
1106
1181
  return result === 0;
1107
1182
  }
1108
1183
  //#endregion
1109
- export { parseRfc9421Signature as a, timingSafeEqual as c, formatRfc9421SignatureParameters as i, verifyRequest as l, doubleKnock as n, parseRfc9421SignatureInput as o, formatRfc9421Signature as r, signRequest as s, createRfc9421SignatureBase as t, verifyRequestDetailed as u };
1184
+ export { listRequestSignatures as a, selectRequestSignature as c, verifyRequest as d, verifyRequestDetailed as f, formatRfc9421SignatureParameters as i, signRequest as l, doubleKnock as n, parseRfc9421Signature as o, formatRfc9421Signature as r, parseRfc9421SignatureInput as s, createRfc9421SignatureBase as t, timingSafeEqual as u };
@@ -1,105 +1,13 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-Beod6tzO.mjs";
4
+ import { n as version, t as name } from "./deno-dxdvl2XZ.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-BP3vdBpb.mjs";
6
+ import { a as isCompatibleKeyId, o as isPortableActorDocument, s as isPortableId } from "./portable-key-id-Bva5iT3W.mjs";
6
7
  import { getLogger } from "@logtape/logtape";
7
8
  import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
8
9
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
9
- import { FetchError, canonicalizePortableUri, formatIri, fromCompatibleEf61Id, getDocumentLoader, parseDidKeyVerificationMethod, toCompatibleEf61Id } from "@fedify/vocab-runtime";
10
- //#region src/sig/portable-key-id.ts
11
- const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
12
- /**
13
- * Checks whether the URL is an `ap:` or `ap+ef61:` URI.
14
- * @internal
15
- */
16
- function isPortableUri(url) {
17
- return url.protocol === "ap:" || url.protocol === "ap+ef61:";
18
- }
19
- /**
20
- * Checks whether an activity is performed by an FEP-ef61 portable actor,
21
- * i.e., any of its actors has an `ap:` or `ap+ef61:` ID.
22
- * @internal
23
- */
24
- function hasPortableActor(activity) {
25
- return activity.actorIds.some(isPortableUri);
26
- }
27
- /**
28
- * Checks whether a key ID looks like an [FEP-ef61] compatible identifier,
29
- * i.e., an HTTP(S) URL under a gateway's `/.well-known/apgateway/did:` path.
30
- * Such a key ID names a key that a gateway holds for a portable actor, so it
31
- * is resolved only as a gateway key, and never cached in a key cache.
32
- *
33
- * [FEP-ef61]: https://w3id.org/fep/ef61
34
- * @internal
35
- */
36
- function isCompatibleKeyId(keyId) {
37
- return (keyId.protocol === "http:" || keyId.protocol === "https:") && COMPATIBLE_KEY_ID_PATH_PATTERN.test(keyId.pathname);
38
- }
39
- /**
40
- * Gets the canonical portable ID of an actor ID that is either an `ap:` or
41
- * `ap+ef61:` URI or a compatible identifier.
42
- * @returns The canonical portable ID, or `null` if the ID is neither, or is
43
- * malformed.
44
- * @internal
45
- */
46
- function getCanonicalPortableId(id) {
47
- try {
48
- if (isPortableUri(id)) return canonicalizePortableUri(formatIri(id));
49
- const portable = fromCompatibleEf61Id(id);
50
- if (portable == null) return null;
51
- return canonicalizePortableUri(formatIri(portable));
52
- } catch (error) {
53
- if (error instanceof TypeError) return null;
54
- throw error;
55
- }
56
- }
57
- /**
58
- * Builds the base of the key IDs of a gateway's keys for a portable actor:
59
- * the actor's compatible identifier on the gateway.
60
- * @param actorId The portable actor's ID, either an `ap:` or `ap+ef61:` URI
61
- * or a compatible identifier. It must not have a fragment.
62
- * @param gateway The gateway's origin, e.g., `https://example.com`.
63
- * @returns The compatible identifier without query and fragment.
64
- * @throws {TypeError} If the actor ID is not a portable ID, or has
65
- * a fragment.
66
- * @internal
67
- */
68
- function getGatewayKeyBase(actorId, gateway) {
69
- let portable;
70
- if (isPortableUri(actorId)) portable = actorId;
71
- else try {
72
- portable = fromCompatibleEf61Id(actorId);
73
- } catch (error) {
74
- if (!(error instanceof TypeError)) throw error;
75
- portable = null;
76
- }
77
- if (portable == null) throw new TypeError(`The portable actor ID ${actorId.href} must be an ap: or ap+ef61: URI, or an FEP-ef61 compatible identifier.`);
78
- if (portable.hash !== "") throw new TypeError(`The portable actor ID ${actorId.href} must not have a fragment.`);
79
- const base = toCompatibleEf61Id(portable, gateway);
80
- base.search = "";
81
- base.hash = "";
82
- return base;
83
- }
84
- /**
85
- * Tells whether a parsed document at a compatible key ID is a portable actor,
86
- * which makes the key a gateway key that only
87
- * {@link verifyPortableGatewayKeyDocument} can vouch for.
88
- * @internal
89
- */
90
- function isPortableActorDocument(object) {
91
- return isActor(object) && object.id != null && isPortableUri(object.id);
92
- }
93
- /**
94
- * Compares two public keys by their key material.
95
- * @internal
96
- */
97
- async function isSamePublicKey(a, b) {
98
- if (a.algorithm.name !== b.algorithm.name) return false;
99
- const [x, y] = await Promise.all([crypto.subtle.exportKey("jwk", a), crypto.subtle.exportKey("jwk", b)]);
100
- return x.kty === y.kty && x.crv === y.crv && x.n === y.n && x.e === y.e && x.x === y.x && x.y === y.y;
101
- }
102
- //#endregion
10
+ import { FetchError, getDocumentLoader, parseDidKeyVerificationMethod } from "@fedify/vocab-runtime";
103
11
  //#region src/sig/key.ts
104
12
  /**
105
13
  * Checks if the given key is valid and supported. No-op if the key is valid,
@@ -200,6 +108,10 @@ async function fetchActorDocument(actorId, options = {}) {
200
108
  "sig",
201
109
  "key"
202
110
  ]);
111
+ if (isPortableId(actorId)) {
112
+ logger.debug("The actor {actorId} is a portable actor, which is not authenticated by the web origin that serves it.", { actorId: actorId.href });
113
+ return null;
114
+ }
203
115
  const documentLoader = options.documentLoader ?? getDocumentLoader();
204
116
  const contextLoader = options.contextLoader ?? getDocumentLoader();
205
117
  const { tracerProvider } = options;
@@ -233,6 +145,13 @@ async function fetchActorDocument(actorId, options = {}) {
233
145
  return null;
234
146
  }
235
147
  if (!isActor(object)) return null;
148
+ if (object.id != null && isPortableId(object.id)) {
149
+ logger.debug("The document served at {documentUrl} claims to be the portable actor {actorId}, which is not authenticated by the web origin that serves it.", {
150
+ documentUrl: documentUrl.href,
151
+ actorId: object.id?.href
152
+ });
153
+ return null;
154
+ }
236
155
  if (object.id == null || object.id.origin !== documentUrl.origin) {
237
156
  logger.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as that actor's document.", {
238
157
  documentUrl: documentUrl.href,
@@ -567,7 +486,7 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
567
486
  let ownerDocument = null;
568
487
  if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
569
488
  else if (isActor(object)) {
570
- if (object.id == null || object.id.origin !== documentUrl.origin) {
489
+ if (object.id == null || isPortableId(object.id) || object.id.origin !== documentUrl.origin) {
571
490
  logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
572
491
  keyId,
573
492
  documentUrl: documentUrl.href,
@@ -643,6 +562,18 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
643
562
  }
644
563
  const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
645
564
  if (!(keyIdBoundByCaller && claimedOwnerId != null && claimedOwnerId.href === stripFragment(cacheKey)?.href)) {
565
+ if (claimedOwnerId != null && isPortableId(claimedOwnerId)) {
566
+ logger.debug("Failed to verify; key {keyId} claims the portable actor {claimedOwnerId} as its owner, but is not its gateway key.", {
567
+ keyId,
568
+ claimedOwnerId: claimedOwnerId.href
569
+ });
570
+ await keyCache?.set(cacheKey, null);
571
+ await clearFetchErrorMetadata(cacheKey, keyCache);
572
+ return {
573
+ key: null,
574
+ cached: false
575
+ };
576
+ }
646
577
  if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
647
578
  else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
648
579
  if (await verifyKeyOwnership(key, {
@@ -672,6 +603,15 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
672
603
  cached: false
673
604
  };
674
605
  }
606
+ /**
607
+ * Tells whether a key claims a portable actor as its owner. Such a key can
608
+ * only be a gateway key, which is never cached, so a cached one was cached
609
+ * by an older version that trusted web origins, and is fetched again.
610
+ */
611
+ function claimsPortableOwner(key) {
612
+ const ownerId = key instanceof CryptographicKey ? key.ownerId : key?.controllerId;
613
+ return ownerId != null && isPortableId(ownerId);
614
+ }
675
615
  async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
676
616
  const start = performance.now();
677
617
  let outcome = { result: "error" };
@@ -698,7 +638,7 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
698
638
  outcome = { result: "hit" };
699
639
  return cachedUnavailable;
700
640
  }
701
- if (cached != null) {
641
+ if (cached != null && !claimsPortableOwner(cached.key)) {
702
642
  outcome = { result: "hit" };
703
643
  return cached;
704
644
  }
@@ -752,4 +692,4 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
752
692
  });
753
693
  }
754
694
  //#endregion
755
- export { generateCryptoKeyPair as a, verifyKeyOwnership as c, hasPortableActor as d, isCompatibleKeyId as f, isSamePublicKey as h, fetchKeyDetailed as i, getCanonicalPortableId as l, isPortableUri as m, fetchActorDocument as n, importJwk as o, isPortableActorDocument as p, fetchKey as r, validateCryptoKey as s, exportJwk as t, getGatewayKeyBase as u };
695
+ export { generateCryptoKeyPair as a, verifyKeyOwnership as c, fetchKeyDetailed as i, fetchActorDocument as n, importJwk as o, fetchKey as r, validateCryptoKey as s, exportJwk as t };
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { R as validateCryptoKey, nt as recordDocumentCache, t as doubleKnock } from "./http-4PCCvhsU.js";
3
+ import { H as validateCryptoKey, et as recordDocumentCache, t as doubleKnock } from "./http-D-rbr_Sy.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-B5q1CHtL.cjs");
2
+ const require_http = require("./http-C8cZTE3Y.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
5
  let es_toolkit = require("es-toolkit");
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-Beod6tzO.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-BP3vdBpb.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Beod6tzO.mjs";
6
- import { r as fetchKey, s as validateCryptoKey } from "./key-BJrp524f.mjs";
4
+ import { n as version, t as name } from "./deno-dxdvl2XZ.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BP3vdBpb.mjs";
6
+ import { r as fetchKey, s as validateCryptoKey } from "./key-DYfmjnmp.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
4
+ import { n as version, t as name } from "./deno-dxdvl2XZ.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts