@fedify/fedify 2.3.12 → 2.3.14

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 (69) 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-B6ixdT3y.mjs → builder-C6yXExXl.mjs} +2 -2
  4. package/dist/compat/transformers.test.mjs +2 -2
  5. package/dist/{deno-D4dcoVfF.mjs → deno-y3L4Oa94.mjs} +1 -1
  6. package/dist/{docloader-NOKfswJV.mjs → docloader-yuyzZC9a.mjs} +6 -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 +19 -8
  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-HjidyDfF.mjs → http-BCSf1KIY.mjs} +3 -3
  27. package/dist/{http-D_Xrr6kp.cjs → http-D77Wjrcg.cjs} +17 -3
  28. package/dist/{http-DkyUBs_T.js → http-DNM9KyIO.js} +17 -3
  29. package/dist/inbox-NObX26c9.mjs +180 -0
  30. package/dist/{key-Dg-NXm4K.mjs → key-APURZ0ky.mjs} +18 -4
  31. package/dist/{keycache-Dkc_sflV.mjs → keycache-BimEQucx.mjs} +1 -1
  32. package/dist/{kv-cache-BwnC6JGt.cjs → kv-cache-Bg4utAuv.cjs} +5 -1
  33. package/dist/{kv-cache-BpVwvcSI.mjs → kv-cache-C69dzgQi.mjs} +1 -1
  34. package/dist/{kv-cache-BhGeZk_o.js → kv-cache-LBCwHNLu.js} +5 -1
  35. package/dist/{ld-C1wveXXf.mjs → ld-BoLqbDaL.mjs} +3 -3
  36. package/dist/{metrics-cIaMxG0v.mjs → metrics-DBSl8wEr.mjs} +1 -1
  37. package/dist/{middleware-DhOFTXmo.cjs → middleware-BQUQ0pPl.cjs} +107 -35
  38. package/dist/{middleware-CnrzVu9i.js → middleware-CRd1JmuL.js} +107 -35
  39. package/dist/{middleware-I-XlrRVl.mjs → middleware-CZ8tI1qL.mjs} +97 -195
  40. package/dist/{middleware-VxPpU0h_.mjs → middleware-DAqo2eOY.mjs} +1 -1
  41. package/dist/mod.cjs +4 -4
  42. package/dist/mod.js +4 -4
  43. package/dist/nodeinfo/client.test.mjs +2 -2
  44. package/dist/nodeinfo/handler.test.mjs +2 -2
  45. package/dist/nodeinfo/types.test.mjs +2 -2
  46. package/dist/otel/exporter.test.mjs +1 -1
  47. package/dist/{owner-CFKfE85G.mjs → owner-DjKvngeb.mjs} +2 -2
  48. package/dist/{proof-DBxu6g2K.cjs → proof-CWTnYhjS.cjs} +1 -1
  49. package/dist/{proof-DtpGEj0u.mjs → proof-D2oFOdNA.mjs} +3 -3
  50. package/dist/{proof-HU__qtUD.js → proof-s9sy44kx.js} +1 -1
  51. package/dist/{send-CwMea5r6.mjs → send-xDK5UnL-.mjs} +3 -3
  52. package/dist/sig/http.test.mjs +7 -7
  53. package/dist/sig/key.test.mjs +200 -6
  54. package/dist/sig/ld.test.mjs +50 -5
  55. package/dist/sig/mod.cjs +2 -2
  56. package/dist/sig/mod.js +2 -2
  57. package/dist/sig/owner.test.mjs +49 -4
  58. package/dist/sig/proof.test.mjs +56 -4
  59. package/dist/{std__assert-BvGuO4xO.mjs → std__assert-BjJn2daJ.mjs} +2 -2
  60. package/dist/{temporal-ClSXyHyu.mjs → temporal-C6icc5n9.mjs} +1 -1
  61. package/dist/utils/docloader.test.mjs +178 -39
  62. package/dist/utils/kv-cache.test.mjs +1 -1
  63. package/dist/utils/mod.cjs +1 -1
  64. package/dist/utils/mod.js +1 -1
  65. package/package.json +6 -6
  66. /package/dist/{assert-jSdZMYam.mjs → assert-CEO06XCw.mjs} +0 -0
  67. /package/dist/{circuit-breaker-test-utils-Dm5MM6l9.mjs → circuit-breaker-test-utils-CAdwjgMi.mjs} +0 -0
  68. /package/dist/{negotiation-CDW-_gUU.mjs → negotiation-DDstyBvc.mjs} +0 -0
  69. /package/dist/{retry-_VvV0h9f.mjs → retry-CXg_MBI-.mjs} +0 -0
@@ -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-D4dcoVfF.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-cIaMxG0v.mjs";
4
+ import { n as version, t as name } from "./deno-y3L4Oa94.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-DBSl8wEr.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -148,16 +148,23 @@ async function importJwk(jwk, type) {
148
148
  /**
149
149
  * Fetches the document that the given actor URI dereferences to, and returns
150
150
  * the actor only if the host that served the document is authoritative for
151
- * the id the document claims.
151
+ * the id the document claims, and that id is the actor that was asked for.
152
152
  *
153
153
  * Only the origin that serves an actor id can speak for it. Without that
154
154
  * rule any host could serve a document describing somebody else's actor—and
155
155
  * listing its own keys as that actor's.
156
156
  *
157
+ * The returned actor always has exactly the requested `actorId` as its `id`.
158
+ * A document loader follows redirects and alternate document links, so the
159
+ * document an actor id leads to may describe a different actor altogether,
160
+ * on the same origin or on another; accepting it would attribute that other
161
+ * actor's keys to the requested one. See GHSA-8863-8468-7hq7.
162
+ *
157
163
  * @param actorId The URI of the actor to fetch.
158
164
  * @param options Options for fetching the document.
159
165
  * @returns The actor, or `null` if the document cannot be fetched, is not an
160
- * actor, or belongs to another origin.
166
+ * actor, belongs to another origin, or describes an actor other
167
+ * than the requested one.
161
168
  * @internal
162
169
  */
163
170
  async function fetchActorDocument(actorId, options = {}) {
@@ -212,6 +219,13 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
212
219
  });
213
220
  return null;
214
221
  }
222
+ if (object.id.href !== actorId.href) {
223
+ logger.debug("The document fetched for the actor {actorId} is the actor {documentActorId} instead; refusing to treat it as the requested actor's document.", {
224
+ actorId: actorId.href,
225
+ documentActorId: object.id.href
226
+ });
227
+ return null;
228
+ }
215
229
  return object;
216
230
  }
217
231
  /**
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { CryptographicKey, Multikey } from "@fedify/vocab";
5
5
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
6
6
  //#region src/federation/keycache.ts
7
- const KEY_CACHE_GENERATION = "2";
7
+ const KEY_CACHE_GENERATION = "3";
8
8
  var KvKeyCache = class {
9
9
  kv;
10
10
  prefix;
@@ -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-D_Xrr6kp.cjs");
4
+ const require_http = require("./http-D77Wjrcg.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
7
7
  let es_toolkit = require("es-toolkit");
@@ -58,6 +58,10 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
58
58
  });
59
59
  }
60
60
  async function validateUrl(url) {
61
+ const parsedUrl = new URL(url);
62
+ const protocol = parsedUrl.protocol;
63
+ if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
64
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new _fedify_vocab_runtime.UrlError("URLs with credentials are not allowed");
61
65
  if (!allowPrivateAddress) try {
62
66
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
63
67
  } catch (error) {
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-cIaMxG0v.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-DBSl8wEr.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,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-DkyUBs_T.js";
3
+ import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-DNM9KyIO.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
@@ -57,6 +57,10 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
57
57
  });
58
58
  }
59
59
  async function validateUrl(url) {
60
+ const parsedUrl = new URL(url);
61
+ const protocol = parsedUrl.protocol;
62
+ if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
63
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
60
64
  if (!allowPrivateAddress) try {
61
65
  await validatePublicUrl(url);
62
66
  } catch (error) {
@@ -1,9 +1,9 @@
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-D4dcoVfF.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-cIaMxG0v.mjs";
6
- import { r as fetchKey, s as validateCryptoKey } from "./key-Dg-NXm4K.mjs";
4
+ import { n as version, t as name } from "./deno-y3L4Oa94.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DBSl8wEr.mjs";
6
+ import { r as fetchKey, s as validateCryptoKey } from "./key-APURZ0ky.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 "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-D4dcoVfF.mjs";
4
+ import { n as version, t as name } from "./deno-y3L4Oa94.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-D_Xrr6kp.cjs");
5
+ const require_http = require("./http-D77Wjrcg.cjs");
6
6
  const require_types = require("./types-DRPjmHVV.cjs");
7
- const require_proof = require("./proof-DBxu6g2K.cjs");
8
- const require_kv_cache = require("./kv-cache-BwnC6JGt.cjs");
7
+ const require_proof = require("./proof-CWTnYhjS.cjs");
8
+ const require_kv_cache = require("./kv-cache-Bg4utAuv.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_uri_template = require("@fedify/uri-template");
11
11
  let _fedify_vocab = require("@fedify/vocab");
@@ -1264,12 +1264,34 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
1264
1264
  }
1265
1265
  //#endregion
1266
1266
  //#region src/federation/inbox.ts
1267
+ /**
1268
+ * Checks whether an activity's ID and all of its actor IDs have the same
1269
+ * non-opaque origin. Activities without an ID have no origin to authenticate.
1270
+ *
1271
+ * The caller is responsible for rejecting a missing primary actor ID before
1272
+ * relying on this check.
1273
+ * @internal
1274
+ */
1275
+ function actorsHaveSameOrigin(activity) {
1276
+ if (activity.id == null) return true;
1277
+ const origin = activity.id.origin;
1278
+ return origin !== "null" && activity.actorIds.every((actor) => actor.origin === origin);
1279
+ }
1267
1280
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
1268
1281
  const logger = (0, _logtape_logtape.getLogger)([
1269
1282
  "fedify",
1270
1283
  "federation",
1271
1284
  "inbox"
1272
1285
  ]);
1286
+ if (activity.actorId == null) {
1287
+ logger.error("Missing actor.", { activity: json });
1288
+ span.setStatus({
1289
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1290
+ message: "Missing actor."
1291
+ });
1292
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
1293
+ return "missingActor";
1294
+ }
1273
1295
  let cacheKey = null;
1274
1296
  if (activity.id != null) {
1275
1297
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href);
@@ -1305,15 +1327,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1305
1327
  return "alreadyProcessed";
1306
1328
  }
1307
1329
  }
1308
- if (activity.actorId == null) {
1309
- logger.error("Missing actor.", { activity: json });
1310
- span.setStatus({
1311
- code: _opentelemetry_api.SpanStatusCode.ERROR,
1312
- message: "Missing actor."
1313
- });
1314
- require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
1315
- return "missingActor";
1316
- }
1317
1330
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
1318
1331
  if (queue != null) {
1319
1332
  const carrier = {};
@@ -1422,7 +1435,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1422
1435
  }
1423
1436
  //#endregion
1424
1437
  //#region src/federation/keycache.ts
1425
- const KEY_CACHE_GENERATION = "2";
1438
+ const KEY_CACHE_GENERATION = "3";
1426
1439
  var KvKeyCache = class {
1427
1440
  kv;
1428
1441
  prefix;
@@ -1848,13 +1861,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
1848
1861
  * @param parameters The parameters for handling the collection.
1849
1862
  * @returns A promise that resolves to an HTTP response.
1850
1863
  */
1851
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
1852
- const spanName = name$2.trim().replace(/\s+/g, "_");
1864
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
1865
+ const spanName = name$1.trim().replace(/\s+/g, "_");
1853
1866
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
1854
1867
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
1855
1868
  const cursor = new URL(request.url).searchParams.get("cursor");
1856
1869
  const metricBase = {
1857
- kind: getCollectionMetricKind(name$2),
1870
+ kind: getCollectionMetricKind(name$1),
1858
1871
  page: cursor != null,
1859
1872
  dispatcher: "built_in"
1860
1873
  };
@@ -1895,7 +1908,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1895
1908
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1896
1909
  return await onNotFound(request);
1897
1910
  }
1898
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
1911
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
1899
1912
  itemCount = items.length;
1900
1913
  span.setAttribute("fedify.collection.items", itemCount);
1901
1914
  return items;
@@ -1935,7 +1948,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1935
1948
  } else {
1936
1949
  const uri = new URL(baseUri);
1937
1950
  uri.searchParams.set("cursor", cursor);
1938
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
1951
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1939
1952
  kind: _opentelemetry_api.SpanKind.SERVER,
1940
1953
  attributes: {
1941
1954
  "activitypub.collection.id": uri.href,
@@ -1951,7 +1964,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1951
1964
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1952
1965
  return await onNotFound(request);
1953
1966
  }
1954
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
1967
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
1955
1968
  itemCount = items.length;
1956
1969
  span.setAttribute("fedify.collection.items", itemCount);
1957
1970
  return {
@@ -2660,6 +2673,36 @@ async function handleInboxInternal(request, parameters, span) {
2660
2673
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
2661
2674
  }
2662
2675
  }
2676
+ if (activity.actorId == null) {
2677
+ logger.error("Missing actor.", {
2678
+ activity: json,
2679
+ recipient
2680
+ });
2681
+ span.setStatus({
2682
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2683
+ message: "Missing actor."
2684
+ });
2685
+ return new Response("Missing actor.", {
2686
+ status: 400,
2687
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2688
+ });
2689
+ }
2690
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
2691
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
2692
+ activity: json,
2693
+ recipient,
2694
+ activityId: activity.id.href,
2695
+ actorIds: activity.actorIds.map((actor) => actor.href)
2696
+ });
2697
+ span.setStatus({
2698
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2699
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
2700
+ });
2701
+ return new Response("The activity and its actors do not have the same origin.", {
2702
+ status: 401,
2703
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2704
+ });
2705
+ }
2663
2706
  const routeResult = await routeActivity({
2664
2707
  context: ctx,
2665
2708
  json,
@@ -2827,8 +2870,8 @@ var CustomCollectionHandler = class {
2827
2870
  * @param CollectionPage The CollectionPage constructor.
2828
2871
  * @param filterPredicate Optional filter predicate for items.
2829
2872
  */
2830
- constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
2831
- this.name = name$1;
2873
+ constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
2874
+ this.name = name$2;
2832
2875
  this.values = values;
2833
2876
  this.context = context;
2834
2877
  this.callbacks = callbacks;
@@ -5061,6 +5104,35 @@ var FederationImpl = class extends FederationBuilderImpl {
5061
5104
  span.setAttribute("activitypub.activity.id", activity.id.href);
5062
5105
  listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
5063
5106
  }
5107
+ if (activity.actorId == null) {
5108
+ logger.error("Missing actor in queued incoming activity.", {
5109
+ activityId: activity.id?.href,
5110
+ activity: message.activity,
5111
+ recipient: message.identifier
5112
+ });
5113
+ listenerSpan.setStatus({
5114
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
5115
+ message: "Missing actor."
5116
+ });
5117
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
5118
+ listenerSpan.end();
5119
+ return;
5120
+ }
5121
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
5122
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
5123
+ activityId: activity.id.href,
5124
+ actorIds: activity.actorIds.map((actor) => actor.href),
5125
+ activity: message.activity,
5126
+ recipient: message.identifier
5127
+ });
5128
+ listenerSpan.setStatus({
5129
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
5130
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
5131
+ });
5132
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
5133
+ listenerSpan.end();
5134
+ return;
5135
+ }
5064
5136
  cacheKey = activity.id == null ? null : [
5065
5137
  ...this.kvPrefixes.activityIdempotence,
5066
5138
  context.origin,
@@ -6323,20 +6395,6 @@ var ContextImpl = class ContextImpl {
6323
6395
  activity: await fetched.toJsonLd({ contextLoader })
6324
6396
  });
6325
6397
  return false;
6326
- } else if (fetched.actorIds.length < 1) {
6327
- logger.debug("Fetched activity object is missing an actor; unable to verify.", {
6328
- recipient,
6329
- activity: await fetched.toJsonLd({ contextLoader })
6330
- });
6331
- return false;
6332
- }
6333
- const activityId = fetched.id;
6334
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
6335
- logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
6336
- recipient,
6337
- activity: await fetched.toJsonLd({ contextLoader })
6338
- });
6339
- return false;
6340
6398
  }
6341
6399
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
6342
6400
  activity = fetched;
@@ -6345,6 +6403,20 @@ var ContextImpl = class ContextImpl {
6345
6403
  recipient,
6346
6404
  activity: json
6347
6405
  });
6406
+ if (activity.actorId == null) {
6407
+ logger.debug("Activity is missing an actor; unable to verify.", {
6408
+ recipient,
6409
+ activity: json
6410
+ });
6411
+ return false;
6412
+ }
6413
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
6414
+ logger.debug("Activity has actors from different origins; unable to verify.", {
6415
+ recipient,
6416
+ activity: json
6417
+ });
6418
+ return false;
6419
+ }
6348
6420
  const routeResult = await routeActivity({
6349
6421
  context: this,
6350
6422
  json,
@@ -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 recordWebFingerHandle, C as recordCollectionRequest, D as recordInboxActivity, E as recordFanoutRecipients, I as name, L as version, M as formatAcceptSignature, O as recordOutboxActivity, S as recordCollectionPageItems, _ as instrumentDocumentLoader, a as verifyRequestDetailed, b as recordCircuitBreakerStateChange, d as importJwk, f as validateCryptoKey, g as getRemoteHost, h as getFederationMetrics, i as verifyRequest, j as registerQueueDepthGauge, k as recordOutboxEnqueue, m as getDurationMs, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as isAbortError, w as recordCollectionTotalItems, x as recordCollectionDispatchDuration } from "./http-DkyUBs_T.js";
5
+ import { A as recordWebFingerHandle, C as recordCollectionRequest, D as recordInboxActivity, E as recordFanoutRecipients, I as name, L as version, M as formatAcceptSignature, O as recordOutboxActivity, S as recordCollectionPageItems, _ as instrumentDocumentLoader, a as verifyRequestDetailed, b as recordCircuitBreakerStateChange, d as importJwk, f as validateCryptoKey, g as getRemoteHost, h as getFederationMetrics, i as verifyRequest, j as registerQueueDepthGauge, k as recordOutboxEnqueue, m as getDurationMs, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as isAbortError, w as recordCollectionTotalItems, x as recordCollectionDispatchDuration } from "./http-DNM9KyIO.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 hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-HU__qtUD.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-BhGeZk_o.js";
7
+ import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-s9sy44kx.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-LBCwHNLu.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
11
11
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
@@ -1263,12 +1263,34 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
1263
1263
  }
1264
1264
  //#endregion
1265
1265
  //#region src/federation/inbox.ts
1266
+ /**
1267
+ * Checks whether an activity's ID and all of its actor IDs have the same
1268
+ * non-opaque origin. Activities without an ID have no origin to authenticate.
1269
+ *
1270
+ * The caller is responsible for rejecting a missing primary actor ID before
1271
+ * relying on this check.
1272
+ * @internal
1273
+ */
1274
+ function actorsHaveSameOrigin(activity) {
1275
+ if (activity.id == null) return true;
1276
+ const origin = activity.id.origin;
1277
+ return origin !== "null" && activity.actorIds.every((actor) => actor.origin === origin);
1278
+ }
1266
1279
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
1267
1280
  const logger = getLogger([
1268
1281
  "fedify",
1269
1282
  "federation",
1270
1283
  "inbox"
1271
1284
  ]);
1285
+ if (activity.actorId == null) {
1286
+ logger.error("Missing actor.", { activity: json });
1287
+ span.setStatus({
1288
+ code: SpanStatusCode.ERROR,
1289
+ message: "Missing actor."
1290
+ });
1291
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
1292
+ return "missingActor";
1293
+ }
1272
1294
  let cacheKey = null;
1273
1295
  if (activity.id != null) {
1274
1296
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
@@ -1304,15 +1326,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1304
1326
  return "alreadyProcessed";
1305
1327
  }
1306
1328
  }
1307
- if (activity.actorId == null) {
1308
- logger.error("Missing actor.", { activity: json });
1309
- span.setStatus({
1310
- code: SpanStatusCode.ERROR,
1311
- message: "Missing actor."
1312
- });
1313
- recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
1314
- return "missingActor";
1315
- }
1316
1329
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
1317
1330
  if (queue != null) {
1318
1331
  const carrier = {};
@@ -1421,7 +1434,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1421
1434
  }
1422
1435
  //#endregion
1423
1436
  //#region src/federation/keycache.ts
1424
- const KEY_CACHE_GENERATION = "2";
1437
+ const KEY_CACHE_GENERATION = "3";
1425
1438
  var KvKeyCache = class {
1426
1439
  kv;
1427
1440
  prefix;
@@ -1847,13 +1860,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
1847
1860
  * @param parameters The parameters for handling the collection.
1848
1861
  * @returns A promise that resolves to an HTTP response.
1849
1862
  */
1850
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
1851
- const spanName = name$2.trim().replace(/\s+/g, "_");
1863
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
1864
+ const spanName = name$1.trim().replace(/\s+/g, "_");
1852
1865
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
1853
1866
  const tracer = tracerProvider.getTracer(name, version);
1854
1867
  const cursor = new URL(request.url).searchParams.get("cursor");
1855
1868
  const metricBase = {
1856
- kind: getCollectionMetricKind(name$2),
1869
+ kind: getCollectionMetricKind(name$1),
1857
1870
  page: cursor != null,
1858
1871
  dispatcher: "built_in"
1859
1872
  };
@@ -1894,7 +1907,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1894
1907
  span.setStatus({ code: SpanStatusCode.ERROR });
1895
1908
  return await onNotFound(request);
1896
1909
  }
1897
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
1910
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
1898
1911
  itemCount = items.length;
1899
1912
  span.setAttribute("fedify.collection.items", itemCount);
1900
1913
  return items;
@@ -1934,7 +1947,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1934
1947
  } else {
1935
1948
  const uri = new URL(baseUri);
1936
1949
  uri.searchParams.set("cursor", cursor);
1937
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
1950
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1938
1951
  kind: SpanKind.SERVER,
1939
1952
  attributes: {
1940
1953
  "activitypub.collection.id": uri.href,
@@ -1950,7 +1963,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1950
1963
  span.setStatus({ code: SpanStatusCode.ERROR });
1951
1964
  return await onNotFound(request);
1952
1965
  }
1953
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
1966
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
1954
1967
  itemCount = items.length;
1955
1968
  span.setAttribute("fedify.collection.items", itemCount);
1956
1969
  return {
@@ -2659,6 +2672,36 @@ async function handleInboxInternal(request, parameters, span) {
2659
2672
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
2660
2673
  }
2661
2674
  }
2675
+ if (activity.actorId == null) {
2676
+ logger.error("Missing actor.", {
2677
+ activity: json,
2678
+ recipient
2679
+ });
2680
+ span.setStatus({
2681
+ code: SpanStatusCode.ERROR,
2682
+ message: "Missing actor."
2683
+ });
2684
+ return new Response("Missing actor.", {
2685
+ status: 400,
2686
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2687
+ });
2688
+ }
2689
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
2690
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
2691
+ activity: json,
2692
+ recipient,
2693
+ activityId: activity.id.href,
2694
+ actorIds: activity.actorIds.map((actor) => actor.href)
2695
+ });
2696
+ span.setStatus({
2697
+ code: SpanStatusCode.ERROR,
2698
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
2699
+ });
2700
+ return new Response("The activity and its actors do not have the same origin.", {
2701
+ status: 401,
2702
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2703
+ });
2704
+ }
2662
2705
  const routeResult = await routeActivity({
2663
2706
  context: ctx,
2664
2707
  json,
@@ -2826,8 +2869,8 @@ var CustomCollectionHandler = class {
2826
2869
  * @param CollectionPage The CollectionPage constructor.
2827
2870
  * @param filterPredicate Optional filter predicate for items.
2828
2871
  */
2829
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
2830
- this.name = name$1;
2872
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
2873
+ this.name = name$2;
2831
2874
  this.values = values;
2832
2875
  this.context = context;
2833
2876
  this.callbacks = callbacks;
@@ -5060,6 +5103,35 @@ var FederationImpl = class extends FederationBuilderImpl {
5060
5103
  span.setAttribute("activitypub.activity.id", activity.id.href);
5061
5104
  listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
5062
5105
  }
5106
+ if (activity.actorId == null) {
5107
+ logger.error("Missing actor in queued incoming activity.", {
5108
+ activityId: activity.id?.href,
5109
+ activity: message.activity,
5110
+ recipient: message.identifier
5111
+ });
5112
+ listenerSpan.setStatus({
5113
+ code: SpanStatusCode.ERROR,
5114
+ message: "Missing actor."
5115
+ });
5116
+ recordInboxActivity(this.meterProvider, "rejected", activityType);
5117
+ listenerSpan.end();
5118
+ return;
5119
+ }
5120
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
5121
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
5122
+ activityId: activity.id.href,
5123
+ actorIds: activity.actorIds.map((actor) => actor.href),
5124
+ activity: message.activity,
5125
+ recipient: message.identifier
5126
+ });
5127
+ listenerSpan.setStatus({
5128
+ code: SpanStatusCode.ERROR,
5129
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
5130
+ });
5131
+ recordInboxActivity(this.meterProvider, "rejected", activityType);
5132
+ listenerSpan.end();
5133
+ return;
5134
+ }
5063
5135
  cacheKey = activity.id == null ? null : [
5064
5136
  ...this.kvPrefixes.activityIdempotence,
5065
5137
  context.origin,
@@ -6322,20 +6394,6 @@ var ContextImpl = class ContextImpl {
6322
6394
  activity: await fetched.toJsonLd({ contextLoader })
6323
6395
  });
6324
6396
  return false;
6325
- } else if (fetched.actorIds.length < 1) {
6326
- logger.debug("Fetched activity object is missing an actor; unable to verify.", {
6327
- recipient,
6328
- activity: await fetched.toJsonLd({ contextLoader })
6329
- });
6330
- return false;
6331
- }
6332
- const activityId = fetched.id;
6333
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
6334
- logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
6335
- recipient,
6336
- activity: await fetched.toJsonLd({ contextLoader })
6337
- });
6338
- return false;
6339
6397
  }
6340
6398
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
6341
6399
  activity = fetched;
@@ -6344,6 +6402,20 @@ var ContextImpl = class ContextImpl {
6344
6402
  recipient,
6345
6403
  activity: json
6346
6404
  });
6405
+ if (activity.actorId == null) {
6406
+ logger.debug("Activity is missing an actor; unable to verify.", {
6407
+ recipient,
6408
+ activity: json
6409
+ });
6410
+ return false;
6411
+ }
6412
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
6413
+ logger.debug("Activity has actors from different origins; unable to verify.", {
6414
+ recipient,
6415
+ activity: json
6416
+ });
6417
+ return false;
6418
+ }
6347
6419
  const routeResult = await routeActivity({
6348
6420
  context: this,
6349
6421
  json,