@fedify/fedify 2.4.0-dev.1988 → 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 (62) hide show
  1. package/dist/{builder-Cts78G9N.mjs → builder-D0DxgOV5.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/compound-proof-CJnWc6c-.mjs +495 -0
  4. package/dist/{deno-BgMEefEr.mjs → deno-CABhzCb_.mjs} +1 -1
  5. package/dist/{docloader-DEYs99FZ.mjs → docloader-BbGSL0Xq.mjs} +2 -2
  6. package/dist/federation/builder.test.mjs +1 -1
  7. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  8. package/dist/federation/handler-compound-proof.test.mjs +152 -0
  9. package/dist/federation/handler.test.mjs +5 -5
  10. package/dist/federation/idempotency.test.mjs +2 -2
  11. package/dist/federation/metrics.test.mjs +1 -1
  12. package/dist/federation/middleware.test.mjs +7 -7
  13. package/dist/federation/mod.cjs +1 -1
  14. package/dist/federation/mod.js +1 -1
  15. package/dist/federation/send.test.mjs +3 -3
  16. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  17. package/dist/federation/tasks/tasks.test.mjs +2 -2
  18. package/dist/federation/temporal.test.mjs +1 -1
  19. package/dist/federation/webfinger.test.mjs +1 -1
  20. package/dist/{federation-DzPUG_AP.cjs → federation-DCEHQk4G.cjs} +542 -43
  21. package/dist/{http-CgwqtxAy.js → http-9WyuldYh.js} +2 -2
  22. package/dist/{http-Z3anvCd-.cjs → http-CRFns-UE.cjs} +2 -2
  23. package/dist/{http-CtidZS_t.mjs → http-D9urSO2D.mjs} +3 -3
  24. package/dist/{key-Cwbg5ggV.mjs → key-Ccs8cnd7.mjs} +2 -2
  25. package/dist/{kv-cache-BM2-w-Wq.cjs → kv-cache-DXT9vmIg.cjs} +1 -1
  26. package/dist/{kv-cache-CykuL0ya.js → kv-cache-DtmXhPXG.js} +1 -1
  27. package/dist/{kv-cache-DHiR8yTf.mjs → kv-cache-lKyFn_Dn.mjs} +1 -1
  28. package/dist/{ld-CzhdWyTt.mjs → ld-qup__MAW.mjs} +3 -3
  29. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  30. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  31. package/dist/{metrics-CrqsHP0l.mjs → metrics-CISXCvB1.mjs} +1 -1
  32. package/dist/{middleware-DCpiAsZi.mjs → middleware-CzvJvPHK.mjs} +52 -20
  33. package/dist/{middleware-CZpYxSKD.mjs → middleware-DkrU2WhY.mjs} +1 -1
  34. package/dist/{middleware-CeCj5caM.js → middleware-DsfWajaD.js} +511 -12
  35. package/dist/mod.cjs +19 -19
  36. package/dist/mod.js +4 -4
  37. package/dist/nodeinfo/handler.test.mjs +1 -1
  38. package/dist/{proof-CUghqhAf.js → owner-BIPEZGiU.js} +1239 -1139
  39. package/dist/{owner-BqVXJLW8.mjs → owner-CRoq8Spb.mjs} +2 -2
  40. package/dist/{proof-DHu5zI0Z.cjs → owner-CVA7L9MA.cjs} +1252 -1140
  41. package/dist/{proof-DL8Iid3a.mjs → proof-BDxNVZ3E.mjs} +176 -76
  42. package/dist/{send-6jS4xFsX.mjs → send-BzM5GLyn.mjs} +3 -3
  43. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  44. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  45. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  46. package/dist/sig/compound-proof-verification.test.mjs +465 -0
  47. package/dist/sig/compound-proof.test.d.mts +2 -0
  48. package/dist/sig/compound-proof.test.mjs +324 -0
  49. package/dist/sig/http.test.mjs +2 -2
  50. package/dist/sig/key.test.mjs +1 -1
  51. package/dist/sig/ld.test.mjs +2 -2
  52. package/dist/sig/mod.cjs +17 -17
  53. package/dist/sig/mod.js +2 -2
  54. package/dist/sig/owner.test.mjs +1 -1
  55. package/dist/sig/proof.test.mjs +1 -1
  56. package/dist/{temporal-DcyzeytP.mjs → temporal-Dv6Qv-8k.mjs} +1 -1
  57. package/dist/testing/kv.test.mjs +1 -1
  58. package/dist/utils/docloader.test.mjs +2 -2
  59. package/dist/utils/kv-cache.test.mjs +1 -1
  60. package/dist/utils/mod.cjs +1 -1
  61. package/dist/utils/mod.js +1 -1
  62. package/package.json +6 -6
@@ -2,10 +2,10 @@ import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./rolldown-runtime-BC-Eo5Mj.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-DAtXl_Kc.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-CgwqtxAy.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-9WyuldYh.js";
6
6
  import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-C_cNlNVK.js";
7
- import { S as verifyCompactJsonLd, T as wrapContextLoaderForJsonLd, _ as hasSignature, b as isInvalidUrlTypeError, c as doesActorOwnKey, d as assertSafeJsonLd, g as getNormalizationContextLoader, h as detachSignature, i as verifyObject, l as getKeyOwner, n as hasProofLike, p as compactJsonLd, r as signObject, s as normalizeOutgoingActivityJsonLd, u as InvalidContextReferenceError, v as hasSignatureLike, x as signJsonLd, y as isClearlyMalformedContextReference } from "./proof-CUghqhAf.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CykuL0ya.js";
7
+ import { C as verifyCompactJsonLd, D as normalizeOutgoingActivityJsonLd, E as wrapContextLoaderForJsonLd, S as signJsonLd, _ as getNormalizationContextLoader, a as signObject, b as isClearlyMalformedContextReference, d as InvalidContextReferenceError, f as assertSafeJsonLd, g as detachSignature, i as hasProofLike, l as verifyPortableObjectProofPolicy, m as compactJsonLd, n as getKeyOwner, o as verifyMapLocalProof, s as verifyObject, t as doesActorOwnKey, v as hasSignature, x as isInvalidUrlTypeError, y as hasSignatureLike } from "./owner-BIPEZGiU.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DtmXhPXG.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";
@@ -13,8 +13,8 @@ import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "
13
13
  import { encodeHex } from "byte-encodings/hex";
14
14
  import { FetchError, UrlError, getDocumentLoader, validatePublicUrl } from "@fedify/vocab-runtime";
15
15
  import { uniq } from "es-toolkit";
16
- import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
17
16
  import jsonld from "@fedify/vocab-runtime/jsonld";
17
+ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
18
18
  import { lookupWebFinger } from "@fedify/webfinger";
19
19
  import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
20
20
  import { parse, stringifyAsync } from "devalue";
@@ -1296,6 +1296,474 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
1296
1296
  return `collectionId="${collectionId}", url="${url}", digest="${encodeHex(await digest(actorIds))}"`;
1297
1297
  }
1298
1298
  //#endregion
1299
+ //#region src/sig/compound-proof.ts
1300
+ const textEncoder = new TextEncoder();
1301
+ const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
1302
+ function isJsonMap(value) {
1303
+ return typeof value === "object" && value != null && !Array.isArray(value);
1304
+ }
1305
+ function isCompoundProofJsonObject(value) {
1306
+ return typeof value === "object" && value != null && !Array.isArray(value);
1307
+ }
1308
+ function hasOnlyDataProperties(value) {
1309
+ try {
1310
+ return Reflect.ownKeys(value).every((key) => {
1311
+ if (typeof key !== "string") return false;
1312
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1313
+ return descriptor != null && "value" in descriptor && descriptor.enumerable;
1314
+ });
1315
+ } catch {
1316
+ return false;
1317
+ }
1318
+ }
1319
+ function hasOnlyJsonArrayProperties(value) {
1320
+ try {
1321
+ const keys = Reflect.ownKeys(value);
1322
+ if (keys.length !== value.length + 1) return false;
1323
+ for (const key of keys) {
1324
+ if (key === "length") continue;
1325
+ if (typeof key !== "string") return false;
1326
+ const index = Number(key);
1327
+ if (!Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== key) return false;
1328
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1329
+ if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) return false;
1330
+ }
1331
+ return true;
1332
+ } catch {
1333
+ return false;
1334
+ }
1335
+ }
1336
+ function escapeJsonPointerSegment(segment) {
1337
+ return segment.replaceAll("~", "~0").replaceAll("/", "~1");
1338
+ }
1339
+ function childPath(path, segment) {
1340
+ return `${path}/${escapeJsonPointerSegment(segment)}`;
1341
+ }
1342
+ function comparePaths(left, right) {
1343
+ return left < right ? -1 : left > right ? 1 : 0;
1344
+ }
1345
+ function encodedLength(value) {
1346
+ return textEncoder.encode(value).byteLength;
1347
+ }
1348
+ function validateLimits(limits) {
1349
+ 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.`);
1350
+ }
1351
+ function limitExceeded(limit, maximum, actual, path) {
1352
+ return {
1353
+ status: "unsupported",
1354
+ reason: {
1355
+ type: "limitExceeded",
1356
+ limit,
1357
+ maximum,
1358
+ actual,
1359
+ path
1360
+ }
1361
+ };
1362
+ }
1363
+ function addBytes(statistics, bytes, limits, path) {
1364
+ statistics.byteLength += bytes;
1365
+ if (statistics.byteLength > limits.maxBytes) return limitExceeded("bytes", limits.maxBytes, statistics.byteLength, path);
1366
+ }
1367
+ function inspectJsonTree(root, limits) {
1368
+ if (!isJsonMap(root)) return {
1369
+ status: "unsupported",
1370
+ reason: {
1371
+ type: "invalidJsonTree",
1372
+ path: ""
1373
+ }
1374
+ };
1375
+ const statistics = {
1376
+ byteLength: 0,
1377
+ mapCount: 0,
1378
+ proofCount: 0,
1379
+ maxDepth: 0
1380
+ };
1381
+ const seen = /* @__PURE__ */ new Set();
1382
+ const pending = [{
1383
+ value: root,
1384
+ path: "",
1385
+ depth: 0,
1386
+ insideProof: false,
1387
+ insideContext: false
1388
+ }];
1389
+ while (pending.length > 0) {
1390
+ const current = pending.pop();
1391
+ statistics.maxDepth = Math.max(statistics.maxDepth, current.depth);
1392
+ if (current.depth > limits.maxDepth) return limitExceeded("depth", limits.maxDepth, current.depth, current.path);
1393
+ const value = current.value;
1394
+ if (value === null) {
1395
+ const failure = addBytes(statistics, 4, limits, current.path);
1396
+ if (failure != null) return failure;
1397
+ } else if (typeof value === "boolean") {
1398
+ const failure = addBytes(statistics, value ? 4 : 5, limits, current.path);
1399
+ if (failure != null) return failure;
1400
+ } else if (typeof value === "string") {
1401
+ const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
1402
+ if (failure != null) return failure;
1403
+ } else if (typeof value === "number") {
1404
+ if (!Number.isFinite(value)) return {
1405
+ status: "unsupported",
1406
+ reason: {
1407
+ type: "invalidJsonTree",
1408
+ path: current.path
1409
+ }
1410
+ };
1411
+ const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
1412
+ if (failure != null) return failure;
1413
+ } else if (Array.isArray(value)) {
1414
+ if (seen.has(value) || !hasOnlyJsonArrayProperties(value)) return {
1415
+ status: "unsupported",
1416
+ reason: {
1417
+ type: "invalidJsonTree",
1418
+ path: current.path
1419
+ }
1420
+ };
1421
+ seen.add(value);
1422
+ const failure = addBytes(statistics, 2 + Math.max(0, value.length - 1), limits, current.path);
1423
+ if (failure != null) return failure;
1424
+ for (let index = value.length - 1; index >= 0; index--) pending.push({
1425
+ value: value[index],
1426
+ path: childPath(current.path, String(index)),
1427
+ depth: current.depth + 1,
1428
+ insideProof: current.insideProof,
1429
+ insideContext: current.insideContext
1430
+ });
1431
+ } else if (isJsonMap(value)) {
1432
+ const prototype = Object.getPrototypeOf(value);
1433
+ if (seen.has(value) || !hasOnlyDataProperties(value) || prototype !== Object.prototype && prototype !== null) return {
1434
+ status: "unsupported",
1435
+ reason: {
1436
+ type: "invalidJsonTree",
1437
+ path: current.path
1438
+ }
1439
+ };
1440
+ seen.add(value);
1441
+ statistics.mapCount++;
1442
+ if (statistics.mapCount > limits.maxMaps) return limitExceeded("maps", limits.maxMaps, statistics.mapCount, current.path);
1443
+ const entries = Object.entries(value).sort(([left], [right]) => comparePaths(left, right));
1444
+ let objectBytes = 2 + Math.max(0, entries.length - 1);
1445
+ for (const [key] of entries) objectBytes += encodedLength(JSON.stringify(key)) + 1;
1446
+ const failure = addBytes(statistics, objectBytes, limits, current.path);
1447
+ if (failure != null) return failure;
1448
+ const hasDirectProof = !current.insideProof && !current.insideContext && Object.hasOwn(value, "proof");
1449
+ if (hasDirectProof) {
1450
+ statistics.proofCount++;
1451
+ if (statistics.proofCount > limits.maxProofs) return limitExceeded("proofs", limits.maxProofs, statistics.proofCount, childPath(current.path, "proof"));
1452
+ if (!isJsonMap(value.proof)) return {
1453
+ status: "unsupported",
1454
+ reason: {
1455
+ type: "unsupportedProofShape",
1456
+ path: childPath(current.path, "proof")
1457
+ }
1458
+ };
1459
+ }
1460
+ for (let index = entries.length - 1; index >= 0; index--) {
1461
+ const [key, child] = entries[index];
1462
+ pending.push({
1463
+ value: child,
1464
+ path: childPath(current.path, key),
1465
+ depth: current.depth + 1,
1466
+ insideProof: current.insideProof || hasDirectProof && key === "proof",
1467
+ insideContext: current.insideContext || key === "@context"
1468
+ });
1469
+ }
1470
+ } else return {
1471
+ status: "unsupported",
1472
+ reason: {
1473
+ type: "invalidJsonTree",
1474
+ path: current.path
1475
+ }
1476
+ };
1477
+ }
1478
+ return statistics;
1479
+ }
1480
+ function freezeJsonTree(root) {
1481
+ const pending = [root];
1482
+ while (pending.length > 0) {
1483
+ const value = pending.pop();
1484
+ if (typeof value !== "object" || value == null) continue;
1485
+ if (Array.isArray(value)) for (const child of value) pending.push(child);
1486
+ else for (const child of Object.values(value)) pending.push(child);
1487
+ Object.freeze(value);
1488
+ }
1489
+ }
1490
+ function collectDocuments(snapshot) {
1491
+ const documents = [];
1492
+ const pending = [{
1493
+ value: snapshot,
1494
+ path: "",
1495
+ depth: 0
1496
+ }];
1497
+ while (pending.length > 0) {
1498
+ const current = pending.pop();
1499
+ if (Array.isArray(current.value)) {
1500
+ for (let index = current.value.length - 1; index >= 0; index--) pending.push({
1501
+ value: current.value[index],
1502
+ path: childPath(current.path, String(index)),
1503
+ depth: current.depth + 1
1504
+ });
1505
+ continue;
1506
+ }
1507
+ if (!isCompoundProofJsonObject(current.value)) continue;
1508
+ const document = current.value;
1509
+ if (Object.hasOwn(document, "proof")) {
1510
+ const proof = document.proof;
1511
+ const id = typeof document.id === "string" ? document.id : typeof document["@id"] === "string" ? document["@id"] : void 0;
1512
+ documents.push(Object.freeze({
1513
+ path: current.path,
1514
+ id,
1515
+ depth: current.depth,
1516
+ securedDocument: document,
1517
+ proof
1518
+ }));
1519
+ }
1520
+ const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
1521
+ for (let index = entries.length - 1; index >= 0; index--) {
1522
+ const [key, value] = entries[index];
1523
+ if (key === "proof" || key === "@context") continue;
1524
+ pending.push({
1525
+ value,
1526
+ path: childPath(current.path, key),
1527
+ depth: current.depth + 1
1528
+ });
1529
+ }
1530
+ }
1531
+ documents.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
1532
+ return documents;
1533
+ }
1534
+ function collectPortableObjects(snapshot) {
1535
+ const objects = [];
1536
+ const pending = [{
1537
+ value: snapshot,
1538
+ path: "",
1539
+ depth: 0
1540
+ }];
1541
+ while (pending.length > 0) {
1542
+ const current = pending.pop();
1543
+ if (Array.isArray(current.value)) {
1544
+ for (let index = current.value.length - 1; index >= 0; index--) pending.push({
1545
+ value: current.value[index],
1546
+ path: childPath(current.path, String(index)),
1547
+ depth: current.depth + 1
1548
+ });
1549
+ continue;
1550
+ }
1551
+ if (!isCompoundProofJsonObject(current.value)) continue;
1552
+ const document = current.value;
1553
+ const id = [document.id, document["@id"]].find((value) => typeof value === "string" && PORTABLE_OBJECT_ID_PATTERN.test(value));
1554
+ if (typeof id === "string") objects.push(Object.freeze({
1555
+ path: current.path,
1556
+ id,
1557
+ depth: current.depth,
1558
+ document
1559
+ }));
1560
+ const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
1561
+ for (let index = entries.length - 1; index >= 0; index--) {
1562
+ const [key, value] = entries[index];
1563
+ if (key === "proof" || key === "@context") continue;
1564
+ pending.push({
1565
+ value,
1566
+ path: childPath(current.path, key),
1567
+ depth: current.depth + 1
1568
+ });
1569
+ }
1570
+ }
1571
+ objects.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
1572
+ return objects;
1573
+ }
1574
+ /**
1575
+ * Determines whether a received JSON tree contains a portable object ID
1576
+ * outside a proof or context definition, without exceeding discovery limits.
1577
+ *
1578
+ * This is only an applicability check. It does not authenticate the input;
1579
+ * callers must still use {@link verifyCompoundPortableObjectProofs} to obtain
1580
+ * an atomic result from an immutable snapshot. `indeterminate` means the
1581
+ * applicability scan itself reached a limit, so a caller must not treat the
1582
+ * document as outside the compound profile.
1583
+ *
1584
+ * @internal
1585
+ */
1586
+ function inspectCompoundPortableObjectApplicability(json, limits) {
1587
+ validateLimits(limits);
1588
+ let byteLength = 0;
1589
+ let mapCount = 0;
1590
+ const pending = [{
1591
+ iterator: [json].values(),
1592
+ depth: 0
1593
+ }];
1594
+ const addByteLength = (length) => {
1595
+ byteLength += length;
1596
+ return byteLength <= limits.maxBytes;
1597
+ };
1598
+ while (pending.length > 0) {
1599
+ const frame = pending[pending.length - 1];
1600
+ const next = frame.iterator.next();
1601
+ if (next.done) {
1602
+ pending.pop();
1603
+ if (byteLength > limits.maxBytes) return "indeterminate";
1604
+ continue;
1605
+ }
1606
+ const value = next.value;
1607
+ if (frame.depth > limits.maxDepth) return "indeterminate";
1608
+ if (value === null) {
1609
+ if (!addByteLength(4)) return "indeterminate";
1610
+ } else if (typeof value === "boolean") {
1611
+ if (!addByteLength(value ? 4 : 5)) return "indeterminate";
1612
+ } else if (typeof value === "string" || typeof value === "number") {
1613
+ if (!addByteLength(encodedLength(JSON.stringify(value)))) return "indeterminate";
1614
+ } else if (Array.isArray(value)) {
1615
+ if (!addByteLength(2 + Math.max(0, value.length - 1))) return "indeterminate";
1616
+ pending.push({
1617
+ iterator: value.values(),
1618
+ depth: frame.depth + 1
1619
+ });
1620
+ } else if (isJsonMap(value)) {
1621
+ mapCount++;
1622
+ if (mapCount > limits.maxMaps) return "indeterminate";
1623
+ if ([value.id, value["@id"]].some((id) => typeof id === "string" && PORTABLE_OBJECT_ID_PATTERN.test(id))) return "present";
1624
+ if (!addByteLength(2)) return "indeterminate";
1625
+ const children = function* () {
1626
+ let first = true;
1627
+ for (const key in value) {
1628
+ if (!Object.hasOwn(value, key)) continue;
1629
+ if (key === "proof" || key === "@context") continue;
1630
+ if (!first && !addByteLength(1)) return;
1631
+ first = false;
1632
+ if (!addByteLength(encodedLength(JSON.stringify(key)) + 1)) return;
1633
+ yield value[key];
1634
+ }
1635
+ }();
1636
+ pending.push({
1637
+ iterator: children,
1638
+ depth: frame.depth + 1
1639
+ });
1640
+ }
1641
+ if (byteLength > limits.maxBytes) return "indeterminate";
1642
+ }
1643
+ return "absent";
1644
+ }
1645
+ /**
1646
+ * Discovers direct literal proof-bearing maps in an immutable JSON snapshot.
1647
+ *
1648
+ * Discovery is bounded and atomic. Unsupported proof shapes, non-JSON input,
1649
+ * or a resource-limit failure return no partial candidate list. Proof
1650
+ * configuration subtrees are validated and counted as JSON, but are not
1651
+ * themselves searched for secured documents.
1652
+ */
1653
+ function discoverCompoundProofDocuments(json, limits) {
1654
+ validateLimits(limits);
1655
+ let inspected;
1656
+ try {
1657
+ inspected = inspectJsonTree(json, limits);
1658
+ } catch {
1659
+ return {
1660
+ status: "unsupported",
1661
+ reason: {
1662
+ type: "invalidJsonTree",
1663
+ path: ""
1664
+ }
1665
+ };
1666
+ }
1667
+ if ("status" in inspected) return inspected;
1668
+ let snapshot;
1669
+ try {
1670
+ snapshot = structuredClone(json);
1671
+ } catch {
1672
+ return {
1673
+ status: "unsupported",
1674
+ reason: {
1675
+ type: "invalidJsonTree",
1676
+ path: ""
1677
+ }
1678
+ };
1679
+ }
1680
+ freezeJsonTree(snapshot);
1681
+ return {
1682
+ status: "ok",
1683
+ snapshot,
1684
+ documents: Object.freeze(collectDocuments(snapshot)),
1685
+ statistics: Object.freeze({ ...inspected })
1686
+ };
1687
+ }
1688
+ async function verifyDiscoveredProofDocuments(discovered, options) {
1689
+ const documents = await Promise.all(discovered.documents.map(async (document) => {
1690
+ const metadata = {
1691
+ path: document.path,
1692
+ ...document.id == null ? {} : { id: document.id },
1693
+ depth: document.depth
1694
+ };
1695
+ if (document.depth > 0 && !Object.hasOwn(document.securedDocument, "@context")) return Object.freeze({
1696
+ ...metadata,
1697
+ verified: false,
1698
+ reason: { type: "missingContext" }
1699
+ });
1700
+ const key = await verifyMapLocalProof(document.securedDocument, options);
1701
+ return key == null ? Object.freeze({
1702
+ ...metadata,
1703
+ verified: false,
1704
+ reason: { type: "invalidProof" }
1705
+ }) : Object.freeze({
1706
+ ...metadata,
1707
+ verified: true,
1708
+ key
1709
+ });
1710
+ }));
1711
+ return Object.freeze(documents);
1712
+ }
1713
+ /**
1714
+ * Applies FEP-ef61 portable-object policy to every portable map in a compound
1715
+ * snapshot without changing the map-local cryptographic inputs.
1716
+ *
1717
+ * @internal
1718
+ */
1719
+ async function verifyCompoundPortableObjectProofs(json, limits, options = {}) {
1720
+ const discovered = discoverCompoundProofDocuments(json, limits);
1721
+ if (discovered.status !== "ok") return discovered;
1722
+ const proofs = await verifyDiscoveredProofDocuments(discovered, options);
1723
+ const proofByPath = new Map(proofs.map((proof) => [proof.path, proof]));
1724
+ const portableObjects = await Promise.all(collectPortableObjects(discovered.snapshot).map(async (object) => {
1725
+ const metadata = {
1726
+ path: object.path,
1727
+ id: object.id,
1728
+ depth: object.depth
1729
+ };
1730
+ if (object.depth > 0 && !Object.hasOwn(object.document, "@context")) return Object.freeze({
1731
+ ...metadata,
1732
+ verified: false,
1733
+ reason: { type: "missingContext" }
1734
+ });
1735
+ const proof = proofByPath.get(object.path);
1736
+ const key = proof?.verified === true ? proof.key : null;
1737
+ try {
1738
+ const policy = await verifyPortableObjectProofPolicy(object.document, key, options);
1739
+ return policy.verified ? Object.freeze({
1740
+ ...metadata,
1741
+ id: policy.objectId,
1742
+ verified: true,
1743
+ keys: policy.keys
1744
+ }) : Object.freeze({
1745
+ ...metadata,
1746
+ verified: false,
1747
+ reason: policy.reason
1748
+ });
1749
+ } catch {
1750
+ return Object.freeze({
1751
+ ...metadata,
1752
+ verified: false,
1753
+ reason: { type: "invalidPortableObject" }
1754
+ });
1755
+ }
1756
+ }));
1757
+ return {
1758
+ status: "ok",
1759
+ verified: portableObjects.length > 0 && proofs.every((proof) => proof.verified) && portableObjects.every((object) => object.verified),
1760
+ snapshot: discovered.snapshot,
1761
+ proofs,
1762
+ portableObjects: Object.freeze(portableObjects),
1763
+ statistics: discovered.statistics
1764
+ };
1765
+ }
1766
+ //#endregion
1299
1767
  //#region src/federation/inbox.ts
1300
1768
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
1301
1769
  const logger = getLogger([
@@ -1739,6 +2207,12 @@ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1739
2207
  //#endregion
1740
2208
  //#region src/federation/handler.ts
1741
2209
  const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
2210
+ const INBOX_COMPOUND_PROOF_LIMITS = {
2211
+ maxDepth: 64,
2212
+ maxMaps: 1e4,
2213
+ maxProofs: 32,
2214
+ maxBytes: 10 * 1024 * 1024
2215
+ };
1742
2216
  function isRemoteContextLoadingFailure$1(error) {
1743
2217
  return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1744
2218
  }
@@ -1863,13 +2337,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
1863
2337
  * @param parameters The parameters for handling the collection.
1864
2338
  * @returns A promise that resolves to an HTTP response.
1865
2339
  */
1866
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
1867
- const spanName = name$2.trim().replace(/\s+/g, "_");
2340
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
2341
+ const spanName = name$1.trim().replace(/\s+/g, "_");
1868
2342
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
1869
2343
  const tracer = tracerProvider.getTracer(name, version);
1870
2344
  const cursor = new URL(request.url).searchParams.get("cursor");
1871
2345
  const metricBase = {
1872
- kind: getCollectionMetricKind(name$2),
2346
+ kind: getCollectionMetricKind(name$1),
1873
2347
  page: cursor != null,
1874
2348
  dispatcher: "built_in"
1875
2349
  };
@@ -1910,7 +2384,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1910
2384
  span.setStatus({ code: SpanStatusCode.ERROR });
1911
2385
  return await onNotFound(request);
1912
2386
  }
1913
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
2387
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
1914
2388
  itemCount = items.length;
1915
2389
  span.setAttribute("fedify.collection.items", itemCount);
1916
2390
  return items;
@@ -1950,7 +2424,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1950
2424
  } else {
1951
2425
  const uri = new URL(baseUri);
1952
2426
  uri.searchParams.set("cursor", cursor);
1953
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
2427
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1954
2428
  kind: SpanKind.SERVER,
1955
2429
  attributes: {
1956
2430
  "activitypub.collection.id": uri.href,
@@ -1966,7 +2440,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1966
2440
  span.setStatus({ code: SpanStatusCode.ERROR });
1967
2441
  return await onNotFound(request);
1968
2442
  }
1969
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
2443
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
1970
2444
  itemCount = items.length;
1971
2445
  span.setAttribute("fedify.collection.items", itemCount);
1972
2446
  return {
@@ -2860,6 +3334,31 @@ async function handleInboxInternal(request, parameters, span) {
2860
3334
  headers: { "Content-Type": "text/plain; charset=utf-8" }
2861
3335
  });
2862
3336
  }
3337
+ if (!skipSignatureVerification) {
3338
+ if (inspectCompoundPortableObjectApplicability(json, INBOX_COMPOUND_PROOF_LIMITS) !== "absent") {
3339
+ const compoundProof = await verifyCompoundPortableObjectProofs(json, INBOX_COMPOUND_PROOF_LIMITS, {
3340
+ documentLoader: ctx.documentLoader,
3341
+ keyCache,
3342
+ meterProvider,
3343
+ tracerProvider
3344
+ });
3345
+ if (compoundProof.status !== "ok" || !compoundProof.verified) {
3346
+ logger.error("Failed to verify compound portable Object Integrity Proofs.", {
3347
+ recipient,
3348
+ activity: json,
3349
+ reason: compoundProof.status === "unsupported" ? compoundProof.reason.type : "invalidProof"
3350
+ });
3351
+ span.setStatus({
3352
+ code: SpanStatusCode.ERROR,
3353
+ message: "Failed to verify compound portable Object Integrity Proofs."
3354
+ });
3355
+ return new Response("Failed to verify compound portable Object Integrity Proofs.", {
3356
+ status: 401,
3357
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
3358
+ });
3359
+ }
3360
+ }
3361
+ }
2863
3362
  if (pendingNonceLabel != null) {
2864
3363
  if (!await verifySignatureNonce(request, kv, kvPrefixes.acceptSignatureNonce, pendingNonceLabel)) {
2865
3364
  getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("invalidNonce", httpSigKey?.id == null ? void 0 : getRemoteHost(httpSigKey.id));
@@ -3034,8 +3533,8 @@ var CustomCollectionHandler = class {
3034
3533
  * @param CollectionPage The CollectionPage constructor.
3035
3534
  * @param filterPredicate Optional filter predicate for items.
3036
3535
  */
3037
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
3038
- this.name = name$1;
3536
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
3537
+ this.name = name$2;
3039
3538
  this.values = values;
3040
3539
  this.context = context;
3041
3540
  this.callbacks = callbacks;
package/dist/mod.cjs CHANGED
@@ -3,11 +3,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  require("./rolldown-runtime-Dmm3t92W.cjs");
4
4
  const require_transformers = require("./transformers-BalCrSsr.cjs");
5
5
  require("./compat/mod.cjs");
6
- const require_http = require("./http-Z3anvCd-.cjs");
7
- const require_federation = require("./federation-DzPUG_AP.cjs");
6
+ const require_http = require("./http-CRFns-UE.cjs");
7
+ const require_federation = require("./federation-DCEHQk4G.cjs");
8
8
  const require_types = require("./types-Be2zbyeG.cjs");
9
- const require_proof = require("./proof-DHu5zI0Z.cjs");
10
- const require_kv_cache = require("./kv-cache-BM2-w-Wq.cjs");
9
+ const require_owner = require("./owner-CVA7L9MA.cjs");
10
+ const require_kv_cache = require("./kv-cache-DXT9vmIg.cjs");
11
11
  require("./nodeinfo/mod.cjs");
12
12
  require("./runtime/mod.cjs");
13
13
  require("./sig/mod.cjs");
@@ -21,17 +21,17 @@ exports.Router = require_federation.Router;
21
21
  exports.RouterError = require_federation.RouterError;
22
22
  exports.SendActivityError = require_federation.SendActivityError;
23
23
  exports.actorDehydrator = require_transformers.actorDehydrator;
24
- exports.attachSignature = require_proof.attachSignature;
24
+ exports.attachSignature = require_owner.attachSignature;
25
25
  exports.autoIdAssigner = require_transformers.autoIdAssigner;
26
26
  exports.buildCollectionSynchronizationHeader = require_federation.buildCollectionSynchronizationHeader;
27
27
  exports.createExponentialBackoffPolicy = require_federation.createExponentialBackoffPolicy;
28
28
  exports.createFederation = require_federation.createFederation;
29
29
  exports.createFederationBuilder = require_federation.createFederationBuilder;
30
- exports.createProof = require_proof.createProof;
31
- exports.createSignature = require_proof.createSignature;
32
- exports.detachSignature = require_proof.detachSignature;
30
+ exports.createProof = require_owner.createProof;
31
+ exports.createSignature = require_owner.createSignature;
32
+ exports.detachSignature = require_owner.detachSignature;
33
33
  exports.digest = require_federation.digest;
34
- exports.doesActorOwnKey = require_proof.doesActorOwnKey;
34
+ exports.doesActorOwnKey = require_owner.doesActorOwnKey;
35
35
  exports.exportJwk = require_http.exportJwk;
36
36
  exports.fetchKey = require_http.fetchKey;
37
37
  exports.fetchKeyDetailed = require_http.fetchKeyDetailed;
@@ -40,11 +40,11 @@ exports.fulfillAcceptSignature = require_http.fulfillAcceptSignature;
40
40
  exports.generateCryptoKeyPair = require_http.generateCryptoKeyPair;
41
41
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
42
42
  exports.getDefaultActivityTransformers = require_transformers.getDefaultActivityTransformers;
43
- exports.getKeyOwner = require_proof.getKeyOwner;
43
+ exports.getKeyOwner = require_owner.getKeyOwner;
44
44
  exports.getNodeInfo = require_types.getNodeInfo;
45
45
  exports.handleWebFinger = require_federation.handleWebFinger;
46
- exports.hasProofLike = require_proof.hasProofLike;
47
- exports.hasSignatureLike = require_proof.hasSignatureLike;
46
+ exports.hasProofLike = require_owner.hasProofLike;
47
+ exports.hasSignatureLike = require_owner.hasSignatureLike;
48
48
  exports.importJwk = require_http.importJwk;
49
49
  exports.kvCache = require_kv_cache.kvCache;
50
50
  exports.nodeInfoToJson = require_types.nodeInfoToJson;
@@ -54,17 +54,17 @@ exports.parseCircuitBreakerKvState = require_federation.parseCircuitBreakerKvSta
54
54
  exports.parseNodeInfo = require_types.parseNodeInfo;
55
55
  exports.respondWithObject = require_federation.respondWithObject;
56
56
  exports.respondWithObjectIfAcceptable = require_federation.respondWithObjectIfAcceptable;
57
- exports.signJsonLd = require_proof.signJsonLd;
58
- exports.signObject = require_proof.signObject;
57
+ exports.signJsonLd = require_owner.signJsonLd;
58
+ exports.signObject = require_owner.signObject;
59
59
  exports.signRequest = require_http.signRequest;
60
60
  exports.validateAcceptSignature = require_http.validateAcceptSignature;
61
- exports.verifyJsonLd = require_proof.verifyJsonLd;
62
- exports.verifyObject = require_proof.verifyObject;
63
- exports.verifyPortableObjectProof = require_proof.verifyPortableObjectProof;
64
- exports.verifyProof = require_proof.verifyProof;
61
+ exports.verifyJsonLd = require_owner.verifyJsonLd;
62
+ exports.verifyObject = require_owner.verifyObject;
63
+ exports.verifyPortableObjectProof = require_owner.verifyPortableObjectProof;
64
+ exports.verifyProof = require_owner.verifyProof;
65
65
  exports.verifyRequest = require_http.verifyRequest;
66
66
  exports.verifyRequestDetailed = require_http.verifyRequestDetailed;
67
- exports.verifySignature = require_proof.verifySignature;
67
+ exports.verifySignature = require_owner.verifySignature;
68
68
  var _fedify_vocab_runtime = require("@fedify/vocab-runtime");
69
69
  Object.keys(_fedify_vocab_runtime).forEach(function(k) {
70
70
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {