@fedify/vocab-runtime 2.1.30 → 2.1.31

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 (114) hide show
  1. package/deno.json +5 -2
  2. package/dist/internal.cjs +6 -0
  3. package/dist/internal.d.cts +3 -0
  4. package/dist/internal.d.ts +3 -0
  5. package/dist/internal.js +3 -0
  6. package/dist/mod.cjs +105 -253
  7. package/dist/mod.d.cts +5 -27
  8. package/dist/mod.d.ts +6 -27
  9. package/dist/mod.js +98 -245
  10. package/dist/public-fetch-BC-nWJNM.d.ts +143 -0
  11. package/dist/public-fetch-CFnW7XMn.cjs +821 -0
  12. package/dist/public-fetch-DBpt3xxf.d.cts +143 -0
  13. package/dist/public-fetch-Dfmc_Kfy.js +772 -0
  14. package/dist/tests/{body-C7C_CqNA.mjs → body-B0Z2Vegb.mjs} +1 -1
  15. package/dist/tests/{body-Bda70xVu.cjs → body-i_FdQE3f.cjs} +1 -1
  16. package/dist/tests/body.test.cjs +1 -1
  17. package/dist/tests/body.test.mjs +1 -1
  18. package/dist/tests/decimal.test.cjs +6 -6
  19. package/dist/tests/decimal.test.mjs +6 -6
  20. package/dist/tests/{docloader-Bsosn1qB.mjs → docloader-dwostFF6.mjs} +102 -37
  21. package/dist/tests/{docloader-D5BTSARP.cjs → docloader-oZrKg7F3.cjs} +106 -35
  22. package/dist/tests/docloader.test.cjs +522 -1272
  23. package/dist/tests/docloader.test.mjs +468 -1237
  24. package/dist/tests/esm-CnLt0wAF.mjs +1225 -0
  25. package/dist/tests/esm-CxbqfkLP.cjs +1208 -0
  26. package/dist/tests/internal/multicodec.test.cjs +1 -1
  27. package/dist/tests/internal/multicodec.test.mjs +1 -1
  28. package/dist/tests/{key-BDIsC6hV.mjs → key-GEdpxA4M.mjs} +2 -2
  29. package/dist/tests/{key-BarKGLQz.cjs → key-L6ReMuUb.cjs} +2 -2
  30. package/dist/tests/key.test.cjs +2 -2
  31. package/dist/tests/key.test.mjs +2 -2
  32. package/dist/tests/{langstr-BsqFJupv.cjs → langstr-CZpWmpo5.cjs} +1 -1
  33. package/dist/tests/{langstr-ABdAOKlF.mjs → langstr-DTB4sl0a.mjs} +1 -1
  34. package/dist/tests/langstr.test.cjs +1 -1
  35. package/dist/tests/langstr.test.mjs +1 -1
  36. package/dist/tests/langtag.test.cjs +1 -1
  37. package/dist/tests/langtag.test.mjs +1 -1
  38. package/dist/tests/link.test.mjs +1 -1
  39. package/dist/tests/multibase/multibase.test.cjs +1 -1
  40. package/dist/tests/multibase/multibase.test.mjs +1 -1
  41. package/dist/tests/node-C3bjFjL1.mjs +84 -0
  42. package/dist/tests/node-DWD76vPo.cjs +90 -0
  43. package/dist/tests/pin-grant-CU-o367k.cjs +70 -0
  44. package/dist/tests/pin-grant-HW3B6SKv.mjs +53 -0
  45. package/dist/tests/pin-grant.test.cjs +88 -0
  46. package/dist/tests/pin-grant.test.d.cts +1 -0
  47. package/dist/tests/pin-grant.test.d.mts +1 -0
  48. package/dist/tests/pin-grant.test.mjs +88 -0
  49. package/dist/tests/pinners/bun.test.cjs +167 -0
  50. package/dist/tests/pinners/bun.test.d.cts +1 -0
  51. package/dist/tests/pinners/bun.test.d.mts +1 -0
  52. package/dist/tests/pinners/bun.test.mjs +167 -0
  53. package/dist/tests/pinners/node.test.cjs +269 -0
  54. package/dist/tests/pinners/node.test.d.cts +1 -0
  55. package/dist/tests/pinners/node.test.d.mts +1 -0
  56. package/dist/tests/pinners/node.test.mjs +268 -0
  57. package/dist/tests/pinners/pinners.test.cjs +197 -0
  58. package/dist/tests/pinners/pinners.test.d.cts +1 -0
  59. package/dist/tests/pinners/pinners.test.d.mts +1 -0
  60. package/dist/tests/pinners/pinners.test.mjs +197 -0
  61. package/dist/tests/pinners/testdata/node-child.cjs +135 -0
  62. package/dist/tests/pinners/testdata/node-child.d.cts +1 -0
  63. package/dist/tests/pinners/testdata/node-child.d.mts +1 -0
  64. package/dist/tests/pinners/testdata/node-child.mjs +134 -0
  65. package/dist/tests/pinners-BjE-YJLL.cjs +221 -0
  66. package/dist/tests/pinners-ZgZDx9y6.mjs +203 -0
  67. package/dist/tests/public-fetch-Ddjfxbwc.cjs +187 -0
  68. package/dist/tests/public-fetch-Yi7Vks9s.mjs +170 -0
  69. package/dist/tests/public-fetch.test.cjs +1074 -0
  70. package/dist/tests/public-fetch.test.d.cts +1 -0
  71. package/dist/tests/public-fetch.test.d.mts +1 -0
  72. package/dist/tests/public-fetch.test.mjs +1074 -0
  73. package/dist/tests/{request-hHQ_KvBF.cjs → request-CzYw8aEp.cjs} +1 -1
  74. package/dist/tests/{request-Bri8X69i.mjs → request-DY2AaRpH.mjs} +1 -1
  75. package/dist/tests/request.test.cjs +1 -1
  76. package/dist/tests/request.test.mjs +1 -1
  77. package/dist/tests/support-D5-KVjWZ.cjs +363 -0
  78. package/dist/tests/support-M5ClN-no.mjs +272 -0
  79. package/dist/tests/{url-vZTgkIZg.cjs → url-DLuSj57U.cjs} +88 -12
  80. package/dist/tests/{url-3fyJuqC0.mjs → url-DQWrMGhu.mjs} +65 -13
  81. package/dist/tests/url.test.cjs +4 -1
  82. package/dist/tests/url.test.mjs +4 -1
  83. package/package.json +14 -3
  84. package/src/docloader.test.ts +696 -31
  85. package/src/docloader.ts +174 -67
  86. package/src/internal.ts +13 -0
  87. package/src/pin-grant.test.ts +118 -0
  88. package/src/pin-grant.ts +99 -0
  89. package/src/pinners/bun.test.ts +278 -0
  90. package/src/pinners/bun.ts +223 -0
  91. package/src/pinners/mod.ts +59 -0
  92. package/src/pinners/node.test.ts +415 -0
  93. package/src/pinners/node.ts +144 -0
  94. package/src/pinners/pinners.test.ts +316 -0
  95. package/src/pinners/testdata/node-child.ts +147 -0
  96. package/src/pinners/testdata/proxy-child.ts +82 -0
  97. package/src/pinners/testdata/support.ts +343 -0
  98. package/src/pinners/testdata/tls-cert.pem +12 -0
  99. package/src/pinners/testdata/tls-child.ts +88 -0
  100. package/src/pinners/testdata/tls-key.pem +5 -0
  101. package/src/pinners/types.ts +48 -0
  102. package/src/public-fetch.test.ts +1286 -0
  103. package/src/public-fetch.ts +338 -0
  104. package/src/testdata/public-fetch-deno-worker.ts +74 -0
  105. package/src/url.test.ts +11 -0
  106. package/src/url.ts +113 -22
  107. package/tsdown.config.ts +12 -3
  108. /package/dist/tests/{langtag-LDBqlxHv.mjs → langtag-D8FWxjv8.mjs} +0 -0
  109. /package/dist/tests/{langtag-DgySESmk.cjs → langtag-a2-s9uyi.cjs} +0 -0
  110. /package/dist/tests/{link-CYszIegt.mjs → link-CHMtH2-k.mjs} +0 -0
  111. /package/dist/tests/{multibase-8zhYPa7o.cjs → multibase-BmWaK56H.cjs} +0 -0
  112. /package/dist/tests/{multibase-Ds75BvXs.mjs → multibase-D642Uv3S.mjs} +0 -0
  113. /package/dist/tests/{multicodec-ByS9XLsw.cjs → multicodec-B-_CSt1M.cjs} +0 -0
  114. /package/dist/tests/{multicodec-BBPZjRfg.mjs → multicodec-caWdG98t.mjs} +0 -0
@@ -18,28 +18,65 @@ var UrlError = class extends Error {
18
18
  };
19
19
  /**
20
20
  * Validates a URL to prevent SSRF attacks.
21
+ *
22
+ * This function only checks the addresses that the hostname currently
23
+ * resolves to; it does not pin the connection to those addresses. A request
24
+ * made after this check resolves the hostname again, so this function alone
25
+ * does not prevent DNS rebinding.
21
26
  */
22
27
  async function validatePublicUrl(url) {
23
- const parsed = new URL(url);
24
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
25
- let hostname = parsed.hostname;
26
- if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
27
- if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
28
- const hostnameFamily = (0, node_net.isIP)(hostname);
29
- if (hostnameFamily !== 0) {
30
- validatePublicIpAddress(hostname, hostnameFamily);
31
- return;
32
- }
33
- if ("Deno" in globalThis && !(0, node_net.isIP)(hostname)) {
28
+ const { hostname, family } = parsePublicUrl(url);
29
+ if (family !== 0) return;
30
+ if ("Deno" in globalThis) {
34
31
  if ((await Deno.permissions.query({ name: "net" })).state !== "granted") return;
35
32
  }
36
33
  if ("Bun" in globalThis) {
37
34
  if (hostname === "example.com" || hostname.endsWith(".example.com")) return;
38
35
  else if (hostname === "fedify-test.internal") throw new UrlError("Invalid or private address: fedify-test.internal");
39
36
  }
37
+ await lookupPublicAddresses(hostname, lookupAllAddresses);
38
+ }
39
+ /**
40
+ * Resolves all addresses of the given hostname through `node:dns/promises`.
41
+ * The default `dns` object is looked up on every call so that tests can still
42
+ * stub `dns.lookup()`.
43
+ * @internal
44
+ */
45
+ function lookupAllAddresses(hostname) {
46
+ return node_dns_promises.default.lookup(hostname, { all: true });
47
+ }
48
+ /**
49
+ * Checks the parts of a URL that can be validated without resolving it:
50
+ * the protocol, the `localhost` hostname, and IP literal hostnames.
51
+ * @throws {UrlError} If the URL is not allowed.
52
+ * @internal
53
+ */
54
+ function parsePublicUrl(url) {
55
+ const parsed = new URL(url);
56
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
57
+ let hostname = parsed.hostname;
58
+ if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
59
+ if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
60
+ const family = (0, node_net.isIP)(hostname);
61
+ if (family !== 0) validatePublicIpAddress(hostname, family);
62
+ return {
63
+ url: parsed,
64
+ hostname,
65
+ family
66
+ };
67
+ }
68
+ /**
69
+ * Resolves the given hostname and checks that every returned IP address is
70
+ * public.
71
+ * @returns The IP addresses of the hostname, without non-IP entries.
72
+ * @throws {UrlError} If the lookup fails, returns no IP address, or returns
73
+ * any non-public address.
74
+ * @internal
75
+ */
76
+ async function lookupPublicAddresses(hostname, lookup) {
40
77
  let addresses;
41
78
  try {
42
- addresses = await node_dns_promises.default.lookup(hostname, { all: true });
79
+ addresses = await lookup(hostname);
43
80
  } catch (error) {
44
81
  throw new UrlError("DNS lookup failed", {
45
82
  cause: error,
@@ -47,6 +84,21 @@ async function validatePublicUrl(url) {
47
84
  });
48
85
  }
49
86
  validateLookupAddresses(addresses);
87
+ return addresses.filter(({ address }) => (0, node_net.isIP)(address) !== 0).map(({ address }) => ({
88
+ address,
89
+ family: (0, node_net.isIP)(address)
90
+ }));
91
+ }
92
+ /**
93
+ * Checks that the given IP address is public. This is used right before
94
+ * connecting to an address that passed {@link lookupPublicAddresses}.
95
+ * @throws {UrlError} If the address is not a public IP address.
96
+ * @internal
97
+ */
98
+ function assertPublicAddress(address) {
99
+ const family = (0, node_net.isIP)(address.address);
100
+ if (family === 0 || family !== address.family) throw new UrlError(`Invalid or private address: ${address.address}`);
101
+ validatePublicIpAddress(address.address, family);
50
102
  }
51
103
  /**
52
104
  * Validates the IP addresses returned by `node:dns.lookup()`.
@@ -218,6 +270,12 @@ Object.defineProperty(exports, "UrlError", {
218
270
  return UrlError;
219
271
  }
220
272
  });
273
+ Object.defineProperty(exports, "assertPublicAddress", {
274
+ enumerable: true,
275
+ get: function() {
276
+ return assertPublicAddress;
277
+ }
278
+ });
221
279
  Object.defineProperty(exports, "expandIPv6Address", {
222
280
  enumerable: true,
223
281
  get: function() {
@@ -236,6 +294,24 @@ Object.defineProperty(exports, "isValidPublicIPv6Address", {
236
294
  return isValidPublicIPv6Address;
237
295
  }
238
296
  });
297
+ Object.defineProperty(exports, "lookupAllAddresses", {
298
+ enumerable: true,
299
+ get: function() {
300
+ return lookupAllAddresses;
301
+ }
302
+ });
303
+ Object.defineProperty(exports, "lookupPublicAddresses", {
304
+ enumerable: true,
305
+ get: function() {
306
+ return lookupPublicAddresses;
307
+ }
308
+ });
309
+ Object.defineProperty(exports, "parsePublicUrl", {
310
+ enumerable: true,
311
+ get: function() {
312
+ return parsePublicUrl;
313
+ }
314
+ });
239
315
  Object.defineProperty(exports, "validateLookupAddresses", {
240
316
  enumerable: true,
241
317
  get: function() {
@@ -16,28 +16,65 @@ var UrlError = class extends Error {
16
16
  };
17
17
  /**
18
18
  * Validates a URL to prevent SSRF attacks.
19
+ *
20
+ * This function only checks the addresses that the hostname currently
21
+ * resolves to; it does not pin the connection to those addresses. A request
22
+ * made after this check resolves the hostname again, so this function alone
23
+ * does not prevent DNS rebinding.
19
24
  */
20
25
  async function validatePublicUrl(url) {
21
- const parsed = new URL(url);
22
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
23
- let hostname = parsed.hostname;
24
- if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
25
- if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
26
- const hostnameFamily = isIP(hostname);
27
- if (hostnameFamily !== 0) {
28
- validatePublicIpAddress(hostname, hostnameFamily);
29
- return;
30
- }
31
- if ("Deno" in globalThis && !isIP(hostname)) {
26
+ const { hostname, family } = parsePublicUrl(url);
27
+ if (family !== 0) return;
28
+ if ("Deno" in globalThis) {
32
29
  if ((await Deno.permissions.query({ name: "net" })).state !== "granted") return;
33
30
  }
34
31
  if ("Bun" in globalThis) {
35
32
  if (hostname === "example.com" || hostname.endsWith(".example.com")) return;
36
33
  else if (hostname === "fedify-test.internal") throw new UrlError("Invalid or private address: fedify-test.internal");
37
34
  }
35
+ await lookupPublicAddresses(hostname, lookupAllAddresses);
36
+ }
37
+ /**
38
+ * Resolves all addresses of the given hostname through `node:dns/promises`.
39
+ * The default `dns` object is looked up on every call so that tests can still
40
+ * stub `dns.lookup()`.
41
+ * @internal
42
+ */
43
+ function lookupAllAddresses(hostname) {
44
+ return dns.lookup(hostname, { all: true });
45
+ }
46
+ /**
47
+ * Checks the parts of a URL that can be validated without resolving it:
48
+ * the protocol, the `localhost` hostname, and IP literal hostnames.
49
+ * @throws {UrlError} If the URL is not allowed.
50
+ * @internal
51
+ */
52
+ function parsePublicUrl(url) {
53
+ const parsed = new URL(url);
54
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
55
+ let hostname = parsed.hostname;
56
+ if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
57
+ if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
58
+ const family = isIP(hostname);
59
+ if (family !== 0) validatePublicIpAddress(hostname, family);
60
+ return {
61
+ url: parsed,
62
+ hostname,
63
+ family
64
+ };
65
+ }
66
+ /**
67
+ * Resolves the given hostname and checks that every returned IP address is
68
+ * public.
69
+ * @returns The IP addresses of the hostname, without non-IP entries.
70
+ * @throws {UrlError} If the lookup fails, returns no IP address, or returns
71
+ * any non-public address.
72
+ * @internal
73
+ */
74
+ async function lookupPublicAddresses(hostname, lookup) {
38
75
  let addresses;
39
76
  try {
40
- addresses = await dns.lookup(hostname, { all: true });
77
+ addresses = await lookup(hostname);
41
78
  } catch (error) {
42
79
  throw new UrlError("DNS lookup failed", {
43
80
  cause: error,
@@ -45,6 +82,21 @@ async function validatePublicUrl(url) {
45
82
  });
46
83
  }
47
84
  validateLookupAddresses(addresses);
85
+ return addresses.filter(({ address }) => isIP(address) !== 0).map(({ address }) => ({
86
+ address,
87
+ family: isIP(address)
88
+ }));
89
+ }
90
+ /**
91
+ * Checks that the given IP address is public. This is used right before
92
+ * connecting to an address that passed {@link lookupPublicAddresses}.
93
+ * @throws {UrlError} If the address is not a public IP address.
94
+ * @internal
95
+ */
96
+ function assertPublicAddress(address) {
97
+ const family = isIP(address.address);
98
+ if (family === 0 || family !== address.family) throw new UrlError(`Invalid or private address: ${address.address}`);
99
+ validatePublicIpAddress(address.address, family);
48
100
  }
49
101
  /**
50
102
  * Validates the IP addresses returned by `node:dns.lookup()`.
@@ -210,4 +262,4 @@ function matchesIPv6Prefix(address, prefixWords, prefixLength) {
210
262
  return true;
211
263
  }
212
264
  //#endregion
213
- export { validateLookupAddresses as a, isValidPublicIPv6Address as i, expandIPv6Address as n, validatePublicUrl as o, isValidPublicIPv4Address as r, UrlError as t };
265
+ export { isValidPublicIPv6Address as a, parsePublicUrl as c, isValidPublicIPv4Address as i, validateLookupAddresses as l, assertPublicAddress as n, lookupAllAddresses as o, expandIPv6Address as r, lookupPublicAddresses as s, UrlError as t, validatePublicUrl as u };
@@ -1,5 +1,5 @@
1
1
  require("./chunk-Do9eywBl.cjs");
2
- const require_url = require("./url-vZTgkIZg.cjs");
2
+ const require_url = require("./url-DLuSj57U.cjs");
3
3
  let node_test = require("node:test");
4
4
  let node_assert = require("node:assert");
5
5
  //#region src/url.test.ts
@@ -52,6 +52,9 @@ let node_assert = require("node:assert");
52
52
  await require_url.validatePublicUrl("https://[2001:db8::1]");
53
53
  await require_url.validatePublicUrl("https://[64:ff9b::8.8.8.8]");
54
54
  });
55
+ (0, node_test.test)("validatePublicUrl() keeps rejecting fedify-test.internal on Bun", { skip: !("Bun" in globalThis) }, async () => {
56
+ await (0, node_assert.rejects)(() => require_url.validatePublicUrl("https://fedify-test.internal/"), (error) => error instanceof require_url.UrlError && error.reason === "disallowed");
57
+ });
55
58
  (0, node_test.test)("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
56
59
  require_url.validateLookupAddresses([
57
60
  {
@@ -1,4 +1,4 @@
1
- import { a as validateLookupAddresses, i as isValidPublicIPv6Address, n as expandIPv6Address, o as validatePublicUrl, r as isValidPublicIPv4Address, t as UrlError } from "./url-3fyJuqC0.mjs";
1
+ import { a as isValidPublicIPv6Address, i as isValidPublicIPv4Address, l as validateLookupAddresses, r as expandIPv6Address, t as UrlError, u as validatePublicUrl } from "./url-DQWrMGhu.mjs";
2
2
  import { test } from "node:test";
3
3
  import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert";
4
4
  //#region src/url.test.ts
@@ -51,6 +51,9 @@ test("validatePublicUrl()", async () => {
51
51
  await validatePublicUrl("https://[2001:db8::1]");
52
52
  await validatePublicUrl("https://[64:ff9b::8.8.8.8]");
53
53
  });
54
+ test("validatePublicUrl() keeps rejecting fedify-test.internal on Bun", { skip: !("Bun" in globalThis) }, async () => {
55
+ await rejects(() => validatePublicUrl("https://fedify-test.internal/"), (error) => error instanceof UrlError && error.reason === "disallowed");
56
+ });
54
57
  test("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
55
58
  validateLookupAddresses([
56
59
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.1.30",
3
+ "version": "2.1.31",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,6 +35,16 @@
35
35
  "require": "./dist/mod.cjs",
36
36
  "default": "./dist/mod.js"
37
37
  },
38
+ "./internal": {
39
+ "types": {
40
+ "import": "./dist/internal.d.ts",
41
+ "require": "./dist/internal.d.cts",
42
+ "default": "./dist/internal.d.ts"
43
+ },
44
+ "import": "./dist/internal.js",
45
+ "require": "./dist/internal.cjs",
46
+ "default": "./dist/internal.js"
47
+ },
38
48
  "./jsonld": {
39
49
  "types": {
40
50
  "import": "./dist/jsonld.d.ts",
@@ -79,11 +89,12 @@
79
89
  "@js-temporal/polyfill": "^0.5.1",
80
90
  "@logtape/logtape": "^2.0.5",
81
91
  "@multiformats/base-x": "^4.0.1",
82
- "@opentelemetry/api": "^1.9.0",
92
+ "@opentelemetry/api": "~1.9.0",
83
93
  "asn1js": "^3.0.6",
84
94
  "byte-encodings": "^1.0.11",
85
95
  "jsonld": "^9.0.0",
86
- "pkijs": "^3.3.3"
96
+ "pkijs": "^3.3.3",
97
+ "undici": "^7.30.1"
87
98
  },
88
99
  "scripts": {
89
100
  "build:self": "tsdown",