@fedify/vocab-runtime 2.1.18 → 2.1.20
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.
- package/deno.json +1 -1
- package/dist/mod.cjs +54 -7
- package/dist/mod.d.cts +3 -1
- package/dist/mod.d.ts +3 -2
- package/dist/mod.js +54 -7
- package/dist/tests/decimal.test.cjs +2 -2
- package/dist/tests/decimal.test.mjs +2 -2
- package/dist/tests/{docloader-DhbeXWJm.mjs → docloader-BogYGZl9.mjs} +23 -3
- package/dist/tests/{docloader-CNNuAaeD.cjs → docloader-q5_iMqdc.cjs} +23 -3
- package/dist/tests/docloader.test.cjs +3 -3
- package/dist/tests/docloader.test.mjs +3 -3
- package/dist/tests/{request-B_7NkCEW.cjs → request-BVRVT4O8.cjs} +1 -1
- package/dist/tests/{request-IHuVvkne.mjs → request-CbQFxyK2.mjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/dist/tests/{url-Ds8K8vxN.cjs → url-CWU5HT3Y.cjs} +38 -5
- package/dist/tests/{url-m9Qzxy-Y.mjs → url-o0e-aXQY.mjs} +33 -6
- package/dist/tests/url.test.cjs +30 -1
- package/dist/tests/url.test.mjs +31 -2
- package/package.json +1 -1
- package/src/contexts/miscellany.json +19 -0
- package/src/contexts.ts +13 -0
- package/src/url.test.ts +28 -1
- package/src/url.ts +35 -5
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -782,6 +782,25 @@ var joinmastodon_default = { "@context": {
|
|
|
782
782
|
}
|
|
783
783
|
} };
|
|
784
784
|
//#endregion
|
|
785
|
+
//#region src/contexts/miscellany.json
|
|
786
|
+
var miscellany_default = { "@context": {
|
|
787
|
+
"as": "https://www.w3.org/ns/activitystreams#",
|
|
788
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
789
|
+
"Hashtag": "as:Hashtag",
|
|
790
|
+
"manuallyApprovesFollowers": {
|
|
791
|
+
"@id": "as:manuallyApprovesFollowers",
|
|
792
|
+
"@type": "xsd:boolean"
|
|
793
|
+
},
|
|
794
|
+
"movedTo": {
|
|
795
|
+
"@id": "as:movedTo",
|
|
796
|
+
"@type": "@id"
|
|
797
|
+
},
|
|
798
|
+
"sensitive": {
|
|
799
|
+
"@id": "as:sensitive",
|
|
800
|
+
"@type": "xsd:boolean"
|
|
801
|
+
}
|
|
802
|
+
} };
|
|
803
|
+
//#endregion
|
|
785
804
|
//#region src/contexts/schemaorg.json
|
|
786
805
|
var schemaorg_default = { "@context": {
|
|
787
806
|
"type": "@type",
|
|
@@ -4379,12 +4398,13 @@ const preloadedContexts = {
|
|
|
4379
4398
|
"https://gotosocial.org/ns": gotosocial_default,
|
|
4380
4399
|
"https://w3id.org/fep/5711": fep_5711_default,
|
|
4381
4400
|
"https://join-lemmy.org/context.json": join_lemmy_default,
|
|
4382
|
-
"http://joinmastodon.org/ns": joinmastodon_default
|
|
4401
|
+
"http://joinmastodon.org/ns": joinmastodon_default,
|
|
4402
|
+
"https://purl.archive.org/miscellany": miscellany_default
|
|
4383
4403
|
};
|
|
4384
4404
|
//#endregion
|
|
4385
4405
|
//#region deno.json
|
|
4386
4406
|
var name = "@fedify/vocab-runtime";
|
|
4387
|
-
var version = "2.1.
|
|
4407
|
+
var version = "2.1.20";
|
|
4388
4408
|
//#endregion
|
|
4389
4409
|
//#region src/link.ts
|
|
4390
4410
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4639,8 +4659,8 @@ function logRequest(logger, request) {
|
|
|
4639
4659
|
//#endregion
|
|
4640
4660
|
//#region src/url.ts
|
|
4641
4661
|
var UrlError = class extends Error {
|
|
4642
|
-
constructor(message) {
|
|
4643
|
-
super(message);
|
|
4662
|
+
constructor(message, options) {
|
|
4663
|
+
super(message, options);
|
|
4644
4664
|
this.name = "UrlError";
|
|
4645
4665
|
}
|
|
4646
4666
|
};
|
|
@@ -4668,10 +4688,37 @@ async function validatePublicUrl(url) {
|
|
|
4668
4688
|
let addresses;
|
|
4669
4689
|
try {
|
|
4670
4690
|
addresses = await (0, node_dns_promises.lookup)(hostname, { all: true });
|
|
4671
|
-
} catch {
|
|
4672
|
-
|
|
4691
|
+
} catch (error) {
|
|
4692
|
+
throw new UrlError("DNS lookup failed", { cause: error });
|
|
4693
|
+
}
|
|
4694
|
+
validateLookupAddresses(addresses);
|
|
4695
|
+
}
|
|
4696
|
+
/**
|
|
4697
|
+
* Validates the IP addresses returned by `node:dns.lookup()`.
|
|
4698
|
+
*
|
|
4699
|
+
* Cloudflare Workers' `node:dns` implementation currently maps every record
|
|
4700
|
+
* in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
|
|
4701
|
+
* `LookupAddress`, even though Node.js specifies that the `address` field must
|
|
4702
|
+
* contain an IPv4 or IPv6 literal. See:
|
|
4703
|
+
* https://github.com/cloudflare/workerd/issues/6886
|
|
4704
|
+
*
|
|
4705
|
+
* Work around that bug by ignoring non-IP entries only when the lookup also
|
|
4706
|
+
* returns at least one actual IP address. This remains fail-closed: a result
|
|
4707
|
+
* containing no IP addresses is rejected, and every returned IP address is
|
|
4708
|
+
* still validated and must be public. This workaround can be revisited once
|
|
4709
|
+
* the Workerd issue is fixed in supported Cloudflare Workers runtimes.
|
|
4710
|
+
*
|
|
4711
|
+
* @internal
|
|
4712
|
+
*/
|
|
4713
|
+
function validateLookupAddresses(addresses) {
|
|
4714
|
+
let ipAddressCount = 0;
|
|
4715
|
+
for (const { address } of addresses) {
|
|
4716
|
+
const family = (0, node_net.isIP)(address);
|
|
4717
|
+
if (family === 0) continue;
|
|
4718
|
+
ipAddressCount++;
|
|
4719
|
+
validatePublicIpAddress(address, family);
|
|
4673
4720
|
}
|
|
4674
|
-
|
|
4721
|
+
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
|
|
4675
4722
|
}
|
|
4676
4723
|
function validatePublicIpAddress(address, family) {
|
|
4677
4724
|
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
|
package/dist/mod.d.cts
CHANGED
|
@@ -417,7 +417,9 @@ declare function encodingFromBaseData(data: string | Uint8Array): Base;
|
|
|
417
417
|
//#endregion
|
|
418
418
|
//#region src/url.d.ts
|
|
419
419
|
declare class UrlError extends Error {
|
|
420
|
-
constructor(message: string
|
|
420
|
+
constructor(message: string, options?: {
|
|
421
|
+
cause?: unknown;
|
|
422
|
+
});
|
|
421
423
|
}
|
|
422
424
|
/**
|
|
423
425
|
* Validates a URL to prevent SSRF attacks.
|
package/dist/mod.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { Logger } from "@logtape/logtape";
|
|
3
|
-
|
|
4
3
|
//#region src/contexts.d.ts
|
|
5
4
|
declare const preloadedContexts: Record<string, unknown>;
|
|
6
5
|
//#endregion
|
|
@@ -417,7 +416,9 @@ declare function encodingFromBaseData(data: string | Uint8Array): Base;
|
|
|
417
416
|
//#endregion
|
|
418
417
|
//#region src/url.d.ts
|
|
419
418
|
declare class UrlError extends Error {
|
|
420
|
-
constructor(message: string
|
|
419
|
+
constructor(message: string, options?: {
|
|
420
|
+
cause?: unknown;
|
|
421
|
+
});
|
|
421
422
|
}
|
|
422
423
|
/**
|
|
423
424
|
* Validates a URL to prevent SSRF attacks.
|
package/dist/mod.js
CHANGED
|
@@ -778,6 +778,25 @@ var joinmastodon_default = { "@context": {
|
|
|
778
778
|
}
|
|
779
779
|
} };
|
|
780
780
|
//#endregion
|
|
781
|
+
//#region src/contexts/miscellany.json
|
|
782
|
+
var miscellany_default = { "@context": {
|
|
783
|
+
"as": "https://www.w3.org/ns/activitystreams#",
|
|
784
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
785
|
+
"Hashtag": "as:Hashtag",
|
|
786
|
+
"manuallyApprovesFollowers": {
|
|
787
|
+
"@id": "as:manuallyApprovesFollowers",
|
|
788
|
+
"@type": "xsd:boolean"
|
|
789
|
+
},
|
|
790
|
+
"movedTo": {
|
|
791
|
+
"@id": "as:movedTo",
|
|
792
|
+
"@type": "@id"
|
|
793
|
+
},
|
|
794
|
+
"sensitive": {
|
|
795
|
+
"@id": "as:sensitive",
|
|
796
|
+
"@type": "xsd:boolean"
|
|
797
|
+
}
|
|
798
|
+
} };
|
|
799
|
+
//#endregion
|
|
781
800
|
//#region src/contexts/schemaorg.json
|
|
782
801
|
var schemaorg_default = { "@context": {
|
|
783
802
|
"type": "@type",
|
|
@@ -4375,12 +4394,13 @@ const preloadedContexts = {
|
|
|
4375
4394
|
"https://gotosocial.org/ns": gotosocial_default,
|
|
4376
4395
|
"https://w3id.org/fep/5711": fep_5711_default,
|
|
4377
4396
|
"https://join-lemmy.org/context.json": join_lemmy_default,
|
|
4378
|
-
"http://joinmastodon.org/ns": joinmastodon_default
|
|
4397
|
+
"http://joinmastodon.org/ns": joinmastodon_default,
|
|
4398
|
+
"https://purl.archive.org/miscellany": miscellany_default
|
|
4379
4399
|
};
|
|
4380
4400
|
//#endregion
|
|
4381
4401
|
//#region deno.json
|
|
4382
4402
|
var name = "@fedify/vocab-runtime";
|
|
4383
|
-
var version = "2.1.
|
|
4403
|
+
var version = "2.1.20";
|
|
4384
4404
|
//#endregion
|
|
4385
4405
|
//#region src/link.ts
|
|
4386
4406
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4635,8 +4655,8 @@ function logRequest(logger, request) {
|
|
|
4635
4655
|
//#endregion
|
|
4636
4656
|
//#region src/url.ts
|
|
4637
4657
|
var UrlError = class extends Error {
|
|
4638
|
-
constructor(message) {
|
|
4639
|
-
super(message);
|
|
4658
|
+
constructor(message, options) {
|
|
4659
|
+
super(message, options);
|
|
4640
4660
|
this.name = "UrlError";
|
|
4641
4661
|
}
|
|
4642
4662
|
};
|
|
@@ -4664,10 +4684,37 @@ async function validatePublicUrl(url) {
|
|
|
4664
4684
|
let addresses;
|
|
4665
4685
|
try {
|
|
4666
4686
|
addresses = await lookup(hostname, { all: true });
|
|
4667
|
-
} catch {
|
|
4668
|
-
|
|
4687
|
+
} catch (error) {
|
|
4688
|
+
throw new UrlError("DNS lookup failed", { cause: error });
|
|
4689
|
+
}
|
|
4690
|
+
validateLookupAddresses(addresses);
|
|
4691
|
+
}
|
|
4692
|
+
/**
|
|
4693
|
+
* Validates the IP addresses returned by `node:dns.lookup()`.
|
|
4694
|
+
*
|
|
4695
|
+
* Cloudflare Workers' `node:dns` implementation currently maps every record
|
|
4696
|
+
* in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
|
|
4697
|
+
* `LookupAddress`, even though Node.js specifies that the `address` field must
|
|
4698
|
+
* contain an IPv4 or IPv6 literal. See:
|
|
4699
|
+
* https://github.com/cloudflare/workerd/issues/6886
|
|
4700
|
+
*
|
|
4701
|
+
* Work around that bug by ignoring non-IP entries only when the lookup also
|
|
4702
|
+
* returns at least one actual IP address. This remains fail-closed: a result
|
|
4703
|
+
* containing no IP addresses is rejected, and every returned IP address is
|
|
4704
|
+
* still validated and must be public. This workaround can be revisited once
|
|
4705
|
+
* the Workerd issue is fixed in supported Cloudflare Workers runtimes.
|
|
4706
|
+
*
|
|
4707
|
+
* @internal
|
|
4708
|
+
*/
|
|
4709
|
+
function validateLookupAddresses(addresses) {
|
|
4710
|
+
let ipAddressCount = 0;
|
|
4711
|
+
for (const { address } of addresses) {
|
|
4712
|
+
const family = isIP(address);
|
|
4713
|
+
if (family === 0) continue;
|
|
4714
|
+
ipAddressCount++;
|
|
4715
|
+
validatePublicIpAddress(address, family);
|
|
4669
4716
|
}
|
|
4670
|
-
|
|
4717
|
+
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
|
|
4671
4718
|
}
|
|
4672
4719
|
function validatePublicIpAddress(address, family) {
|
|
4673
4720
|
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
require("./docloader-
|
|
3
|
-
require("./url-
|
|
2
|
+
require("./docloader-q5_iMqdc.cjs");
|
|
3
|
+
require("./url-CWU5HT3Y.cjs");
|
|
4
4
|
require("./key-DTTIntwb.cjs");
|
|
5
5
|
require("./multibase-F7LtMMsK.cjs");
|
|
6
6
|
require("./langstr-CbAxaeEZ.cjs");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-
|
|
1
|
+
import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-CbQFxyK2.mjs";
|
|
2
2
|
import { t as HttpHeaderLink } from "./link-NUUWCdnK.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { o as validatePublicUrl, t as UrlError } from "./url-o0e-aXQY.mjs";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
6
6
|
//#region src/contexts/activitystreams.json
|
|
@@ -771,6 +771,25 @@ var joinmastodon_default = { "@context": {
|
|
|
771
771
|
}
|
|
772
772
|
} };
|
|
773
773
|
//#endregion
|
|
774
|
+
//#region src/contexts/miscellany.json
|
|
775
|
+
var miscellany_default = { "@context": {
|
|
776
|
+
"as": "https://www.w3.org/ns/activitystreams#",
|
|
777
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
778
|
+
"Hashtag": "as:Hashtag",
|
|
779
|
+
"manuallyApprovesFollowers": {
|
|
780
|
+
"@id": "as:manuallyApprovesFollowers",
|
|
781
|
+
"@type": "xsd:boolean"
|
|
782
|
+
},
|
|
783
|
+
"movedTo": {
|
|
784
|
+
"@id": "as:movedTo",
|
|
785
|
+
"@type": "@id"
|
|
786
|
+
},
|
|
787
|
+
"sensitive": {
|
|
788
|
+
"@id": "as:sensitive",
|
|
789
|
+
"@type": "xsd:boolean"
|
|
790
|
+
}
|
|
791
|
+
} };
|
|
792
|
+
//#endregion
|
|
774
793
|
//#region src/contexts/schemaorg.json
|
|
775
794
|
var schemaorg_default = { "@context": {
|
|
776
795
|
"type": "@type",
|
|
@@ -4368,7 +4387,8 @@ const preloadedContexts = {
|
|
|
4368
4387
|
"https://gotosocial.org/ns": gotosocial_default,
|
|
4369
4388
|
"https://w3id.org/fep/5711": fep_5711_default,
|
|
4370
4389
|
"https://join-lemmy.org/context.json": join_lemmy_default,
|
|
4371
|
-
"http://joinmastodon.org/ns": joinmastodon_default
|
|
4390
|
+
"http://joinmastodon.org/ns": joinmastodon_default,
|
|
4391
|
+
"https://purl.archive.org/miscellany": miscellany_default
|
|
4372
4392
|
};
|
|
4373
4393
|
//#endregion
|
|
4374
4394
|
//#region src/docloader.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
2
|
+
const require_request = require("./request-BVRVT4O8.cjs");
|
|
3
3
|
const require_link = require("./link-FguCydMA.cjs");
|
|
4
|
-
const require_url = require("./url-
|
|
4
|
+
const require_url = require("./url-CWU5HT3Y.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
6
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
7
7
|
//#region src/contexts/activitystreams.json
|
|
@@ -772,6 +772,25 @@ var joinmastodon_default = { "@context": {
|
|
|
772
772
|
}
|
|
773
773
|
} };
|
|
774
774
|
//#endregion
|
|
775
|
+
//#region src/contexts/miscellany.json
|
|
776
|
+
var miscellany_default = { "@context": {
|
|
777
|
+
"as": "https://www.w3.org/ns/activitystreams#",
|
|
778
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
779
|
+
"Hashtag": "as:Hashtag",
|
|
780
|
+
"manuallyApprovesFollowers": {
|
|
781
|
+
"@id": "as:manuallyApprovesFollowers",
|
|
782
|
+
"@type": "xsd:boolean"
|
|
783
|
+
},
|
|
784
|
+
"movedTo": {
|
|
785
|
+
"@id": "as:movedTo",
|
|
786
|
+
"@type": "@id"
|
|
787
|
+
},
|
|
788
|
+
"sensitive": {
|
|
789
|
+
"@id": "as:sensitive",
|
|
790
|
+
"@type": "xsd:boolean"
|
|
791
|
+
}
|
|
792
|
+
} };
|
|
793
|
+
//#endregion
|
|
775
794
|
//#region src/contexts/schemaorg.json
|
|
776
795
|
var schemaorg_default = { "@context": {
|
|
777
796
|
"type": "@type",
|
|
@@ -4369,7 +4388,8 @@ const preloadedContexts = {
|
|
|
4369
4388
|
"https://gotosocial.org/ns": gotosocial_default,
|
|
4370
4389
|
"https://w3id.org/fep/5711": fep_5711_default,
|
|
4371
4390
|
"https://join-lemmy.org/context.json": join_lemmy_default,
|
|
4372
|
-
"http://joinmastodon.org/ns": joinmastodon_default
|
|
4391
|
+
"http://joinmastodon.org/ns": joinmastodon_default,
|
|
4392
|
+
"https://purl.archive.org/miscellany": miscellany_default
|
|
4373
4393
|
};
|
|
4374
4394
|
//#endregion
|
|
4375
4395
|
//#region src/docloader.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
const require_docloader = require("./docloader-
|
|
3
|
-
const require_request = require("./request-
|
|
4
|
-
const require_url = require("./url-
|
|
2
|
+
const require_docloader = require("./docloader-q5_iMqdc.cjs");
|
|
3
|
+
const require_request = require("./request-BVRVT4O8.cjs");
|
|
4
|
+
const require_url = require("./url-CWU5HT3Y.cjs");
|
|
5
5
|
let node_assert = require("node:assert");
|
|
6
6
|
let node_test = require("node:test");
|
|
7
7
|
//#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as preloadedContexts, t as getDocumentLoader } from "./docloader-
|
|
2
|
-
import { t as FetchError } from "./request-
|
|
3
|
-
import { t as UrlError } from "./url-
|
|
1
|
+
import { n as preloadedContexts, t as getDocumentLoader } from "./docloader-BogYGZl9.mjs";
|
|
2
|
+
import { t as FetchError } from "./request-CbQFxyK2.mjs";
|
|
3
|
+
import { t as UrlError } from "./url-o0e-aXQY.mjs";
|
|
4
4
|
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
5
5
|
import { test } from "node:test";
|
|
6
6
|
//#region \0rolldown/runtime.js
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
2
|
+
const require_request = require("./request-BVRVT4O8.cjs");
|
|
3
3
|
let node_assert = require("node:assert");
|
|
4
4
|
let node_test = require("node:test");
|
|
5
5
|
let node_process = require("node:process");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as version, r as getUserAgent } from "./request-
|
|
1
|
+
import { o as version, r as getUserAgent } from "./request-CbQFxyK2.mjs";
|
|
2
2
|
import { deepStrictEqual } from "node:assert";
|
|
3
3
|
import { test } from "node:test";
|
|
4
4
|
import process from "node:process";
|
|
@@ -3,8 +3,8 @@ let node_dns_promises = require("node:dns/promises");
|
|
|
3
3
|
let node_net = require("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
|
};
|
|
@@ -32,10 +32,37 @@ async function validatePublicUrl(url) {
|
|
|
32
32
|
let addresses;
|
|
33
33
|
try {
|
|
34
34
|
addresses = await (0, node_dns_promises.lookup)(hostname, { all: true });
|
|
35
|
-
} catch {
|
|
36
|
-
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new UrlError("DNS lookup failed", { cause: error });
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
validateLookupAddresses(addresses);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validates the IP addresses returned by `node:dns.lookup()`.
|
|
42
|
+
*
|
|
43
|
+
* Cloudflare Workers' `node:dns` implementation currently maps every record
|
|
44
|
+
* in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
|
|
45
|
+
* `LookupAddress`, even though Node.js specifies that the `address` field must
|
|
46
|
+
* contain an IPv4 or IPv6 literal. See:
|
|
47
|
+
* https://github.com/cloudflare/workerd/issues/6886
|
|
48
|
+
*
|
|
49
|
+
* Work around that bug by ignoring non-IP entries only when the lookup also
|
|
50
|
+
* returns at least one actual IP address. This remains fail-closed: a result
|
|
51
|
+
* containing no IP addresses is rejected, and every returned IP address is
|
|
52
|
+
* still validated and must be public. This workaround can be revisited once
|
|
53
|
+
* the Workerd issue is fixed in supported Cloudflare Workers runtimes.
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
function validateLookupAddresses(addresses) {
|
|
58
|
+
let ipAddressCount = 0;
|
|
59
|
+
for (const { address } of addresses) {
|
|
60
|
+
const family = (0, node_net.isIP)(address);
|
|
61
|
+
if (family === 0) continue;
|
|
62
|
+
ipAddressCount++;
|
|
63
|
+
validatePublicIpAddress(address, family);
|
|
64
|
+
}
|
|
65
|
+
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
|
|
39
66
|
}
|
|
40
67
|
function validatePublicIpAddress(address, family) {
|
|
41
68
|
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
|
|
@@ -198,6 +225,12 @@ Object.defineProperty(exports, "isValidPublicIPv6Address", {
|
|
|
198
225
|
return isValidPublicIPv6Address;
|
|
199
226
|
}
|
|
200
227
|
});
|
|
228
|
+
Object.defineProperty(exports, "validateLookupAddresses", {
|
|
229
|
+
enumerable: true,
|
|
230
|
+
get: function() {
|
|
231
|
+
return validateLookupAddresses;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
201
234
|
Object.defineProperty(exports, "validatePublicUrl", {
|
|
202
235
|
enumerable: true,
|
|
203
236
|
get: function() {
|
|
@@ -2,8 +2,8 @@ import { lookup } from "node:dns/promises";
|
|
|
2
2
|
import { isIP } from "node:net";
|
|
3
3
|
//#region src/url.ts
|
|
4
4
|
var UrlError = class extends Error {
|
|
5
|
-
constructor(message) {
|
|
6
|
-
super(message);
|
|
5
|
+
constructor(message, options) {
|
|
6
|
+
super(message, options);
|
|
7
7
|
this.name = "UrlError";
|
|
8
8
|
}
|
|
9
9
|
};
|
|
@@ -31,10 +31,37 @@ async function validatePublicUrl(url) {
|
|
|
31
31
|
let addresses;
|
|
32
32
|
try {
|
|
33
33
|
addresses = await lookup(hostname, { all: true });
|
|
34
|
-
} catch {
|
|
35
|
-
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new UrlError("DNS lookup failed", { cause: error });
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
validateLookupAddresses(addresses);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Validates the IP addresses returned by `node:dns.lookup()`.
|
|
41
|
+
*
|
|
42
|
+
* Cloudflare Workers' `node:dns` implementation currently maps every record
|
|
43
|
+
* in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
|
|
44
|
+
* `LookupAddress`, even though Node.js specifies that the `address` field must
|
|
45
|
+
* contain an IPv4 or IPv6 literal. See:
|
|
46
|
+
* https://github.com/cloudflare/workerd/issues/6886
|
|
47
|
+
*
|
|
48
|
+
* Work around that bug by ignoring non-IP entries only when the lookup also
|
|
49
|
+
* returns at least one actual IP address. This remains fail-closed: a result
|
|
50
|
+
* containing no IP addresses is rejected, and every returned IP address is
|
|
51
|
+
* still validated and must be public. This workaround can be revisited once
|
|
52
|
+
* the Workerd issue is fixed in supported Cloudflare Workers runtimes.
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
function validateLookupAddresses(addresses) {
|
|
57
|
+
let ipAddressCount = 0;
|
|
58
|
+
for (const { address } of addresses) {
|
|
59
|
+
const family = isIP(address);
|
|
60
|
+
if (family === 0) continue;
|
|
61
|
+
ipAddressCount++;
|
|
62
|
+
validatePublicIpAddress(address, family);
|
|
63
|
+
}
|
|
64
|
+
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
|
|
38
65
|
}
|
|
39
66
|
function validatePublicIpAddress(address, family) {
|
|
40
67
|
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
|
|
@@ -173,4 +200,4 @@ function matchesIPv6Prefix(address, prefixWords, prefixLength) {
|
|
|
173
200
|
return true;
|
|
174
201
|
}
|
|
175
202
|
//#endregion
|
|
176
|
-
export {
|
|
203
|
+
export { validateLookupAddresses as a, isValidPublicIPv6Address as i, expandIPv6Address as n, validatePublicUrl as o, isValidPublicIPv4Address as r, UrlError as t };
|
package/dist/tests/url.test.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
const require_url = require("./url-
|
|
2
|
+
const require_url = require("./url-CWU5HT3Y.cjs");
|
|
3
3
|
let node_assert = require("node:assert");
|
|
4
4
|
let node_test = require("node:test");
|
|
5
5
|
//#region src/url.test.ts
|
|
@@ -29,6 +29,35 @@ let node_test = require("node:test");
|
|
|
29
29
|
await require_url.validatePublicUrl("https://[2001:db8::1]");
|
|
30
30
|
await require_url.validatePublicUrl("https://[64:ff9b::8.8.8.8]");
|
|
31
31
|
});
|
|
32
|
+
(0, node_test.test)("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
|
|
33
|
+
require_url.validateLookupAddresses([
|
|
34
|
+
{
|
|
35
|
+
address: "app-host.example.net.",
|
|
36
|
+
family: 4
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
address: "93.184.216.34",
|
|
40
|
+
family: 4
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
address: "2606:2800:220:1:248:1893:25c8:1946",
|
|
44
|
+
family: 6
|
|
45
|
+
}
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
(0, node_test.test)("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers results", () => {
|
|
49
|
+
(0, node_assert.throws)(() => require_url.validateLookupAddresses([{
|
|
50
|
+
address: "private-host.example.net.",
|
|
51
|
+
family: 4
|
|
52
|
+
}, {
|
|
53
|
+
address: "127.0.0.1",
|
|
54
|
+
family: 4
|
|
55
|
+
}]), require_url.UrlError);
|
|
56
|
+
(0, node_assert.throws)(() => require_url.validateLookupAddresses([{
|
|
57
|
+
address: "app-host.example.net.",
|
|
58
|
+
family: 4
|
|
59
|
+
}]), require_url.UrlError);
|
|
60
|
+
});
|
|
32
61
|
(0, node_test.test)("isValidPublicIPv4Address()", () => {
|
|
33
62
|
(0, node_assert.ok)(require_url.isValidPublicIPv4Address("8.8.8.8"));
|
|
34
63
|
(0, node_assert.ok)(!require_url.isValidPublicIPv4Address("192.168.1.1"));
|
package/dist/tests/url.test.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
1
|
+
import { a as validateLookupAddresses, i as isValidPublicIPv6Address, n as expandIPv6Address, o as validatePublicUrl, r as isValidPublicIPv4Address, t as UrlError } from "./url-o0e-aXQY.mjs";
|
|
2
|
+
import { deepStrictEqual, ok, rejects, throws } from "node:assert";
|
|
3
3
|
import { test } from "node:test";
|
|
4
4
|
//#region src/url.test.ts
|
|
5
5
|
test("validatePublicUrl()", async () => {
|
|
@@ -28,6 +28,35 @@ test("validatePublicUrl()", async () => {
|
|
|
28
28
|
await validatePublicUrl("https://[2001:db8::1]");
|
|
29
29
|
await validatePublicUrl("https://[64:ff9b::8.8.8.8]");
|
|
30
30
|
});
|
|
31
|
+
test("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
|
|
32
|
+
validateLookupAddresses([
|
|
33
|
+
{
|
|
34
|
+
address: "app-host.example.net.",
|
|
35
|
+
family: 4
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
address: "93.184.216.34",
|
|
39
|
+
family: 4
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
address: "2606:2800:220:1:248:1893:25c8:1946",
|
|
43
|
+
family: 6
|
|
44
|
+
}
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
test("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers results", () => {
|
|
48
|
+
throws(() => validateLookupAddresses([{
|
|
49
|
+
address: "private-host.example.net.",
|
|
50
|
+
family: 4
|
|
51
|
+
}, {
|
|
52
|
+
address: "127.0.0.1",
|
|
53
|
+
family: 4
|
|
54
|
+
}]), UrlError);
|
|
55
|
+
throws(() => validateLookupAddresses([{
|
|
56
|
+
address: "app-host.example.net.",
|
|
57
|
+
family: 4
|
|
58
|
+
}]), UrlError);
|
|
59
|
+
});
|
|
31
60
|
test("isValidPublicIPv4Address()", () => {
|
|
32
61
|
ok(isValidPublicIPv4Address("8.8.8.8"));
|
|
33
62
|
ok(!isValidPublicIPv4Address("192.168.1.1"));
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"as": "https://www.w3.org/ns/activitystreams#",
|
|
4
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
5
|
+
"Hashtag": "as:Hashtag",
|
|
6
|
+
"manuallyApprovesFollowers": {
|
|
7
|
+
"@id": "as:manuallyApprovesFollowers",
|
|
8
|
+
"@type": "xsd:boolean"
|
|
9
|
+
},
|
|
10
|
+
"movedTo": {
|
|
11
|
+
"@id": "as:movedTo",
|
|
12
|
+
"@type": "@id"
|
|
13
|
+
},
|
|
14
|
+
"sensitive": {
|
|
15
|
+
"@id": "as:sensitive",
|
|
16
|
+
"@type": "xsd:boolean"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/contexts.ts
CHANGED
|
@@ -11,6 +11,7 @@ import gotosocial from "./contexts/gotosocial.json" with { type: "json" };
|
|
|
11
11
|
import identityV1 from "./contexts/identity-v1.json" with { type: "json" };
|
|
12
12
|
import joinLemmyContext from "./contexts/join-lemmy.json" with { type: "json" };
|
|
13
13
|
import joinmastodon from "./contexts/joinmastodon.json" with { type: "json" };
|
|
14
|
+
import miscellany from "./contexts/miscellany.json" with { type: "json" };
|
|
14
15
|
import schemaorg from "./contexts/schemaorg.json" with { type: "json" };
|
|
15
16
|
import securityDataIntegrityV1 from "./contexts/security-data-integrity-v1.json" with {
|
|
16
17
|
type: "json",
|
|
@@ -55,6 +56,18 @@ const preloadedContexts: Record<string, unknown> = {
|
|
|
55
56
|
// See: https://github.com/mastodon/joinmastodon/issues/148
|
|
56
57
|
// https://github.com/fedify-dev/fedify/issues/630
|
|
57
58
|
"http://joinmastodon.org/ns": joinmastodon,
|
|
59
|
+
|
|
60
|
+
// The SWICG "ActivityPub Miscellaneous Terms" context. Bridgy Fed (via
|
|
61
|
+
// granary) references this URL in the @context of every activity it sends,
|
|
62
|
+
// and other implementations use it as the recommended home for widely
|
|
63
|
+
// deployed extension terms like as:Hashtag and as:sensitive. It is served
|
|
64
|
+
// through purl.archive.org (Internet Archive's PURL service), which suffers
|
|
65
|
+
// recurring outages; while it is unreachable, every activity referencing
|
|
66
|
+
// this URL fails JSON-LD expansion before application handlers run. The
|
|
67
|
+
// document is a deliberately stable, versioned SWICG deliverable, so we
|
|
68
|
+
// ship a built-in copy.
|
|
69
|
+
// See: https://swicg.github.io/miscellany/
|
|
70
|
+
"https://purl.archive.org/miscellany": miscellany,
|
|
58
71
|
};
|
|
59
72
|
|
|
60
73
|
export default preloadedContexts;
|
package/src/url.test.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
1
|
+
import { deepStrictEqual, ok, rejects, throws } from "node:assert";
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
import {
|
|
4
4
|
expandIPv6Address,
|
|
5
5
|
isValidPublicIPv4Address,
|
|
6
6
|
isValidPublicIPv6Address,
|
|
7
7
|
UrlError,
|
|
8
|
+
validateLookupAddresses,
|
|
8
9
|
validatePublicUrl,
|
|
9
10
|
} from "./url.ts";
|
|
10
11
|
|
|
@@ -65,6 +66,32 @@ test("validatePublicUrl()", async () => {
|
|
|
65
66
|
await validatePublicUrl("https://[64:ff9b::8.8.8.8]");
|
|
66
67
|
});
|
|
67
68
|
|
|
69
|
+
test("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
|
|
70
|
+
validateLookupAddresses([
|
|
71
|
+
{ address: "app-host.example.net.", family: 4 },
|
|
72
|
+
{ address: "93.184.216.34", family: 4 },
|
|
73
|
+
{ address: "2606:2800:220:1:248:1893:25c8:1946", family: 6 },
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers results", () => {
|
|
78
|
+
throws(
|
|
79
|
+
() =>
|
|
80
|
+
validateLookupAddresses([
|
|
81
|
+
{ address: "private-host.example.net.", family: 4 },
|
|
82
|
+
{ address: "127.0.0.1", family: 4 },
|
|
83
|
+
]),
|
|
84
|
+
UrlError,
|
|
85
|
+
);
|
|
86
|
+
throws(
|
|
87
|
+
() =>
|
|
88
|
+
validateLookupAddresses([
|
|
89
|
+
{ address: "app-host.example.net.", family: 4 },
|
|
90
|
+
]),
|
|
91
|
+
UrlError,
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
68
95
|
test("isValidPublicIPv4Address()", () => {
|
|
69
96
|
ok(isValidPublicIPv4Address("8.8.8.8")); // Google DNS
|
|
70
97
|
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
|
}
|
|
@@ -51,12 +51,42 @@ export async function validatePublicUrl(url: string): Promise<void> {
|
|
|
51
51
|
let addresses: LookupAddress[];
|
|
52
52
|
try {
|
|
53
53
|
addresses = await lookup(hostname, { all: true });
|
|
54
|
-
} catch {
|
|
55
|
-
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new UrlError("DNS lookup failed", { cause: error });
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
validateLookupAddresses(addresses);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validates the IP addresses returned by `node:dns.lookup()`.
|
|
62
|
+
*
|
|
63
|
+
* Cloudflare Workers' `node:dns` implementation currently maps every record
|
|
64
|
+
* in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
|
|
65
|
+
* `LookupAddress`, even though Node.js specifies that the `address` field must
|
|
66
|
+
* contain an IPv4 or IPv6 literal. See:
|
|
67
|
+
* https://github.com/cloudflare/workerd/issues/6886
|
|
68
|
+
*
|
|
69
|
+
* Work around that bug by ignoring non-IP entries only when the lookup also
|
|
70
|
+
* returns at least one actual IP address. This remains fail-closed: a result
|
|
71
|
+
* containing no IP addresses is rejected, and every returned IP address is
|
|
72
|
+
* still validated and must be public. This workaround can be revisited once
|
|
73
|
+
* the Workerd issue is fixed in supported Cloudflare Workers runtimes.
|
|
74
|
+
*
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
export function validateLookupAddresses(
|
|
78
|
+
addresses: readonly LookupAddress[],
|
|
79
|
+
): void {
|
|
80
|
+
let ipAddressCount = 0;
|
|
81
|
+
for (const { address } of addresses) {
|
|
82
|
+
const family = isIP(address);
|
|
83
|
+
if (family === 0) continue;
|
|
84
|
+
ipAddressCount++;
|
|
58
85
|
validatePublicIpAddress(address, family);
|
|
59
86
|
}
|
|
87
|
+
if (ipAddressCount === 0) {
|
|
88
|
+
throw new UrlError("DNS lookup did not return any IP address");
|
|
89
|
+
}
|
|
60
90
|
}
|
|
61
91
|
|
|
62
92
|
function validatePublicIpAddress(address: string, family: number): void {
|