@fedify/vocab-runtime 2.4.0-dev.1668 → 2.4.0-dev.1727

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/deno.json +1 -1
  2. package/dist/internal/jsonld-cache.cjs +1 -1
  3. package/dist/internal/jsonld-cache.d.cts +1 -1
  4. package/dist/internal/jsonld-cache.d.ts +1 -1
  5. package/dist/internal/jsonld-cache.js +1 -1
  6. package/dist/jsonld.cjs +2 -2
  7. package/dist/mod.cjs +9 -8
  8. package/dist/mod.d.cts +4 -2
  9. package/dist/mod.d.ts +4 -3
  10. package/dist/mod.js +6 -5
  11. package/dist/tests/decimal.test.cjs +6 -7
  12. package/dist/tests/decimal.test.mjs +6 -6
  13. package/dist/tests/{digest-DLwUyHCK.cjs → digest-3FeH2Y-Q.cjs} +6 -5
  14. package/dist/tests/{digest-BJlumPNJ.mjs → digest-COC7xDiQ.mjs} +6 -5
  15. package/dist/tests/digest.test.cjs +11 -12
  16. package/dist/tests/digest.test.mjs +11 -11
  17. package/dist/tests/{docloader-u_7i2eIZ.mjs → docloader-BD58nJdO.mjs} +3 -3
  18. package/dist/tests/{docloader-BLOFskDa.cjs → docloader-DFqMzdME.cjs} +3 -4
  19. package/dist/tests/docloader.test.cjs +19 -12
  20. package/dist/tests/docloader.test.mjs +16 -9
  21. package/dist/tests/internal/multicodec.test.cjs +1 -2
  22. package/dist/tests/internal/multicodec.test.mjs +1 -1
  23. package/dist/tests/jsonld-cache.test.cjs +17 -17
  24. package/dist/tests/jsonld-cache.test.mjs +15 -15
  25. package/dist/tests/{key-BcOWDLMm.cjs → key-8Qg2SbZi.cjs} +2 -3
  26. package/dist/tests/{key-Drbw_2tw.mjs → key-C7MNhlkO.mjs} +2 -2
  27. package/dist/tests/key.test.cjs +3 -4
  28. package/dist/tests/key.test.mjs +3 -3
  29. package/dist/tests/langstr.test.cjs +3 -3
  30. package/dist/tests/langstr.test.mjs +1 -1
  31. package/dist/tests/link.test.cjs +1 -2
  32. package/dist/tests/link.test.mjs +1 -1
  33. package/dist/tests/multibase/multibase.test.cjs +1 -2
  34. package/dist/tests/multibase/multibase.test.mjs +1 -1
  35. package/dist/tests/{multibase-DcDQDNnI.cjs → multibase-B5Mea7Ip.cjs} +2 -2
  36. package/dist/tests/{request-C7z-t67V.mjs → request-9_Y1N3iF.mjs} +1 -1
  37. package/dist/tests/{request-BCN6x611.cjs → request-kH0uXibB.cjs} +3 -3
  38. package/dist/tests/request.test.cjs +3 -3
  39. package/dist/tests/request.test.mjs +1 -1
  40. package/dist/tests/temporal.test.cjs +0 -1
  41. package/dist/tests/{url-Du7RQQgP.mjs → url-Basm1L_c.mjs} +33 -6
  42. package/dist/tests/{url-CsOV_B_P.cjs → url-CPfMatUL.cjs} +38 -6
  43. package/dist/tests/url.test.cjs +30 -2
  44. package/dist/tests/url.test.mjs +30 -1
  45. package/dist/{url-DGVbSVVi.js → url-BHAShw9J.js} +32 -5
  46. package/dist/{url-DD4F0ULf.cjs → url-i33c0US6.cjs} +32 -6
  47. package/package.json +1 -1
  48. package/src/url.test.ts +27 -0
  49. package/src/url.ts +35 -5
  50. /package/dist/{docloader-C_dir7Xb.d.ts → docloader-BK627tQQ.d.cts} +0 -0
  51. /package/dist/{docloader-D2DTRiyA.d.cts → docloader-BK627tQQ.d.ts} +0 -0
  52. /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +0 -0
  53. /package/dist/tests/{langstr-BOQHfJDr.cjs → langstr-C4Fl80ae.cjs} +0 -0
  54. /package/dist/tests/{langstr-DDLc4833.mjs → langstr-CQ26J_L7.mjs} +0 -0
  55. /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
  56. /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
  57. /package/dist/tests/{multibase-Cluwl8OM.mjs → multibase-BPnF_L4e.mjs} +0 -0
  58. /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
  59. /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
  60. /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
@@ -3,8 +3,8 @@ import { lookup } from "node:dns/promises";
3
3
  import { isIP } from "node:net";
4
4
  //#region src/url.ts
5
5
  var UrlError = class extends Error {
6
- constructor(message) {
7
- super(message);
6
+ constructor(message, options) {
7
+ super(message, options);
8
8
  this.name = "UrlError";
9
9
  }
10
10
  };
@@ -248,10 +248,37 @@ async function validatePublicUrl(url) {
248
248
  let addresses;
249
249
  try {
250
250
  addresses = await lookup(hostname, { all: true });
251
- } catch {
252
- addresses = [];
251
+ } catch (error) {
252
+ throw new UrlError("DNS lookup failed", { cause: error });
253
+ }
254
+ validateLookupAddresses(addresses);
255
+ }
256
+ /**
257
+ * Validates the IP addresses returned by `node:dns.lookup()`.
258
+ *
259
+ * Cloudflare Workers' `node:dns` implementation currently maps every record
260
+ * in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
261
+ * `LookupAddress`, even though Node.js specifies that the `address` field must
262
+ * contain an IPv4 or IPv6 literal. See:
263
+ * https://github.com/cloudflare/workerd/issues/6886
264
+ *
265
+ * Work around that bug by ignoring non-IP entries only when the lookup also
266
+ * returns at least one actual IP address. This remains fail-closed: a result
267
+ * containing no IP addresses is rejected, and every returned IP address is
268
+ * still validated and must be public. This workaround can be revisited once
269
+ * the Workerd issue is fixed in supported Cloudflare Workers runtimes.
270
+ *
271
+ * @internal
272
+ */
273
+ function validateLookupAddresses(addresses) {
274
+ let ipAddressCount = 0;
275
+ for (const { address } of addresses) {
276
+ const family = isIP(address);
277
+ if (family === 0) continue;
278
+ ipAddressCount++;
279
+ validatePublicIpAddress(address, family);
253
280
  }
254
- for (const { address, family } of addresses) validatePublicIpAddress(address, family);
281
+ if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
255
282
  }
256
283
  function validatePublicIpAddress(address, family) {
257
284
  if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
@@ -1,11 +1,10 @@
1
1
 
2
- require("./chunk-M78iaK0I.cjs");
3
2
  let node_dns_promises = require("node:dns/promises");
4
3
  let node_net = require("node:net");
5
4
  //#region src/url.ts
6
5
  var UrlError = class extends Error {
7
- constructor(message) {
8
- super(message);
6
+ constructor(message, options) {
7
+ super(message, options);
9
8
  this.name = "UrlError";
10
9
  }
11
10
  };
@@ -249,10 +248,37 @@ async function validatePublicUrl(url) {
249
248
  let addresses;
250
249
  try {
251
250
  addresses = await (0, node_dns_promises.lookup)(hostname, { all: true });
252
- } catch {
253
- addresses = [];
251
+ } catch (error) {
252
+ throw new UrlError("DNS lookup failed", { cause: error });
253
+ }
254
+ validateLookupAddresses(addresses);
255
+ }
256
+ /**
257
+ * Validates the IP addresses returned by `node:dns.lookup()`.
258
+ *
259
+ * Cloudflare Workers' `node:dns` implementation currently maps every record
260
+ * in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
261
+ * `LookupAddress`, even though Node.js specifies that the `address` field must
262
+ * contain an IPv4 or IPv6 literal. See:
263
+ * https://github.com/cloudflare/workerd/issues/6886
264
+ *
265
+ * Work around that bug by ignoring non-IP entries only when the lookup also
266
+ * returns at least one actual IP address. This remains fail-closed: a result
267
+ * containing no IP addresses is rejected, and every returned IP address is
268
+ * still validated and must be public. This workaround can be revisited once
269
+ * the Workerd issue is fixed in supported Cloudflare Workers runtimes.
270
+ *
271
+ * @internal
272
+ */
273
+ function validateLookupAddresses(addresses) {
274
+ let ipAddressCount = 0;
275
+ for (const { address } of addresses) {
276
+ const family = (0, node_net.isIP)(address);
277
+ if (family === 0) continue;
278
+ ipAddressCount++;
279
+ validatePublicIpAddress(address, family);
254
280
  }
255
- for (const { address, family } of addresses) validatePublicIpAddress(address, family);
281
+ if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
256
282
  }
257
283
  function validatePublicIpAddress(address, family) {
258
284
  if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.0-dev.1668+90797b01",
3
+ "version": "2.4.0-dev.1727+1eadcb04",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
package/src/url.test.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  parseIri,
16
16
  parseJsonLdId,
17
17
  UrlError,
18
+ validateLookupAddresses,
18
19
  validatePublicUrl,
19
20
  } from "./url.ts";
20
21
 
@@ -718,6 +719,32 @@ test("validatePublicUrl()", async () => {
718
719
  await validatePublicUrl("https://[64:ff9b::8.8.8.8]");
719
720
  });
720
721
 
722
+ test("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
723
+ validateLookupAddresses([
724
+ { address: "app-host.example.net.", family: 4 },
725
+ { address: "93.184.216.34", family: 4 },
726
+ { address: "2606:2800:220:1:248:1893:25c8:1946", family: 6 },
727
+ ]);
728
+ });
729
+
730
+ test("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers results", () => {
731
+ throws(
732
+ () =>
733
+ validateLookupAddresses([
734
+ { address: "private-host.example.net.", family: 4 },
735
+ { address: "127.0.0.1", family: 4 },
736
+ ]),
737
+ UrlError,
738
+ );
739
+ throws(
740
+ () =>
741
+ validateLookupAddresses([
742
+ { address: "app-host.example.net.", family: 4 },
743
+ ]),
744
+ UrlError,
745
+ );
746
+ });
747
+
721
748
  test("isValidPublicIPv4Address()", () => {
722
749
  ok(isValidPublicIPv4Address("8.8.8.8")); // Google DNS
723
750
  ok(!isValidPublicIPv4Address("192.168.1.1")); // private
package/src/url.ts CHANGED
@@ -3,8 +3,8 @@ import { lookup } from "node:dns/promises";
3
3
  import { isIP } from "node:net";
4
4
 
5
5
  export class UrlError extends Error {
6
- constructor(message: string) {
7
- super(message);
6
+ constructor(message: string, options?: { cause?: unknown }) {
7
+ super(message, options);
8
8
  this.name = "UrlError";
9
9
  }
10
10
  }
@@ -388,12 +388,42 @@ export async function validatePublicUrl(url: string): Promise<void> {
388
388
  let addresses: LookupAddress[];
389
389
  try {
390
390
  addresses = await lookup(hostname, { all: true });
391
- } catch {
392
- addresses = [];
391
+ } catch (error) {
392
+ throw new UrlError("DNS lookup failed", { cause: error });
393
393
  }
394
- for (const { address, family } of addresses) {
394
+ validateLookupAddresses(addresses);
395
+ }
396
+
397
+ /**
398
+ * Validates the IP addresses returned by `node:dns.lookup()`.
399
+ *
400
+ * Cloudflare Workers' `node:dns` implementation currently maps every record
401
+ * in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
402
+ * `LookupAddress`, even though Node.js specifies that the `address` field must
403
+ * contain an IPv4 or IPv6 literal. See:
404
+ * https://github.com/cloudflare/workerd/issues/6886
405
+ *
406
+ * Work around that bug by ignoring non-IP entries only when the lookup also
407
+ * returns at least one actual IP address. This remains fail-closed: a result
408
+ * containing no IP addresses is rejected, and every returned IP address is
409
+ * still validated and must be public. This workaround can be revisited once
410
+ * the Workerd issue is fixed in supported Cloudflare Workers runtimes.
411
+ *
412
+ * @internal
413
+ */
414
+ export function validateLookupAddresses(
415
+ addresses: readonly LookupAddress[],
416
+ ): void {
417
+ let ipAddressCount = 0;
418
+ for (const { address } of addresses) {
419
+ const family = isIP(address);
420
+ if (family === 0) continue;
421
+ ipAddressCount++;
395
422
  validatePublicIpAddress(address, family);
396
423
  }
424
+ if (ipAddressCount === 0) {
425
+ throw new UrlError("DNS lookup did not return any IP address");
426
+ }
397
427
  }
398
428
 
399
429
  function validatePublicIpAddress(address: string, family: number): void {