@fedify/fedify 2.1.27 → 2.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-WyYSTDDX.mjs} +2 -20
  2. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-D4DNWrux.mjs} +19 -1
  3. package/dist/{builder-CKQkP3a2.mjs → builder-Mg2G3qj8.mjs} +3 -3
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{deno-BR2CGENT.mjs → deno-BiYv5RT5.mjs} +1 -1
  6. package/dist/{docloader-BDHKVMoV.mjs → docloader-DrxXBFL8.mjs} +4 -2
  7. package/dist/federation/builder.test.mjs +3 -3
  8. package/dist/federation/collection.test.mjs +1 -1
  9. package/dist/federation/handler.test.mjs +262 -9
  10. package/dist/federation/idempotency.test.mjs +3 -3
  11. package/dist/federation/inbox.test.mjs +25 -2
  12. package/dist/federation/kv.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +128 -10
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.js +1 -1
  16. package/dist/federation/mq.test.mjs +2 -2
  17. package/dist/federation/negotiation.test.mjs +2 -2
  18. package/dist/federation/retry.test.mjs +1 -1
  19. package/dist/federation/router.test.mjs +2 -3
  20. package/dist/federation/send.test.mjs +7 -6
  21. package/dist/federation/temporal.test.mjs +3 -3
  22. package/dist/federation/webfinger.test.mjs +2 -2
  23. package/dist/{http-DQVY5EMT.js → http-B3rSb2GQ.js} +104 -10
  24. package/dist/{http-QzHm1vsW.mjs → http-C1ga9WQu.mjs} +2 -2
  25. package/dist/{http-CsQRvInz.cjs → http-Cginz7L0.cjs} +103 -9
  26. package/dist/{inbox-DEXoNgGa.mjs → inbox-BWzL0xMU.mjs} +23 -10
  27. package/dist/{key-CDIMbgsc.mjs → key-O-ATwIGS.mjs} +104 -10
  28. package/dist/{kv-cache-cTsPZ49Z.js → kv-cache-8Kg2CHiD.js} +3 -1
  29. package/dist/{kv-cache-FfmHQljR.cjs → kv-cache-BdylAE9w.cjs} +3 -1
  30. package/dist/{ld-BpkaBh2H.mjs → ld-C-fCGM-z.mjs} +2 -2
  31. package/dist/{middleware-DwEVn3gK.js → middleware-41MkquTV.js} +95 -25
  32. package/dist/{middleware-DVaturw-.mjs → middleware-C9UCWOqw.mjs} +82 -25
  33. package/dist/{middleware-Bla8jBXk.cjs → middleware-CVRFS7PZ.cjs} +96 -26
  34. package/dist/{middleware-C9vVE-6R.mjs → middleware-CxgFiKI3.mjs} +1 -1
  35. package/dist/{middleware-Ddrg18eD.cjs → middleware-VvrqPwHL.cjs} +1 -1
  36. package/dist/mod.cjs +4 -4
  37. package/dist/mod.js +4 -4
  38. package/dist/nodeinfo/client.test.mjs +1 -1
  39. package/dist/nodeinfo/handler.test.mjs +2 -2
  40. package/dist/nodeinfo/types.test.mjs +2 -2
  41. package/dist/otel/exporter.test.mjs +1 -1
  42. package/dist/{owner-DnOwKsK6.mjs → owner-C8IVASD3.mjs} +2 -2
  43. package/dist/{proof-JfqNWc2E.cjs → proof-BbHMAkPG.cjs} +1 -1
  44. package/dist/{proof-CShmVLWt.mjs → proof-CUdcBgvg.mjs} +2 -2
  45. package/dist/{proof-uEay4EIq.js → proof-cGfRPTiV.js} +1 -1
  46. package/dist/{send-DCg0WblG.mjs → send-BoV4dyVO.mjs} +2 -2
  47. package/dist/sig/http.test.mjs +5 -5
  48. package/dist/sig/key.test.mjs +200 -6
  49. package/dist/sig/ld.test.mjs +4 -4
  50. package/dist/sig/mod.cjs +2 -2
  51. package/dist/sig/mod.js +2 -2
  52. package/dist/sig/owner.test.mjs +3 -3
  53. package/dist/sig/proof.test.mjs +3 -3
  54. package/dist/{std__assert-BUOJXi-Z.mjs → std__assert-DflMZha5.mjs} +2 -2
  55. package/dist/{temporal-C96jXvaM.mjs → temporal-BJlnku_d.mjs} +1 -1
  56. package/dist/utils/docloader.test.mjs +114 -39
  57. package/dist/utils/mod.cjs +1 -1
  58. package/dist/utils/mod.js +1 -1
  59. package/package.json +6 -6
  60. /package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-BkxW5QJI.mjs} +0 -0
@@ -1,12 +1,70 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BR2CGENT.mjs";
4
+ import { n as version, t as name } from "./deno-BiYv5RT5.mjs";
5
5
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
- import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
7
+ import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  //#region src/sig/key.ts
10
+ /** Classifies decoding failures without hiding unexpected loader errors. */
11
+ function classifyKeyJsonLdError(error, seen = /* @__PURE__ */ new Set()) {
12
+ if (!(error instanceof Error) || !error.name.startsWith("jsonld.")) return null;
13
+ if (seen.has(error)) throw error;
14
+ seen.add(error);
15
+ const details = error.details;
16
+ if (typeof details?.code !== "string") return null;
17
+ if (details.code !== "loading remote context failed" || !("cause" in details) && !("cause" in error)) return "invalid";
18
+ const cause = "cause" in details ? details.cause : error.cause;
19
+ const nestedFailure = classifyKeyJsonLdError(cause, seen);
20
+ if (nestedFailure != null) return nestedFailure;
21
+ if (cause instanceof SyntaxError) return "invalid";
22
+ if (cause instanceof FetchError || cause instanceof UrlError) return "fetch";
23
+ if (cause instanceof Error && [
24
+ "AbortError",
25
+ "TimeoutError",
26
+ "NetworkError"
27
+ ].includes(cause.name)) return "fetch";
28
+ if (cause instanceof TypeError) {
29
+ const urlCode = cause.code;
30
+ if (cause.name === "InvalidContextReferenceError") return "invalid";
31
+ 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";
32
+ const networkCause = cause.cause;
33
+ const code = cause.code ?? networkCause?.code;
34
+ if (typeof code === "string" && [
35
+ "ECONNREFUSED",
36
+ "ECONNRESET",
37
+ "ECONNABORTED",
38
+ "ETIMEDOUT",
39
+ "EHOSTUNREACH",
40
+ "ENETUNREACH",
41
+ "ENOTFOUND",
42
+ "EAI_AGAIN",
43
+ "UND_ERR_CONNECT_TIMEOUT",
44
+ "UND_ERR_HEADERS_TIMEOUT",
45
+ "UND_ERR_BODY_TIMEOUT",
46
+ "UND_ERR_SOCKET"
47
+ ].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";
48
+ }
49
+ throw cause;
50
+ }
51
+ /** Carries a context transport failure through the key decoding boundary. */
52
+ var KeyContextFetchError = class extends Error {
53
+ constructor(error) {
54
+ super("Failed to fetch a key's JSON-LD context.", { cause: error });
55
+ this.error = error;
56
+ }
57
+ };
58
+ function contextFetchError(error) {
59
+ const seen = /* @__PURE__ */ new Set();
60
+ while (error instanceof Error && !seen.has(error)) {
61
+ seen.add(error);
62
+ const details = error.details;
63
+ if (!error.name.startsWith("jsonld.") || details?.code !== "loading remote context failed") break;
64
+ error = "cause" in details ? details.cause : error.cause;
65
+ }
66
+ return new KeyContextFetchError(error);
67
+ }
10
68
  /**
11
69
  * Checks if the given key is valid and supported. No-op if the key is valid,
12
70
  * otherwise throws an error.
@@ -101,6 +159,9 @@ async function importJwk(jwk, type) {
101
159
  * @internal
102
160
  */
103
161
  async function fetchActorDocument(actorId, options = {}) {
162
+ return await fetchActorDocumentInternal(actorId, options, false);
163
+ }
164
+ async function fetchActorDocumentInternal(actorId, options, propagateContextFetchErrors) {
104
165
  const logger = getLogger([
105
166
  "fedify",
106
167
  "sig",
@@ -131,9 +192,12 @@ async function fetchActorDocument(actorId, options = {}) {
131
192
  baseUrl: documentUrl
132
193
  });
133
194
  } catch (error) {
134
- if (!(error instanceof TypeError)) throw error;
135
- logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
195
+ const failure = classifyKeyJsonLdError(error);
196
+ if (failure === "fetch" && propagateContextFetchErrors) throw contextFetchError(error);
197
+ if (failure == null && !(error instanceof TypeError)) throw error;
198
+ logger.debug("Failed to decode the actor document at {documentUrl}: {error}", {
136
199
  documentUrl: documentUrl.href,
200
+ failure: failure ?? "invalid",
137
201
  error
138
202
  });
139
203
  return null;
@@ -172,6 +236,9 @@ async function fetchActorDocument(actorId, options = {}) {
172
236
  * @internal
173
237
  */
174
238
  async function verifyKeyOwnership(key, options = {}) {
239
+ return await verifyKeyOwnershipInternal(key, options, false);
240
+ }
241
+ async function verifyKeyOwnershipInternal(key, options, propagateContextFetchErrors) {
175
242
  const logger = getLogger([
176
243
  "fedify",
177
244
  "sig",
@@ -181,7 +248,7 @@ async function verifyKeyOwnership(key, options = {}) {
181
248
  if (keyId == null) return null;
182
249
  const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
183
250
  if (claimedOwnerId == null) return null;
184
- const owner = await fetchActorDocument(claimedOwnerId, options);
251
+ const owner = await fetchActorDocumentInternal(claimedOwnerId, options, propagateContextFetchErrors);
185
252
  if (owner == null) {
186
253
  logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
187
254
  keyId: keyId.href,
@@ -333,6 +400,21 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
333
400
  tracerProvider
334
401
  });
335
402
  } catch (e) {
403
+ const failure = classifyKeyJsonLdError(e);
404
+ if (failure === "fetch") throw contextFetchError(e);
405
+ if (failure != null) {
406
+ logger.debug("Failed to decode key {keyId}: {error}", {
407
+ keyId,
408
+ failure,
409
+ error: e
410
+ });
411
+ await keyCache?.set(cacheKey, null);
412
+ await clearFetchErrorMetadata(cacheKey, keyCache);
413
+ return {
414
+ key: null,
415
+ cached: false
416
+ };
417
+ }
336
418
  if (!(e instanceof TypeError)) throw e;
337
419
  try {
338
420
  object = await cls.fromJsonLd(document, {
@@ -341,8 +423,14 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
341
423
  tracerProvider
342
424
  });
343
425
  } catch (e) {
344
- if (e instanceof TypeError) {
345
- logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
426
+ const failure = classifyKeyJsonLdError(e);
427
+ if (failure === "fetch") throw contextFetchError(e);
428
+ if (failure != null || e instanceof TypeError) {
429
+ logger.debug("Failed to decode key {keyId}: {error}", {
430
+ keyId,
431
+ failure: failure ?? "invalid",
432
+ error: e
433
+ });
346
434
  await keyCache?.set(cacheKey, null);
347
435
  await clearFetchErrorMetadata(cacheKey, keyCache);
348
436
  return {
@@ -434,16 +522,17 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
434
522
  const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
435
523
  if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
436
524
  else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
437
- if (await verifyKeyOwnership(key, {
525
+ if (await verifyKeyOwnershipInternal(key, {
438
526
  documentLoader,
439
527
  contextLoader,
440
528
  tracerProvider
441
- }) == null) {
529
+ }, true) == null) {
442
530
  logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
443
531
  keyId,
444
532
  claimedOwnerId: claimedOwnerId.href
445
533
  });
446
534
  await keyCache?.set(cacheKey, null);
535
+ await clearFetchErrorMetadata(cacheKey, keyCache);
447
536
  return {
448
537
  key: null,
449
538
  cached: false
@@ -481,7 +570,12 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
481
570
  } catch (error) {
482
571
  return await onFetchError(error, cacheKey, keyId, keyCache, logger);
483
572
  }
484
- return await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
573
+ try {
574
+ return await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
575
+ } catch (error) {
576
+ if (!(error instanceof KeyContextFetchError)) throw error;
577
+ return await onFetchError(error.error, cacheKey, keyId, keyCache, logger);
578
+ }
485
579
  }
486
580
  async function fetchKeyInternal(keyId, cls, options = {}) {
487
581
  return await fetchKeyWithResult(typeof keyId === "string" ? new URL(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { f as validateCryptoKey, t as doubleKnock } from "./http-DQVY5EMT.js";
3
+ import { f as validateCryptoKey, t as doubleKnock } from "./http-B3rSb2GQ.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
6
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -56,6 +56,8 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAge
56
56
  });
57
57
  }
58
58
  async function validateUrl(url) {
59
+ const protocol = new URL(url).protocol;
60
+ if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
59
61
  if (!allowPrivateAddress) try {
60
62
  await validatePublicUrl(url);
61
63
  } catch (error) {
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-CsQRvInz.cjs");
4
+ const require_http = require("./http-Cginz7L0.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
@@ -57,6 +57,8 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAge
57
57
  });
58
58
  }
59
59
  async function validateUrl(url) {
60
+ const protocol = new URL(url).protocol;
61
+ if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
60
62
  if (!allowPrivateAddress) try {
61
63
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
62
64
  } catch (error) {
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BR2CGENT.mjs";
5
- import { r as fetchKey, s as validateCryptoKey } from "./key-CDIMbgsc.mjs";
4
+ import { n as version, t as name } from "./deno-BiYv5RT5.mjs";
5
+ import { r as fetchKey, s as validateCryptoKey } from "./key-O-ATwIGS.mjs";
6
6
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { a as verifyRequestDetailed, d as importJwk, f as validateCryptoKey, i as verifyRequest, m as formatAcceptSignature, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as name, y as version } from "./http-DQVY5EMT.js";
5
+ import { a as verifyRequestDetailed, d as importJwk, f as validateCryptoKey, i as verifyRequest, m as formatAcceptSignature, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as name, y as version } from "./http-B3rSb2GQ.js";
6
6
  import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-miAYfiGO.js";
7
- import { _ as signJsonLd, a as doesActorOwnKey, c as assertSafeJsonLd, f as detachSignature, g as isInvalidUrlTypeError, h as isClearlyMalformedContextReference, m as hasSignature, n as signObject, o as getKeyOwner, p as getNormalizationContextLoader, r as verifyObject, s as InvalidContextReferenceError, u as compactJsonLd, v as verifyCompactJsonLd, x as wrapContextLoaderForJsonLd } from "./proof-uEay4EIq.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-cTsPZ49Z.js";
7
+ import { _ as signJsonLd, a as doesActorOwnKey, c as assertSafeJsonLd, f as detachSignature, g as isInvalidUrlTypeError, h as isClearlyMalformedContextReference, m as hasSignature, n as signObject, o as getKeyOwner, p as getNormalizationContextLoader, r as verifyObject, s as InvalidContextReferenceError, u as compactJsonLd, v as verifyCompactJsonLd, x as wrapContextLoaderForJsonLd } from "./proof-cGfRPTiV.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-8Kg2CHiD.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -52,12 +52,33 @@ var InboxListenerSet = class InboxListenerSet {
52
52
  return this.dispatchWithClass(activity)?.listener ?? null;
53
53
  }
54
54
  };
55
+ /**
56
+ * Checks whether an activity's ID and all of its actor IDs have the same
57
+ * non-opaque origin. Activities without an ID have no origin to authenticate.
58
+ *
59
+ * The caller is responsible for rejecting a missing primary actor ID before
60
+ * relying on this check.
61
+ * @internal
62
+ */
63
+ function actorsHaveSameOrigin(activity) {
64
+ if (activity.id == null) return true;
65
+ const origin = activity.id.origin;
66
+ return origin !== "null" && activity.actorIds.every((actor) => actor.origin === origin);
67
+ }
55
68
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
56
69
  const logger = getLogger([
57
70
  "fedify",
58
71
  "federation",
59
72
  "inbox"
60
73
  ]);
74
+ if (activity.actorId == null) {
75
+ logger.error("Missing actor.", { activity: json });
76
+ span.setStatus({
77
+ code: SpanStatusCode.ERROR,
78
+ message: "Missing actor."
79
+ });
80
+ return "missingActor";
81
+ }
61
82
  let cacheKey = null;
62
83
  if (activity.id != null) {
63
84
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
@@ -92,14 +113,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
92
113
  return "alreadyProcessed";
93
114
  }
94
115
  }
95
- if (activity.actorId == null) {
96
- logger.error("Missing actor.", { activity: json });
97
- span.setStatus({
98
- code: SpanStatusCode.ERROR,
99
- message: "Missing actor."
100
- });
101
- return "missingActor";
102
- }
103
116
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
104
117
  if (queue != null) {
105
118
  const carrier = {};
@@ -1798,6 +1811,36 @@ async function handleInboxInternal(request, parameters, span) {
1798
1811
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1799
1812
  }
1800
1813
  }
1814
+ if (activity.actorId == null) {
1815
+ logger.error("Missing actor.", {
1816
+ activity: json,
1817
+ recipient
1818
+ });
1819
+ span.setStatus({
1820
+ code: SpanStatusCode.ERROR,
1821
+ message: "Missing actor."
1822
+ });
1823
+ return new Response("Missing actor.", {
1824
+ status: 400,
1825
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1826
+ });
1827
+ }
1828
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
1829
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
1830
+ activity: json,
1831
+ recipient,
1832
+ activityId: activity.id.href,
1833
+ actorIds: activity.actorIds.map((actor) => actor.href)
1834
+ });
1835
+ span.setStatus({
1836
+ code: SpanStatusCode.ERROR,
1837
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
1838
+ });
1839
+ return new Response("The activity and its actors do not have the same origin.", {
1840
+ status: 401,
1841
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1842
+ });
1843
+ }
1801
1844
  const routeResult = await routeActivity({
1802
1845
  context: ctx,
1803
1846
  json,
@@ -3316,6 +3359,33 @@ var FederationImpl = class extends FederationBuilderImpl {
3316
3359
  activity = await Activity.fromJsonLd(parseInput, parseContext);
3317
3360
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
3318
3361
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3362
+ if (activity.actorId == null) {
3363
+ logger.error("Missing actor in queued incoming activity.", {
3364
+ activityId: activity.id?.href,
3365
+ activity: message.activity,
3366
+ recipient: message.identifier
3367
+ });
3368
+ span.setStatus({
3369
+ code: SpanStatusCode.ERROR,
3370
+ message: "Missing actor."
3371
+ });
3372
+ span.end();
3373
+ return;
3374
+ }
3375
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
3376
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
3377
+ activityId: activity.id.href,
3378
+ actorIds: activity.actorIds.map((actor) => actor.href),
3379
+ activity: message.activity,
3380
+ recipient: message.identifier
3381
+ });
3382
+ span.setStatus({
3383
+ code: SpanStatusCode.ERROR,
3384
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
3385
+ });
3386
+ span.end();
3387
+ return;
3388
+ }
3319
3389
  cacheKey = activity.id == null ? null : [
3320
3390
  ...this.kvPrefixes.activityIdempotence,
3321
3391
  context.origin,
@@ -4428,20 +4498,6 @@ var ContextImpl = class ContextImpl {
4428
4498
  activity: await fetched.toJsonLd({ contextLoader })
4429
4499
  });
4430
4500
  return false;
4431
- } else if (fetched.actorIds.length < 1) {
4432
- logger.debug("Fetched activity object is missing an actor; unable to verify.", {
4433
- recipient,
4434
- activity: await fetched.toJsonLd({ contextLoader })
4435
- });
4436
- return false;
4437
- }
4438
- const activityId = fetched.id;
4439
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
4440
- logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
4441
- recipient,
4442
- activity: await fetched.toJsonLd({ contextLoader })
4443
- });
4444
- return false;
4445
4501
  }
4446
4502
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
4447
4503
  activity = fetched;
@@ -4450,6 +4506,20 @@ var ContextImpl = class ContextImpl {
4450
4506
  recipient,
4451
4507
  activity: json
4452
4508
  });
4509
+ if (activity.actorId == null) {
4510
+ logger.debug("Activity is missing an actor; unable to verify.", {
4511
+ recipient,
4512
+ activity: json
4513
+ });
4514
+ return false;
4515
+ }
4516
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
4517
+ logger.debug("Activity has actors from different origins; unable to verify.", {
4518
+ recipient,
4519
+ activity: json
4520
+ });
4521
+ return false;
4522
+ }
4453
4523
  const routeResult = await routeActivity({
4454
4524
  context: this,
4455
4525
  json,
@@ -2,25 +2,25 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-BR2CGENT.mjs";
5
+ import { n as version, t as name } from "./deno-BiYv5RT5.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-BavzedPB.mjs";
7
- import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-CDIMbgsc.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-QzHm1vsW.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-BDHKVMoV.mjs";
7
+ import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-O-ATwIGS.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-C1ga9WQu.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-DrxXBFL8.mjs";
10
10
  import { n as kvCache } from "./kv-cache-DqEKe5E0.mjs";
11
11
  import { t as KvKeyCache } from "./keycache-RFlZObU3.mjs";
12
- import { a as compactJsonLd, c as getNormalizationContextLoader, d as isInvalidUrlTypeError, f as signJsonLd, g as wrapContextLoaderForJsonLd, l as hasSignature, p as verifyCompactJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as isClearlyMalformedContextReference } from "./ld-BpkaBh2H.mjs";
13
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-DnOwKsK6.mjs";
14
- import { n as signObject, r as verifyObject } from "./proof-CShmVLWt.mjs";
12
+ import { a as compactJsonLd, c as getNormalizationContextLoader, d as isInvalidUrlTypeError, f as signJsonLd, g as wrapContextLoaderForJsonLd, l as hasSignature, p as verifyCompactJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as isClearlyMalformedContextReference } from "./ld-C-fCGM-z.mjs";
13
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-C8IVASD3.mjs";
14
+ import { n as signObject, r as verifyObject } from "./proof-CUdcBgvg.mjs";
15
15
  import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-CjDMsn4a.mjs";
16
16
  import { t as nodeInfoToJson } from "./types-DCP0WLdt.mjs";
17
- import { n as routeActivity } from "./inbox-DEXoNgGa.mjs";
18
- import { t as FederationBuilderImpl } from "./builder-CKQkP3a2.mjs";
17
+ import { n as actorsHaveSameOrigin, r as routeActivity } from "./inbox-BWzL0xMU.mjs";
18
+ import { t as FederationBuilderImpl } from "./builder-Mg2G3qj8.mjs";
19
19
  import { t as buildCollectionSynchronizationHeader } from "./collection-BD6-SZ6O.mjs";
20
20
  import { t as acceptsJsonLd } from "./negotiation-BehA2uul.mjs";
21
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-C96jXvaM.mjs";
21
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-BJlnku_d.mjs";
22
22
  import { t as createExponentialBackoffPolicy } from "./retry-B_E3V_Dx.mjs";
23
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-DCg0WblG.mjs";
23
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-BoV4dyVO.mjs";
24
24
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
25
25
  import { lookupWebFinger } from "@fedify/webfinger";
26
26
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -812,6 +812,36 @@ async function handleInboxInternal(request, parameters, span) {
812
812
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
813
813
  }
814
814
  }
815
+ if (activity.actorId == null) {
816
+ logger.error("Missing actor.", {
817
+ activity: json,
818
+ recipient
819
+ });
820
+ span.setStatus({
821
+ code: SpanStatusCode.ERROR,
822
+ message: "Missing actor."
823
+ });
824
+ return new Response("Missing actor.", {
825
+ status: 400,
826
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
827
+ });
828
+ }
829
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
830
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
831
+ activity: json,
832
+ recipient,
833
+ activityId: activity.id.href,
834
+ actorIds: activity.actorIds.map((actor) => actor.href)
835
+ });
836
+ span.setStatus({
837
+ code: SpanStatusCode.ERROR,
838
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
839
+ });
840
+ return new Response("The activity and its actors do not have the same origin.", {
841
+ status: 401,
842
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
843
+ });
844
+ }
815
845
  const routeResult = await routeActivity({
816
846
  context: ctx,
817
847
  json,
@@ -2007,6 +2037,33 @@ var FederationImpl = class extends FederationBuilderImpl {
2007
2037
  activity = await Activity.fromJsonLd(parseInput, parseContext);
2008
2038
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
2009
2039
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2040
+ if (activity.actorId == null) {
2041
+ logger.error("Missing actor in queued incoming activity.", {
2042
+ activityId: activity.id?.href,
2043
+ activity: message.activity,
2044
+ recipient: message.identifier
2045
+ });
2046
+ span.setStatus({
2047
+ code: SpanStatusCode.ERROR,
2048
+ message: "Missing actor."
2049
+ });
2050
+ span.end();
2051
+ return;
2052
+ }
2053
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
2054
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
2055
+ activityId: activity.id.href,
2056
+ actorIds: activity.actorIds.map((actor) => actor.href),
2057
+ activity: message.activity,
2058
+ recipient: message.identifier
2059
+ });
2060
+ span.setStatus({
2061
+ code: SpanStatusCode.ERROR,
2062
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
2063
+ });
2064
+ span.end();
2065
+ return;
2066
+ }
2010
2067
  cacheKey = activity.id == null ? null : [
2011
2068
  ...this.kvPrefixes.activityIdempotence,
2012
2069
  context.origin,
@@ -3119,20 +3176,6 @@ var ContextImpl = class ContextImpl {
3119
3176
  activity: await fetched.toJsonLd({ contextLoader })
3120
3177
  });
3121
3178
  return false;
3122
- } else if (fetched.actorIds.length < 1) {
3123
- logger.debug("Fetched activity object is missing an actor; unable to verify.", {
3124
- recipient,
3125
- activity: await fetched.toJsonLd({ contextLoader })
3126
- });
3127
- return false;
3128
- }
3129
- const activityId = fetched.id;
3130
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
3131
- logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
3132
- recipient,
3133
- activity: await fetched.toJsonLd({ contextLoader })
3134
- });
3135
- return false;
3136
3179
  }
3137
3180
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
3138
3181
  activity = fetched;
@@ -3141,6 +3184,20 @@ var ContextImpl = class ContextImpl {
3141
3184
  recipient,
3142
3185
  activity: json
3143
3186
  });
3187
+ if (activity.actorId == null) {
3188
+ logger.debug("Activity is missing an actor; unable to verify.", {
3189
+ recipient,
3190
+ activity: json
3191
+ });
3192
+ return false;
3193
+ }
3194
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
3195
+ logger.debug("Activity has actors from different origins; unable to verify.", {
3196
+ recipient,
3197
+ activity: json
3198
+ });
3199
+ return false;
3200
+ }
3144
3201
  const routeResult = await routeActivity({
3145
3202
  context: this,
3146
3203
  json,