@fedify/fedify 2.4.0-dev.1964 → 2.4.0-dev.1995

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 (94) hide show
  1. package/dist/{builder-B4zpKIFU.mjs → builder-D0DxgOV5.mjs} +2 -2
  2. package/dist/{client-CtxvmazV.mjs → client-DpnpgjyX.mjs} +78 -4
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/compound-proof-CJnWc6c-.mjs +495 -0
  7. package/dist/{context-COLwHogJ.d.cts → context-BeDnDuF3.d.cts} +4 -3
  8. package/dist/{context-DiiCt4S7.d.ts → context-uyRUWoZQ.d.ts} +4 -3
  9. package/dist/{deno-CtG93ohj.mjs → deno-CABhzCb_.mjs} +1 -1
  10. package/dist/{docloader-dk_k3S8d.mjs → docloader-BbGSL0Xq.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  13. package/dist/federation/handler-compound-proof.test.mjs +152 -0
  14. package/dist/federation/handler.test.mjs +122 -4
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/keycache.test.mjs +52 -9
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +111 -15
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +2 -2
  21. package/dist/federation/mod.d.ts +2 -2
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/send.test.mjs +139 -4
  24. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  25. package/dist/federation/tasks/tasks.test.mjs +2 -2
  26. package/dist/federation/temporal.test.mjs +1 -1
  27. package/dist/federation/webfinger.test.mjs +1 -1
  28. package/dist/{federation-Drjlrrax.cjs → federation-DCEHQk4G.cjs} +632 -57
  29. package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
  30. package/dist/{http-Dj4s1UQ0.js → http-9WyuldYh.js} +170 -8
  31. package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
  32. package/dist/{http-B3skRvGo.cjs → http-CRFns-UE.cjs} +181 -7
  33. package/dist/{http-D7LX-BL9.mjs → http-D9urSO2D.mjs} +3 -3
  34. package/dist/{key-BVjgbs_9.mjs → key-Ccs8cnd7.mjs} +169 -7
  35. package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
  36. package/dist/{kv-cache-DtsjH_bY.cjs → kv-cache-DXT9vmIg.cjs} +2 -2
  37. package/dist/{kv-cache-tGNbvDCF.js → kv-cache-DtmXhPXG.js} +2 -2
  38. package/dist/{kv-cache-xRcC_xxP.mjs → kv-cache-lKyFn_Dn.mjs} +1 -1
  39. package/dist/{ld-OuCeQFny.mjs → ld-qup__MAW.mjs} +3 -3
  40. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  41. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  42. package/dist/{metrics-B9N27cCB.mjs → metrics-CISXCvB1.mjs} +1 -1
  43. package/dist/{middleware-TeLjzoxn.mjs → middleware-CzvJvPHK.mjs} +58 -15
  44. package/dist/{middleware-Cd_vPqDH.mjs → middleware-DkrU2WhY.mjs} +1 -1
  45. package/dist/{middleware-CPTDFMmJ.js → middleware-DsfWajaD.js} +593 -18
  46. package/dist/{mod-DypxPS9H.d.ts → mod-C0BYc0OU.d.ts} +1 -1
  47. package/dist/{mod-CnGLNoVw.d.cts → mod-CbKDOnCt.d.cts} +1 -1
  48. package/dist/{mod-CLHkCwLm.d.cts → mod-CyiSjRkk.d.cts} +1 -1
  49. package/dist/{mod-CSH1Ee-I.d.ts → mod-D882_Xsg.d.ts} +1 -1
  50. package/dist/{mod-DzRXACM5.d.ts → mod-UDwMjuHM.d.ts} +1 -1
  51. package/dist/{mod-BZldCEU0.d.cts → mod-x9W0d0Pk.d.cts} +1 -1
  52. package/dist/mod.cjs +20 -20
  53. package/dist/mod.d.cts +6 -6
  54. package/dist/mod.d.ts +6 -6
  55. package/dist/mod.js +5 -5
  56. package/dist/nodeinfo/client.test.mjs +28 -1
  57. package/dist/nodeinfo/handler.test.mjs +1 -1
  58. package/dist/nodeinfo/mod.cjs +1 -1
  59. package/dist/nodeinfo/mod.js +1 -1
  60. package/dist/{owner-DGSfntI-.d.ts → owner-BF0L9cMt.d.cts} +12 -1
  61. package/dist/{proof-BO_esQii.js → owner-BIPEZGiU.js} +1255 -1098
  62. package/dist/{owner-r_waTiQj.d.cts → owner-BSzr5-I8.d.ts} +12 -1
  63. package/dist/owner-CRoq8Spb.mjs +175 -0
  64. package/dist/{proof-Dua0d906.cjs → owner-CVA7L9MA.cjs} +1266 -1097
  65. package/dist/{proof-D5Ajk4DJ.mjs → proof-BDxNVZ3E.mjs} +186 -83
  66. package/dist/{send-DVXyUqhO.mjs → send-BzM5GLyn.mjs} +68 -11
  67. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  68. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  69. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  70. package/dist/sig/compound-proof-verification.test.mjs +465 -0
  71. package/dist/sig/compound-proof.test.d.mts +2 -0
  72. package/dist/sig/compound-proof.test.mjs +324 -0
  73. package/dist/sig/http.test.mjs +2 -2
  74. package/dist/sig/key.test.mjs +169 -2
  75. package/dist/sig/ld.test.mjs +55 -2
  76. package/dist/sig/mod.cjs +17 -17
  77. package/dist/sig/mod.d.cts +3 -3
  78. package/dist/sig/mod.d.ts +3 -3
  79. package/dist/sig/mod.js +2 -2
  80. package/dist/sig/owner.test.mjs +103 -4
  81. package/dist/sig/proof.test.mjs +120 -1
  82. package/dist/{temporal-BoDw51A-.mjs → temporal-Dv6Qv-8k.mjs} +1 -1
  83. package/dist/testing/kv.test.mjs +1 -1
  84. package/dist/testing/mod.d.mts +2 -1
  85. package/dist/{types-RXm5YxpC.cjs → types-Be2zbyeG.cjs} +94 -2
  86. package/dist/{types-Dtyf8lVj.js → types-C_cNlNVK.js} +78 -4
  87. package/dist/utils/docloader.test.mjs +27 -3
  88. package/dist/utils/kv-cache.test.mjs +1 -1
  89. package/dist/utils/mod.cjs +1 -1
  90. package/dist/utils/mod.d.cts +1 -1
  91. package/dist/utils/mod.d.ts +1 -1
  92. package/dist/utils/mod.js +1 -1
  93. package/package.json +6 -6
  94. package/dist/owner-DTEqTzSr.mjs +0 -120
@@ -1,20 +1,20 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-B3skRvGo.cjs");
5
- const require_proof = require("./proof-Dua0d906.cjs");
6
- const require_types = require("./types-RXm5YxpC.cjs");
7
- const require_kv_cache = require("./kv-cache-DtsjH_bY.cjs");
4
+ const require_http = require("./http-CRFns-UE.cjs");
5
+ const require_types = require("./types-Be2zbyeG.cjs");
6
+ const require_owner = require("./owner-CVA7L9MA.cjs");
7
+ const require_kv_cache = require("./kv-cache-DXT9vmIg.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
12
12
  let byte_encodings_hex = require("byte-encodings/hex");
13
- let es_toolkit = require("es-toolkit");
14
13
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
15
- let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
14
+ let es_toolkit = require("es-toolkit");
16
15
  let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
17
16
  _fedify_vocab_runtime_jsonld = require_rolldown_runtime.__toESM(_fedify_vocab_runtime_jsonld, 1);
17
+ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
18
18
  let _fedify_webfinger = require("@fedify/webfinger");
19
19
  let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
20
20
  let devalue = require("devalue");
@@ -1297,6 +1297,474 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
1297
1297
  return `collectionId="${collectionId}", url="${url}", digest="${(0, byte_encodings_hex.encodeHex)(await digest(actorIds))}"`;
1298
1298
  }
1299
1299
  //#endregion
1300
+ //#region src/sig/compound-proof.ts
1301
+ const textEncoder = new TextEncoder();
1302
+ const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
1303
+ function isJsonMap(value) {
1304
+ return typeof value === "object" && value != null && !Array.isArray(value);
1305
+ }
1306
+ function isCompoundProofJsonObject(value) {
1307
+ return typeof value === "object" && value != null && !Array.isArray(value);
1308
+ }
1309
+ function hasOnlyDataProperties(value) {
1310
+ try {
1311
+ return Reflect.ownKeys(value).every((key) => {
1312
+ if (typeof key !== "string") return false;
1313
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1314
+ return descriptor != null && "value" in descriptor && descriptor.enumerable;
1315
+ });
1316
+ } catch {
1317
+ return false;
1318
+ }
1319
+ }
1320
+ function hasOnlyJsonArrayProperties(value) {
1321
+ try {
1322
+ const keys = Reflect.ownKeys(value);
1323
+ if (keys.length !== value.length + 1) return false;
1324
+ for (const key of keys) {
1325
+ if (key === "length") continue;
1326
+ if (typeof key !== "string") return false;
1327
+ const index = Number(key);
1328
+ if (!Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== key) return false;
1329
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1330
+ if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) return false;
1331
+ }
1332
+ return true;
1333
+ } catch {
1334
+ return false;
1335
+ }
1336
+ }
1337
+ function escapeJsonPointerSegment(segment) {
1338
+ return segment.replaceAll("~", "~0").replaceAll("/", "~1");
1339
+ }
1340
+ function childPath(path, segment) {
1341
+ return `${path}/${escapeJsonPointerSegment(segment)}`;
1342
+ }
1343
+ function comparePaths(left, right) {
1344
+ return left < right ? -1 : left > right ? 1 : 0;
1345
+ }
1346
+ function encodedLength(value) {
1347
+ return textEncoder.encode(value).byteLength;
1348
+ }
1349
+ function validateLimits(limits) {
1350
+ for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative safe integer.`);
1351
+ }
1352
+ function limitExceeded(limit, maximum, actual, path) {
1353
+ return {
1354
+ status: "unsupported",
1355
+ reason: {
1356
+ type: "limitExceeded",
1357
+ limit,
1358
+ maximum,
1359
+ actual,
1360
+ path
1361
+ }
1362
+ };
1363
+ }
1364
+ function addBytes(statistics, bytes, limits, path) {
1365
+ statistics.byteLength += bytes;
1366
+ if (statistics.byteLength > limits.maxBytes) return limitExceeded("bytes", limits.maxBytes, statistics.byteLength, path);
1367
+ }
1368
+ function inspectJsonTree(root, limits) {
1369
+ if (!isJsonMap(root)) return {
1370
+ status: "unsupported",
1371
+ reason: {
1372
+ type: "invalidJsonTree",
1373
+ path: ""
1374
+ }
1375
+ };
1376
+ const statistics = {
1377
+ byteLength: 0,
1378
+ mapCount: 0,
1379
+ proofCount: 0,
1380
+ maxDepth: 0
1381
+ };
1382
+ const seen = /* @__PURE__ */ new Set();
1383
+ const pending = [{
1384
+ value: root,
1385
+ path: "",
1386
+ depth: 0,
1387
+ insideProof: false,
1388
+ insideContext: false
1389
+ }];
1390
+ while (pending.length > 0) {
1391
+ const current = pending.pop();
1392
+ statistics.maxDepth = Math.max(statistics.maxDepth, current.depth);
1393
+ if (current.depth > limits.maxDepth) return limitExceeded("depth", limits.maxDepth, current.depth, current.path);
1394
+ const value = current.value;
1395
+ if (value === null) {
1396
+ const failure = addBytes(statistics, 4, limits, current.path);
1397
+ if (failure != null) return failure;
1398
+ } else if (typeof value === "boolean") {
1399
+ const failure = addBytes(statistics, value ? 4 : 5, limits, current.path);
1400
+ if (failure != null) return failure;
1401
+ } else if (typeof value === "string") {
1402
+ const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
1403
+ if (failure != null) return failure;
1404
+ } else if (typeof value === "number") {
1405
+ if (!Number.isFinite(value)) return {
1406
+ status: "unsupported",
1407
+ reason: {
1408
+ type: "invalidJsonTree",
1409
+ path: current.path
1410
+ }
1411
+ };
1412
+ const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
1413
+ if (failure != null) return failure;
1414
+ } else if (Array.isArray(value)) {
1415
+ if (seen.has(value) || !hasOnlyJsonArrayProperties(value)) return {
1416
+ status: "unsupported",
1417
+ reason: {
1418
+ type: "invalidJsonTree",
1419
+ path: current.path
1420
+ }
1421
+ };
1422
+ seen.add(value);
1423
+ const failure = addBytes(statistics, 2 + Math.max(0, value.length - 1), limits, current.path);
1424
+ if (failure != null) return failure;
1425
+ for (let index = value.length - 1; index >= 0; index--) pending.push({
1426
+ value: value[index],
1427
+ path: childPath(current.path, String(index)),
1428
+ depth: current.depth + 1,
1429
+ insideProof: current.insideProof,
1430
+ insideContext: current.insideContext
1431
+ });
1432
+ } else if (isJsonMap(value)) {
1433
+ const prototype = Object.getPrototypeOf(value);
1434
+ if (seen.has(value) || !hasOnlyDataProperties(value) || prototype !== Object.prototype && prototype !== null) return {
1435
+ status: "unsupported",
1436
+ reason: {
1437
+ type: "invalidJsonTree",
1438
+ path: current.path
1439
+ }
1440
+ };
1441
+ seen.add(value);
1442
+ statistics.mapCount++;
1443
+ if (statistics.mapCount > limits.maxMaps) return limitExceeded("maps", limits.maxMaps, statistics.mapCount, current.path);
1444
+ const entries = Object.entries(value).sort(([left], [right]) => comparePaths(left, right));
1445
+ let objectBytes = 2 + Math.max(0, entries.length - 1);
1446
+ for (const [key] of entries) objectBytes += encodedLength(JSON.stringify(key)) + 1;
1447
+ const failure = addBytes(statistics, objectBytes, limits, current.path);
1448
+ if (failure != null) return failure;
1449
+ const hasDirectProof = !current.insideProof && !current.insideContext && Object.hasOwn(value, "proof");
1450
+ if (hasDirectProof) {
1451
+ statistics.proofCount++;
1452
+ if (statistics.proofCount > limits.maxProofs) return limitExceeded("proofs", limits.maxProofs, statistics.proofCount, childPath(current.path, "proof"));
1453
+ if (!isJsonMap(value.proof)) return {
1454
+ status: "unsupported",
1455
+ reason: {
1456
+ type: "unsupportedProofShape",
1457
+ path: childPath(current.path, "proof")
1458
+ }
1459
+ };
1460
+ }
1461
+ for (let index = entries.length - 1; index >= 0; index--) {
1462
+ const [key, child] = entries[index];
1463
+ pending.push({
1464
+ value: child,
1465
+ path: childPath(current.path, key),
1466
+ depth: current.depth + 1,
1467
+ insideProof: current.insideProof || hasDirectProof && key === "proof",
1468
+ insideContext: current.insideContext || key === "@context"
1469
+ });
1470
+ }
1471
+ } else return {
1472
+ status: "unsupported",
1473
+ reason: {
1474
+ type: "invalidJsonTree",
1475
+ path: current.path
1476
+ }
1477
+ };
1478
+ }
1479
+ return statistics;
1480
+ }
1481
+ function freezeJsonTree(root) {
1482
+ const pending = [root];
1483
+ while (pending.length > 0) {
1484
+ const value = pending.pop();
1485
+ if (typeof value !== "object" || value == null) continue;
1486
+ if (Array.isArray(value)) for (const child of value) pending.push(child);
1487
+ else for (const child of Object.values(value)) pending.push(child);
1488
+ Object.freeze(value);
1489
+ }
1490
+ }
1491
+ function collectDocuments(snapshot) {
1492
+ const documents = [];
1493
+ const pending = [{
1494
+ value: snapshot,
1495
+ path: "",
1496
+ depth: 0
1497
+ }];
1498
+ while (pending.length > 0) {
1499
+ const current = pending.pop();
1500
+ if (Array.isArray(current.value)) {
1501
+ for (let index = current.value.length - 1; index >= 0; index--) pending.push({
1502
+ value: current.value[index],
1503
+ path: childPath(current.path, String(index)),
1504
+ depth: current.depth + 1
1505
+ });
1506
+ continue;
1507
+ }
1508
+ if (!isCompoundProofJsonObject(current.value)) continue;
1509
+ const document = current.value;
1510
+ if (Object.hasOwn(document, "proof")) {
1511
+ const proof = document.proof;
1512
+ const id = typeof document.id === "string" ? document.id : typeof document["@id"] === "string" ? document["@id"] : void 0;
1513
+ documents.push(Object.freeze({
1514
+ path: current.path,
1515
+ id,
1516
+ depth: current.depth,
1517
+ securedDocument: document,
1518
+ proof
1519
+ }));
1520
+ }
1521
+ const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
1522
+ for (let index = entries.length - 1; index >= 0; index--) {
1523
+ const [key, value] = entries[index];
1524
+ if (key === "proof" || key === "@context") continue;
1525
+ pending.push({
1526
+ value,
1527
+ path: childPath(current.path, key),
1528
+ depth: current.depth + 1
1529
+ });
1530
+ }
1531
+ }
1532
+ documents.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
1533
+ return documents;
1534
+ }
1535
+ function collectPortableObjects(snapshot) {
1536
+ const objects = [];
1537
+ const pending = [{
1538
+ value: snapshot,
1539
+ path: "",
1540
+ depth: 0
1541
+ }];
1542
+ while (pending.length > 0) {
1543
+ const current = pending.pop();
1544
+ if (Array.isArray(current.value)) {
1545
+ for (let index = current.value.length - 1; index >= 0; index--) pending.push({
1546
+ value: current.value[index],
1547
+ path: childPath(current.path, String(index)),
1548
+ depth: current.depth + 1
1549
+ });
1550
+ continue;
1551
+ }
1552
+ if (!isCompoundProofJsonObject(current.value)) continue;
1553
+ const document = current.value;
1554
+ const id = [document.id, document["@id"]].find((value) => typeof value === "string" && PORTABLE_OBJECT_ID_PATTERN.test(value));
1555
+ if (typeof id === "string") objects.push(Object.freeze({
1556
+ path: current.path,
1557
+ id,
1558
+ depth: current.depth,
1559
+ document
1560
+ }));
1561
+ const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
1562
+ for (let index = entries.length - 1; index >= 0; index--) {
1563
+ const [key, value] = entries[index];
1564
+ if (key === "proof" || key === "@context") continue;
1565
+ pending.push({
1566
+ value,
1567
+ path: childPath(current.path, key),
1568
+ depth: current.depth + 1
1569
+ });
1570
+ }
1571
+ }
1572
+ objects.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
1573
+ return objects;
1574
+ }
1575
+ /**
1576
+ * Determines whether a received JSON tree contains a portable object ID
1577
+ * outside a proof or context definition, without exceeding discovery limits.
1578
+ *
1579
+ * This is only an applicability check. It does not authenticate the input;
1580
+ * callers must still use {@link verifyCompoundPortableObjectProofs} to obtain
1581
+ * an atomic result from an immutable snapshot. `indeterminate` means the
1582
+ * applicability scan itself reached a limit, so a caller must not treat the
1583
+ * document as outside the compound profile.
1584
+ *
1585
+ * @internal
1586
+ */
1587
+ function inspectCompoundPortableObjectApplicability(json, limits) {
1588
+ validateLimits(limits);
1589
+ let byteLength = 0;
1590
+ let mapCount = 0;
1591
+ const pending = [{
1592
+ iterator: [json].values(),
1593
+ depth: 0
1594
+ }];
1595
+ const addByteLength = (length) => {
1596
+ byteLength += length;
1597
+ return byteLength <= limits.maxBytes;
1598
+ };
1599
+ while (pending.length > 0) {
1600
+ const frame = pending[pending.length - 1];
1601
+ const next = frame.iterator.next();
1602
+ if (next.done) {
1603
+ pending.pop();
1604
+ if (byteLength > limits.maxBytes) return "indeterminate";
1605
+ continue;
1606
+ }
1607
+ const value = next.value;
1608
+ if (frame.depth > limits.maxDepth) return "indeterminate";
1609
+ if (value === null) {
1610
+ if (!addByteLength(4)) return "indeterminate";
1611
+ } else if (typeof value === "boolean") {
1612
+ if (!addByteLength(value ? 4 : 5)) return "indeterminate";
1613
+ } else if (typeof value === "string" || typeof value === "number") {
1614
+ if (!addByteLength(encodedLength(JSON.stringify(value)))) return "indeterminate";
1615
+ } else if (Array.isArray(value)) {
1616
+ if (!addByteLength(2 + Math.max(0, value.length - 1))) return "indeterminate";
1617
+ pending.push({
1618
+ iterator: value.values(),
1619
+ depth: frame.depth + 1
1620
+ });
1621
+ } else if (isJsonMap(value)) {
1622
+ mapCount++;
1623
+ if (mapCount > limits.maxMaps) return "indeterminate";
1624
+ if ([value.id, value["@id"]].some((id) => typeof id === "string" && PORTABLE_OBJECT_ID_PATTERN.test(id))) return "present";
1625
+ if (!addByteLength(2)) return "indeterminate";
1626
+ const children = function* () {
1627
+ let first = true;
1628
+ for (const key in value) {
1629
+ if (!Object.hasOwn(value, key)) continue;
1630
+ if (key === "proof" || key === "@context") continue;
1631
+ if (!first && !addByteLength(1)) return;
1632
+ first = false;
1633
+ if (!addByteLength(encodedLength(JSON.stringify(key)) + 1)) return;
1634
+ yield value[key];
1635
+ }
1636
+ }();
1637
+ pending.push({
1638
+ iterator: children,
1639
+ depth: frame.depth + 1
1640
+ });
1641
+ }
1642
+ if (byteLength > limits.maxBytes) return "indeterminate";
1643
+ }
1644
+ return "absent";
1645
+ }
1646
+ /**
1647
+ * Discovers direct literal proof-bearing maps in an immutable JSON snapshot.
1648
+ *
1649
+ * Discovery is bounded and atomic. Unsupported proof shapes, non-JSON input,
1650
+ * or a resource-limit failure return no partial candidate list. Proof
1651
+ * configuration subtrees are validated and counted as JSON, but are not
1652
+ * themselves searched for secured documents.
1653
+ */
1654
+ function discoverCompoundProofDocuments(json, limits) {
1655
+ validateLimits(limits);
1656
+ let inspected;
1657
+ try {
1658
+ inspected = inspectJsonTree(json, limits);
1659
+ } catch {
1660
+ return {
1661
+ status: "unsupported",
1662
+ reason: {
1663
+ type: "invalidJsonTree",
1664
+ path: ""
1665
+ }
1666
+ };
1667
+ }
1668
+ if ("status" in inspected) return inspected;
1669
+ let snapshot;
1670
+ try {
1671
+ snapshot = structuredClone(json);
1672
+ } catch {
1673
+ return {
1674
+ status: "unsupported",
1675
+ reason: {
1676
+ type: "invalidJsonTree",
1677
+ path: ""
1678
+ }
1679
+ };
1680
+ }
1681
+ freezeJsonTree(snapshot);
1682
+ return {
1683
+ status: "ok",
1684
+ snapshot,
1685
+ documents: Object.freeze(collectDocuments(snapshot)),
1686
+ statistics: Object.freeze({ ...inspected })
1687
+ };
1688
+ }
1689
+ async function verifyDiscoveredProofDocuments(discovered, options) {
1690
+ const documents = await Promise.all(discovered.documents.map(async (document) => {
1691
+ const metadata = {
1692
+ path: document.path,
1693
+ ...document.id == null ? {} : { id: document.id },
1694
+ depth: document.depth
1695
+ };
1696
+ if (document.depth > 0 && !Object.hasOwn(document.securedDocument, "@context")) return Object.freeze({
1697
+ ...metadata,
1698
+ verified: false,
1699
+ reason: { type: "missingContext" }
1700
+ });
1701
+ const key = await require_owner.verifyMapLocalProof(document.securedDocument, options);
1702
+ return key == null ? Object.freeze({
1703
+ ...metadata,
1704
+ verified: false,
1705
+ reason: { type: "invalidProof" }
1706
+ }) : Object.freeze({
1707
+ ...metadata,
1708
+ verified: true,
1709
+ key
1710
+ });
1711
+ }));
1712
+ return Object.freeze(documents);
1713
+ }
1714
+ /**
1715
+ * Applies FEP-ef61 portable-object policy to every portable map in a compound
1716
+ * snapshot without changing the map-local cryptographic inputs.
1717
+ *
1718
+ * @internal
1719
+ */
1720
+ async function verifyCompoundPortableObjectProofs(json, limits, options = {}) {
1721
+ const discovered = discoverCompoundProofDocuments(json, limits);
1722
+ if (discovered.status !== "ok") return discovered;
1723
+ const proofs = await verifyDiscoveredProofDocuments(discovered, options);
1724
+ const proofByPath = new Map(proofs.map((proof) => [proof.path, proof]));
1725
+ const portableObjects = await Promise.all(collectPortableObjects(discovered.snapshot).map(async (object) => {
1726
+ const metadata = {
1727
+ path: object.path,
1728
+ id: object.id,
1729
+ depth: object.depth
1730
+ };
1731
+ if (object.depth > 0 && !Object.hasOwn(object.document, "@context")) return Object.freeze({
1732
+ ...metadata,
1733
+ verified: false,
1734
+ reason: { type: "missingContext" }
1735
+ });
1736
+ const proof = proofByPath.get(object.path);
1737
+ const key = proof?.verified === true ? proof.key : null;
1738
+ try {
1739
+ const policy = await require_owner.verifyPortableObjectProofPolicy(object.document, key, options);
1740
+ return policy.verified ? Object.freeze({
1741
+ ...metadata,
1742
+ id: policy.objectId,
1743
+ verified: true,
1744
+ keys: policy.keys
1745
+ }) : Object.freeze({
1746
+ ...metadata,
1747
+ verified: false,
1748
+ reason: policy.reason
1749
+ });
1750
+ } catch {
1751
+ return Object.freeze({
1752
+ ...metadata,
1753
+ verified: false,
1754
+ reason: { type: "invalidPortableObject" }
1755
+ });
1756
+ }
1757
+ }));
1758
+ return {
1759
+ status: "ok",
1760
+ verified: portableObjects.length > 0 && proofs.every((proof) => proof.verified) && portableObjects.every((object) => object.verified),
1761
+ snapshot: discovered.snapshot,
1762
+ proofs,
1763
+ portableObjects: Object.freeze(portableObjects),
1764
+ statistics: discovered.statistics
1765
+ };
1766
+ }
1767
+ //#endregion
1300
1768
  //#region src/federation/inbox.ts
1301
1769
  const Temporal$9 = require_http.Temporal;
1302
1770
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
@@ -1458,6 +1926,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1458
1926
  //#endregion
1459
1927
  //#region src/federation/keycache.ts
1460
1928
  const Temporal$8 = require_http.Temporal;
1929
+ const KEY_CACHE_GENERATION = "2";
1461
1930
  var KvKeyCache = class {
1462
1931
  kv;
1463
1932
  prefix;
@@ -1480,13 +1949,20 @@ var KvKeyCache = class {
1480
1949
  keyId.href
1481
1950
  ];
1482
1951
  }
1952
+ #entryKey(keyId) {
1953
+ return [
1954
+ ...this.prefix,
1955
+ KEY_CACHE_GENERATION,
1956
+ keyId.href
1957
+ ];
1958
+ }
1483
1959
  async get(keyId) {
1484
1960
  const negativeExpiration = this.nullKeys.get(keyId.href);
1485
1961
  if (negativeExpiration != null) {
1486
1962
  if (Temporal$8.Now.instant().until(negativeExpiration).sign >= 0) return null;
1487
1963
  this.nullKeys.delete(keyId.href);
1488
1964
  }
1489
- const serialized = await this.kv.get([...this.prefix, keyId.href]);
1965
+ const serialized = await this.kv.get(this.#entryKey(keyId));
1490
1966
  if (serialized === void 0) return void 0;
1491
1967
  if (serialized === null) {
1492
1968
  this.nullKeys.set(keyId.href, Temporal$8.Now.instant().add(this.unavailableKeyTtl));
@@ -1498,7 +1974,7 @@ var KvKeyCache = class {
1498
1974
  try {
1499
1975
  return await _fedify_vocab.Multikey.fromJsonLd(serialized, this.options);
1500
1976
  } catch {
1501
- await this.kv.delete([...this.prefix, keyId.href]);
1977
+ await this.kv.delete(this.#entryKey(keyId));
1502
1978
  return;
1503
1979
  }
1504
1980
  }
@@ -1506,12 +1982,12 @@ var KvKeyCache = class {
1506
1982
  async set(keyId, key) {
1507
1983
  if (key == null) {
1508
1984
  this.nullKeys.set(keyId.href, Temporal$8.Now.instant().add(this.unavailableKeyTtl));
1509
- await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
1985
+ await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
1510
1986
  return;
1511
1987
  }
1512
1988
  this.nullKeys.delete(keyId.href);
1513
1989
  const serialized = await key.toJsonLd(this.options);
1514
- await this.kv.set([...this.prefix, keyId.href], serialized, { ttl: this.keyTtl });
1990
+ await this.kv.set(this.#entryKey(keyId), serialized, { ttl: this.keyTtl });
1515
1991
  }
1516
1992
  async getFetchError(keyId) {
1517
1993
  const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
@@ -1725,7 +2201,7 @@ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1725
2201
  async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1726
2202
  try {
1727
2203
  return hasMalformedKnownTemporalLiteralInternal(await _fedify_vocab_runtime_jsonld.default.expand(value, {
1728
- documentLoader: require_proof.getNormalizationContextLoader(contextLoader),
2204
+ documentLoader: require_owner.getNormalizationContextLoader(contextLoader),
1729
2205
  keepFreeFloatingNodes: true
1730
2206
  }), /* @__PURE__ */ new Set());
1731
2207
  } catch {
@@ -1736,6 +2212,12 @@ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1736
2212
  //#region src/federation/handler.ts
1737
2213
  const Temporal$6 = require_http.Temporal;
1738
2214
  const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
2215
+ const INBOX_COMPOUND_PROOF_LIMITS = {
2216
+ maxDepth: 64,
2217
+ maxMaps: 1e4,
2218
+ maxProofs: 32,
2219
+ maxBytes: 10 * 1024 * 1024
2220
+ };
1739
2221
  function isRemoteContextLoadingFailure$1(error) {
1740
2222
  return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1741
2223
  }
@@ -1743,15 +2225,15 @@ function isPermanentRemoteContextError$1(error) {
1743
2225
  if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1744
2226
  const details = error.details;
1745
2227
  if (details?.code === "invalid remote context") return true;
1746
- return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
2228
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_owner.isClearlyMalformedContextReference(details.url);
1747
2229
  }
1748
2230
  function isInvalidJsonLdError(error) {
1749
2231
  if (!(error instanceof Error)) return false;
1750
2232
  const name = error.name;
1751
- return name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
2233
+ return name === "UnsafeJsonLdError" || error instanceof require_owner.InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1752
2234
  }
1753
2235
  function isValidationTypeError(error) {
1754
- return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:|Invalid @id:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
2236
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:|Invalid @id:)/.test(error.message) || require_owner.isInvalidUrlTypeError(error));
1755
2237
  }
1756
2238
  function isPermanentActivityParseError(error) {
1757
2239
  return isInvalidJsonLdError(error) || isValidationTypeError(error);
@@ -1860,13 +2342,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
1860
2342
  * @param parameters The parameters for handling the collection.
1861
2343
  * @returns A promise that resolves to an HTTP response.
1862
2344
  */
1863
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
1864
- const spanName = name$2.trim().replace(/\s+/g, "_");
2345
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
2346
+ const spanName = name$1.trim().replace(/\s+/g, "_");
1865
2347
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
1866
2348
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
1867
2349
  const cursor = new URL(request.url).searchParams.get("cursor");
1868
2350
  const metricBase = {
1869
- kind: getCollectionMetricKind(name$2),
2351
+ kind: getCollectionMetricKind(name$1),
1870
2352
  page: cursor != null,
1871
2353
  dispatcher: "built_in"
1872
2354
  };
@@ -1907,7 +2389,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1907
2389
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1908
2390
  return await onNotFound(request);
1909
2391
  }
1910
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
2392
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
1911
2393
  itemCount = items.length;
1912
2394
  span.setAttribute("fedify.collection.items", itemCount);
1913
2395
  return items;
@@ -1947,7 +2429,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1947
2429
  } else {
1948
2430
  const uri = new URL(baseUri);
1949
2431
  uri.searchParams.set("cursor", cursor);
1950
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
2432
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1951
2433
  kind: _opentelemetry_api.SpanKind.SERVER,
1952
2434
  attributes: {
1953
2435
  "activitypub.collection.id": uri.href,
@@ -1963,7 +2445,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1963
2445
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1964
2446
  return await onNotFound(request);
1965
2447
  }
1966
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
2448
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
1967
2449
  itemCount = items.length;
1968
2450
  span.setAttribute("fedify.collection.items", itemCount);
1969
2451
  return {
@@ -2516,8 +2998,16 @@ async function handleInboxInternal(request, parameters, span) {
2516
2998
  }
2517
2999
  let json;
2518
3000
  try {
2519
- json = await request.clone().json();
3001
+ json = JSON.parse(await require_types.readBoundedText(request.clone(), require_types.MAX_BODY_SIZE, request.url));
2520
3002
  } catch (error) {
3003
+ if (error instanceof require_types.BodyTooLargeError) {
3004
+ request.body?.cancel(error).catch(() => {});
3005
+ span.setStatus({
3006
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3007
+ message: String(error)
3008
+ });
3009
+ return new Response("Inbox body too large.", { status: 413 });
3010
+ }
2521
3011
  logger.error("Failed to parse JSON:\n{error}", {
2522
3012
  recipient,
2523
3013
  error
@@ -2546,8 +3036,8 @@ async function handleInboxInternal(request, parameters, span) {
2546
3036
  tracerProvider,
2547
3037
  keyTtl: publicKeyTtl
2548
3038
  });
2549
- const jsonWithoutSig = require_proof.detachSignature(json);
2550
- const hasLdSignature = require_proof.hasSignature(json);
3039
+ const jsonWithoutSig = require_owner.detachSignature(json);
3040
+ const hasLdSignature = require_owner.hasSignature(json);
2551
3041
  const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
2552
3042
  let deferredLdSignatureError = void 0;
2553
3043
  const respondInvalidActivity = async (error) => {
@@ -2579,7 +3069,7 @@ async function handleInboxInternal(request, parameters, span) {
2579
3069
  let ldSigVerified = false;
2580
3070
  if (hasLdSignature) {
2581
3071
  try {
2582
- compactedJson = await require_proof.compactJsonLd(json, ctx.contextLoader);
3072
+ compactedJson = await require_owner.compactJsonLd(json, ctx.contextLoader);
2583
3073
  } catch (error) {
2584
3074
  if (isInvalidJsonLdError(error)) {
2585
3075
  logger.error("Failed to parse JSON-LD:\n{error}", {
@@ -2599,9 +3089,9 @@ async function handleInboxInternal(request, parameters, span) {
2599
3089
  });
2600
3090
  }
2601
3091
  if (compactedJson !== json) {
2602
- compactedJsonWithoutSig = require_proof.detachSignature(compactedJson);
3092
+ compactedJsonWithoutSig = require_owner.detachSignature(compactedJson);
2603
3093
  try {
2604
- ldSigVerified = await require_proof.verifyCompactJsonLd(compactedJson, {
3094
+ ldSigVerified = await require_owner.verifyCompactJsonLd(compactedJson, {
2605
3095
  contextLoader: ctx.contextLoader,
2606
3096
  documentLoader: ctx.documentLoader,
2607
3097
  keyCache,
@@ -2623,7 +3113,7 @@ async function handleInboxInternal(request, parameters, span) {
2623
3113
  if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
2624
3114
  if (!skipSignatureVerification) try {
2625
3115
  await _fedify_vocab.Object.fromJsonLd(compactedJson, {
2626
- contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader),
3116
+ contextLoader: require_owner.getNormalizationContextLoader(ctx.contextLoader),
2627
3117
  documentLoader: ctx.documentLoader,
2628
3118
  tracerProvider
2629
3119
  });
@@ -2655,7 +3145,7 @@ async function handleInboxInternal(request, parameters, span) {
2655
3145
  try {
2656
3146
  activity = await _fedify_vocab.Activity.fromJsonLd(compactedJsonWithoutSig, {
2657
3147
  ...ctx,
2658
- contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader)
3148
+ contextLoader: require_owner.getNormalizationContextLoader(ctx.contextLoader)
2659
3149
  });
2660
3150
  } catch (error) {
2661
3151
  if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
@@ -2669,8 +3159,8 @@ async function handleInboxInternal(request, parameters, span) {
2669
3159
  json
2670
3160
  });
2671
3161
  try {
2672
- activity = await require_proof.verifyObject(_fedify_vocab.Activity, jsonWithoutSig, {
2673
- contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader),
3162
+ activity = await require_owner.verifyObject(_fedify_vocab.Activity, jsonWithoutSig, {
3163
+ contextLoader: require_owner.wrapContextLoaderForJsonLd(ctx.contextLoader),
2674
3164
  documentLoader: ctx.documentLoader,
2675
3165
  keyCache,
2676
3166
  meterProvider,
@@ -2815,7 +3305,7 @@ async function handleInboxInternal(request, parameters, span) {
2815
3305
  try {
2816
3306
  activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, {
2817
3307
  ...ctx,
2818
- contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader)
3308
+ contextLoader: require_owner.wrapContextLoaderForJsonLd(ctx.contextLoader)
2819
3309
  });
2820
3310
  } catch (error) {
2821
3311
  if (!isPermanentActivityParseError(error)) throw error;
@@ -2831,7 +3321,7 @@ async function handleInboxInternal(request, parameters, span) {
2831
3321
  "http_signatures.verified": httpSigKey != null,
2832
3322
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
2833
3323
  });
2834
- if (httpSigKey != null && !await require_proof.doesActorOwnKey(activity, httpSigKey, ctx)) {
3324
+ if (httpSigKey != null && !await require_owner.doesActorOwnKey(activity, httpSigKey, ctx)) {
2835
3325
  if (deferredLdSignatureError != null) throw deferredLdSignatureError;
2836
3326
  require_http.getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("actorKeyMismatch", httpSigKey.id == null ? void 0 : require_http.getRemoteHost(httpSigKey.id));
2837
3327
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
@@ -2849,6 +3339,31 @@ async function handleInboxInternal(request, parameters, span) {
2849
3339
  headers: { "Content-Type": "text/plain; charset=utf-8" }
2850
3340
  });
2851
3341
  }
3342
+ if (!skipSignatureVerification) {
3343
+ if (inspectCompoundPortableObjectApplicability(json, INBOX_COMPOUND_PROOF_LIMITS) !== "absent") {
3344
+ const compoundProof = await verifyCompoundPortableObjectProofs(json, INBOX_COMPOUND_PROOF_LIMITS, {
3345
+ documentLoader: ctx.documentLoader,
3346
+ keyCache,
3347
+ meterProvider,
3348
+ tracerProvider
3349
+ });
3350
+ if (compoundProof.status !== "ok" || !compoundProof.verified) {
3351
+ logger.error("Failed to verify compound portable Object Integrity Proofs.", {
3352
+ recipient,
3353
+ activity: json,
3354
+ reason: compoundProof.status === "unsupported" ? compoundProof.reason.type : "invalidProof"
3355
+ });
3356
+ span.setStatus({
3357
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3358
+ message: "Failed to verify compound portable Object Integrity Proofs."
3359
+ });
3360
+ return new Response("Failed to verify compound portable Object Integrity Proofs.", {
3361
+ status: 401,
3362
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
3363
+ });
3364
+ }
3365
+ }
3366
+ }
2852
3367
  if (pendingNonceLabel != null) {
2853
3368
  if (!await verifySignatureNonce(request, kv, kvPrefixes.acceptSignatureNonce, pendingNonceLabel)) {
2854
3369
  require_http.getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("invalidNonce", httpSigKey?.id == null ? void 0 : require_http.getRemoteHost(httpSigKey.id));
@@ -3023,8 +3538,8 @@ var CustomCollectionHandler = class {
3023
3538
  * @param CollectionPage The CollectionPage constructor.
3024
3539
  * @param filterPredicate Optional filter predicate for items.
3025
3540
  */
3026
- constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
3027
- this.name = name$1;
3541
+ constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
3542
+ this.name = name$2;
3028
3543
  this.values = values;
3029
3544
  this.context = context;
3030
3545
  this.callbacks = callbacks;
@@ -3737,7 +4252,7 @@ async function readLimitedResponseBody(response, maxBytes) {
3737
4252
  if (truncated) result += "… (truncated)";
3738
4253
  return result;
3739
4254
  }
3740
- async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, headers, specDeterminer, meterProvider, tracerProvider }, span) {
4255
+ async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
3741
4256
  const logger = (0, _logtape_logtape.getLogger)([
3742
4257
  "fedify",
3743
4258
  "federation",
@@ -3746,12 +4261,25 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
3746
4261
  const federationMetrics = require_http.getFederationMetrics(meterProvider);
3747
4262
  const started = performance.now();
3748
4263
  let deliverySuccess = false;
4264
+ async function validateUrl(url) {
4265
+ if (!allowPrivateAddress) try {
4266
+ await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
4267
+ } catch (error) {
4268
+ if (error instanceof _fedify_vocab_runtime.UrlError) logger.error("Disallowed private URL: {url}", {
4269
+ url,
4270
+ error
4271
+ });
4272
+ throw error;
4273
+ }
4274
+ }
4275
+ await validateUrl(inbox.href);
3749
4276
  headers = new Headers(headers);
3750
4277
  headers.set("Content-Type", "application/activity+json");
3751
4278
  const request = new Request(inbox, {
3752
4279
  method: "POST",
3753
4280
  headers,
3754
- body: JSON.stringify(activity)
4281
+ body: JSON.stringify(activity),
4282
+ redirect: "manual"
3755
4283
  });
3756
4284
  let rsaKey = null;
3757
4285
  for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
@@ -3767,19 +4295,20 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
3767
4295
  });
3768
4296
  let response;
3769
4297
  try {
3770
- response = rsaKey == null ? await fetch(request) : await require_http.doubleKnock(request, rsaKey, {
4298
+ response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await require_http.doubleKnock(request, rsaKey, {
3771
4299
  tracerProvider,
3772
- specDeterminer
4300
+ specDeterminer,
4301
+ validateRedirect: validateUrl
3773
4302
  });
3774
4303
  } catch (error) {
3775
- const transportError = error instanceof _fedify_vocab_runtime.FetchError ? error : createFetchError(inbox.href, error);
4304
+ const failure = error instanceof _fedify_vocab_runtime.FetchError || error instanceof _fedify_vocab_runtime.UrlError ? error : createFetchError(inbox.href, error);
3776
4305
  logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
3777
4306
  activityId,
3778
4307
  inbox: inbox.href,
3779
- error: transportError
4308
+ error: failure
3780
4309
  });
3781
4310
  federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), false, activityType);
3782
- throw transportError;
4311
+ throw failure;
3783
4312
  }
3784
4313
  try {
3785
4314
  if (!response.ok) {
@@ -3816,6 +4345,49 @@ function createFetchError(url, cause) {
3816
4345
  error.cause = cause;
3817
4346
  return error;
3818
4347
  }
4348
+ async function fetchWithValidatedRedirects(request, validateUrl) {
4349
+ let body = await request.clone().text();
4350
+ for (let redirects = 0;; redirects++) {
4351
+ const response = await fetch(request, { redirect: "manual" });
4352
+ const location = response.headers.get("Location");
4353
+ if (![
4354
+ 301,
4355
+ 302,
4356
+ 303,
4357
+ 307,
4358
+ 308
4359
+ ].includes(response.status) || location == null) return response;
4360
+ await response.body?.cancel();
4361
+ if (redirects >= 20) throw new TypeError("Too many redirects");
4362
+ const url = new URL(location, request.url);
4363
+ await validateUrl(url.href);
4364
+ const headers = new Headers(request.headers);
4365
+ if (url.origin !== new URL(request.url).origin) {
4366
+ headers.delete("Authorization");
4367
+ headers.delete("Proxy-Authorization");
4368
+ headers.delete("Cookie");
4369
+ headers.delete("Host");
4370
+ }
4371
+ let method = request.method;
4372
+ if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
4373
+ method = "GET";
4374
+ body = void 0;
4375
+ for (const name of [
4376
+ "Content-Encoding",
4377
+ "Content-Language",
4378
+ "Content-Length",
4379
+ "Content-Location",
4380
+ "Content-Type"
4381
+ ]) headers.delete(name);
4382
+ }
4383
+ request = new Request(url, {
4384
+ method,
4385
+ headers,
4386
+ body,
4387
+ redirect: "manual"
4388
+ });
4389
+ }
4390
+ }
3819
4391
  /**
3820
4392
  * An error that is thrown when an activity fails to send to a remote inbox.
3821
4393
  * It contains structured information about the failure, including the HTTP
@@ -4742,10 +5314,10 @@ function isPermanentRemoteContextError(error) {
4742
5314
  if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
4743
5315
  const details = error.details;
4744
5316
  if (details?.code === "invalid remote context") return true;
4745
- return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
5317
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_owner.isClearlyMalformedContextReference(details.url);
4746
5318
  }
4747
5319
  function isPermanentInboxParseError(error) {
4748
- return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:|Invalid @id:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
5320
+ return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof require_owner.InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:|Invalid @id:)/.test(error.message) || require_owner.isInvalidUrlTypeError(error));
4749
5321
  }
4750
5322
  function hasLinkedDataSignatureJsonLdProcessingError(error) {
4751
5323
  if (error.message.startsWith("Maximum deep iterations exceeded")) return true;
@@ -5309,6 +5881,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5309
5881
  if (decision.stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, decision.stateChange);
5310
5882
  }
5311
5883
  await sendActivity({
5884
+ allowPrivateAddress: this.allowPrivateAddress,
5312
5885
  keys,
5313
5886
  activity: message.activity,
5314
5887
  activityId: message.activityId,
@@ -5596,22 +6169,22 @@ var FederationImpl = class extends FederationBuilderImpl {
5596
6169
  let parseInput = void 0;
5597
6170
  let parseContextLoader = context.contextLoader;
5598
6171
  try {
5599
- const hasSignatureField = require_proof.hasSignature(message.activity);
6172
+ const hasSignatureField = require_owner.hasSignature(message.activity);
5600
6173
  const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
5601
6174
  const parseContext = hasSignatureField ? {
5602
6175
  ...context,
5603
- contextLoader: require_proof.getNormalizationContextLoader(context.contextLoader)
6176
+ contextLoader: require_owner.getNormalizationContextLoader(context.contextLoader)
5604
6177
  } : {
5605
6178
  ...context,
5606
- contextLoader: require_proof.wrapContextLoaderForJsonLd(context.contextLoader)
6179
+ contextLoader: require_owner.wrapContextLoaderForJsonLd(context.contextLoader)
5607
6180
  };
5608
6181
  parseContextLoader = parseContext.contextLoader;
5609
6182
  let normalizedActivity;
5610
6183
  if (shouldParseFromNormalizedSignedPayload) {
5611
- normalizedActivity = message.normalizedActivity ?? await require_proof.compactJsonLd(message.activity, context.contextLoader);
5612
- require_proof.assertSafeJsonLd(normalizedActivity);
6184
+ normalizedActivity = message.normalizedActivity ?? await require_owner.compactJsonLd(message.activity, context.contextLoader);
6185
+ require_owner.assertSafeJsonLd(normalizedActivity);
5613
6186
  }
5614
- parseInput = shouldParseFromNormalizedSignedPayload ? require_proof.detachSignature(normalizedActivity) : hasSignatureField ? require_proof.detachSignature(message.activity) : message.activity;
6187
+ parseInput = shouldParseFromNormalizedSignedPayload ? require_owner.detachSignature(normalizedActivity) : hasSignatureField ? require_owner.detachSignature(message.activity) : message.activity;
5615
6188
  activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
5616
6189
  activityType = (0, _fedify_vocab.getTypeId)(activity).href;
5617
6190
  span.setAttribute("activitypub.activity.type", activityType);
@@ -5880,7 +6453,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5880
6453
  }
5881
6454
  if (!hasProof) {
5882
6455
  for (const { keyId, privateKey } of keys) if (privateKey.algorithm.name === "Ed25519") {
5883
- activity = await require_proof.signObject(activity, privateKey, keyId, {
6456
+ activity = await require_owner.signObject(activity, privateKey, keyId, {
5884
6457
  contextLoader,
5885
6458
  tracerProvider: this.tracerProvider
5886
6459
  });
@@ -5892,7 +6465,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5892
6465
  format: "compact",
5893
6466
  contextLoader
5894
6467
  });
5895
- if (proofCreated || !hasProof || options.normalizeExistingProofs) jsonLd = await require_proof.normalizeOutgoingActivityJsonLd(jsonLd, contextLoader);
6468
+ if (proofCreated || !hasProof || options.normalizeExistingProofs) jsonLd = await require_owner.normalizeOutgoingActivityJsonLd(jsonLd, contextLoader);
5896
6469
  if (rsaKey == null) logger.warn("No supported key found to create a Linked Data signature for the activity {activityId}. The activity will be sent without a Linked Data signature. In order to create a Linked Data signature, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
5897
6470
  activityId,
5898
6471
  keys: keys.map((pair) => ({
@@ -5901,7 +6474,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5901
6474
  }))
5902
6475
  });
5903
6476
  else try {
5904
- jsonLd = await require_proof.signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
6477
+ jsonLd = await require_owner.signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
5905
6478
  contextLoader,
5906
6479
  tracerProvider: this.tracerProvider
5907
6480
  });
@@ -5930,6 +6503,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5930
6503
  });
5931
6504
  const promises = [];
5932
6505
  for (const inbox in inboxes) promises.push(sendActivity({
6506
+ allowPrivateAddress: this.allowPrivateAddress,
5933
6507
  keys,
5934
6508
  activity: jsonLd,
5935
6509
  activityId: activity.id?.href,
@@ -6868,7 +7442,7 @@ var ContextImpl = class ContextImpl {
6868
7442
  const contextLoader = this.contextLoader;
6869
7443
  for (const kp of actorKeyPairs) {
6870
7444
  if (kp.privateKey.algorithm.name !== "Ed25519" || kp.multikey.id == null) continue;
6871
- activity = await require_proof.signObject(activity, kp.privateKey, kp.multikey.id, {
7445
+ activity = await require_owner.signObject(activity, kp.privateKey, kp.multikey.id, {
6872
7446
  contextLoader,
6873
7447
  tracerProvider: this.tracerProvider
6874
7448
  });
@@ -6998,7 +7572,7 @@ var ContextImpl = class ContextImpl {
6998
7572
  tracerProvider: this.tracerProvider,
6999
7573
  keyTtl: this.federation.publicKeyTtl
7000
7574
  });
7001
- if (await require_proof.verifyObject(_fedify_vocab.Activity, json, {
7575
+ if (await require_owner.verifyObject(_fedify_vocab.Activity, json, {
7002
7576
  contextLoader,
7003
7577
  documentLoader: options.documentLoader ?? this.documentLoader,
7004
7578
  meterProvider: this.meterProvider,
@@ -7155,7 +7729,7 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
7155
7729
  const key = await this.getSignedKey(options);
7156
7730
  if (key == null) return this.#signedKeyOwner = null;
7157
7731
  try {
7158
- return this.#signedKeyOwner = await require_proof.getKeyOwner(key, {
7732
+ return this.#signedKeyOwner = await require_owner.getKeyOwner(key, {
7159
7733
  contextLoader: options.contextLoader ?? this.contextLoader,
7160
7734
  documentLoader: options.documentLoader ?? this.documentLoader,
7161
7735
  tracerProvider: options.tracerProvider ?? this.tracerProvider
@@ -7225,8 +7799,8 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
7225
7799
  if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
7226
7800
  keys = forwarder;
7227
7801
  } else keys = [forwarder];
7228
- if (!require_proof.hasSignatureLike(ctx.activity)) {
7229
- if (!require_proof.hasProofLike(ctx.activity)) {
7802
+ if (!require_owner.hasSignatureLike(ctx.activity)) {
7803
+ if (!require_owner.hasProofLike(ctx.activity)) {
7230
7804
  if (options?.skipIfUnsigned) return false;
7231
7805
  logger.warn("The activity {activityId} is not signed; even if it is forwarded to other servers as is, it may not be accepted by them due to the lack of a signature/proof.", {
7232
7806
  activityId: ctx.activityId,
@@ -7264,6 +7838,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
7264
7838
  else logger.debug("Forwarding activity immediately without queue since queue is not set.");
7265
7839
  const promises = [];
7266
7840
  for (const inbox in inboxes) promises.push(sendActivity({
7841
+ allowPrivateAddress: ctx.federation.allowPrivateAddress,
7267
7842
  keys,
7268
7843
  activity: ctx.activity,
7269
7844
  activityId: ctx.activityId,