@fedify/vocab-runtime 2.5.0-dev.2424 → 2.5.0-dev.2432

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.5.0-dev.2424+97214632",
3
+ "version": "2.5.0-dev.2432+f56118e1",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
package/dist/mod.cjs CHANGED
@@ -16,7 +16,7 @@ let _multiformats_base_x = require("@multiformats/base-x");
16
16
  _multiformats_base_x = require_rolldown_runtime.__toESM(_multiformats_base_x, 1);
17
17
  //#region deno.json
18
18
  var name = "@fedify/vocab-runtime";
19
- var version = "2.5.0-dev.2424+97214632";
19
+ var version = "2.5.0-dev.2432+f56118e1";
20
20
  //#endregion
21
21
  //#region src/request.ts
22
22
  /**
@@ -774,6 +774,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
774
774
  options?.signal?.throwIfAborted();
775
775
  const parsedUrl = new URL(url);
776
776
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new require_url.UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
777
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new require_url.UrlError("URLs with credentials are not allowed");
777
778
  const currentUrl = parsedUrl.href;
778
779
  if (!skipPreloadedContexts && currentUrl in require_contexts.preloadedContexts) {
779
780
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
package/dist/mod.js CHANGED
@@ -12,7 +12,7 @@ import { PublicKeyInfo } from "pkijs";
12
12
  import baseX from "@multiformats/base-x";
13
13
  //#region deno.json
14
14
  var name = "@fedify/vocab-runtime";
15
- var version = "2.5.0-dev.2424+97214632";
15
+ var version = "2.5.0-dev.2432+f56118e1";
16
16
  //#endregion
17
17
  //#region src/request.ts
18
18
  /**
@@ -770,6 +770,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
770
770
  options?.signal?.throwIfAborted();
771
771
  const parsedUrl = new URL(url);
772
772
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
773
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
773
774
  const currentUrl = parsedUrl.href;
774
775
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
775
776
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
@@ -1,4 +1,4 @@
1
- import { t as FetchError } from "./request-BHqGsrJF.mjs";
1
+ import { t as FetchError } from "./request-ClluSsbr.mjs";
2
2
  import { getLogger } from "@logtape/logtape";
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- const require_request = require("./request-3qhu9psJ.cjs");
1
+ const require_request = require("./request-B5PYS32O.cjs");
2
2
  let _logtape_logtape = require("@logtape/logtape");
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- const require_body = require("./body-DVcRd85t.cjs");
1
+ const require_body = require("./body-DLo6Wfqk.cjs");
2
2
  let node_assert_strict = require("node:assert/strict");
3
3
  let node_test = require("node:test");
4
4
  let node_zlib = require("node:zlib");
@@ -1,4 +1,4 @@
1
- import { i as readBoundedText, t as BodyTooLargeError } from "./body-Djuimvjd.mjs";
1
+ import { i as readBoundedText, t as BodyTooLargeError } from "./body-B6FmobUg.mjs";
2
2
  import { deepStrictEqual, ok, rejects } from "node:assert/strict";
3
3
  import { test } from "node:test";
4
4
  import { gunzipSync, gzipSync } from "node:zlib";
@@ -1,12 +1,12 @@
1
- require("./request-3qhu9psJ.cjs");
1
+ require("./request-B5PYS32O.cjs");
2
2
  require("./url-BNakuZ8k.cjs");
3
- require("./docloader-DzwnQe1e.cjs");
3
+ require("./docloader-D5A8bwJx.cjs");
4
4
  require("./key-C-AYkdJJ.cjs");
5
5
  require("./multibase-B5Mea7Ip.cjs");
6
6
  require("./digest-3FeH2Y-Q.cjs");
7
7
  require("./langtag-DASCPgl3.cjs");
8
8
  require("./langstr-h89Kjekh.cjs");
9
- require("./portable-media-LtEUNA6B.cjs");
9
+ require("./portable-media-BhuGLllk.cjs");
10
10
  let node_test = require("node:test");
11
11
  let node_assert = require("node:assert");
12
12
  //#region src/decimal.ts
@@ -1,12 +1,12 @@
1
- import "./request-BHqGsrJF.mjs";
1
+ import "./request-ClluSsbr.mjs";
2
2
  import "./url-DMxmp7ZG.mjs";
3
- import "./docloader--jQ3XcCi.mjs";
3
+ import "./docloader-BwMNJWDL.mjs";
4
4
  import "./key-C2Db_TAJ.mjs";
5
5
  import "./multibase-BPnF_L4e.mjs";
6
6
  import "./digest-COC7xDiQ.mjs";
7
7
  import "./langtag-CD3zHKPN.mjs";
8
8
  import "./langstr-Dw8IBDZl.mjs";
9
- import "./portable-media-BHBIOsiA.mjs";
9
+ import "./portable-media-DudjnSMO.mjs";
10
10
  import { test } from "node:test";
11
11
  import { deepStrictEqual, throws } from "node:assert";
12
12
  //#region src/decimal.ts
@@ -1,6 +1,6 @@
1
- import { a as logRequest, n as createActivityPubRequest, o as name, s as version, t as FetchError } from "./request-BHqGsrJF.mjs";
1
+ import { a as logRequest, n as createActivityPubRequest, o as name, s as version, t as FetchError } from "./request-ClluSsbr.mjs";
2
2
  import { n as UrlError, x as validatePublicUrl } from "./url-DMxmp7ZG.mjs";
3
- import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-Djuimvjd.mjs";
3
+ import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-B6FmobUg.mjs";
4
4
  import { t as preloadedContexts } from "./contexts-duRA_dDC.mjs";
5
5
  import { t as HttpHeaderLink } from "./link-Cevmc87v.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
@@ -293,6 +293,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
293
293
  options?.signal?.throwIfAborted();
294
294
  const parsedUrl = new URL(url);
295
295
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
296
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
296
297
  const currentUrl = parsedUrl.href;
297
298
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
298
299
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
@@ -1,6 +1,6 @@
1
- const require_request = require("./request-3qhu9psJ.cjs");
1
+ const require_request = require("./request-B5PYS32O.cjs");
2
2
  const require_url = require("./url-BNakuZ8k.cjs");
3
- const require_body = require("./body-DVcRd85t.cjs");
3
+ const require_body = require("./body-DLo6Wfqk.cjs");
4
4
  const require_contexts = require("./contexts-C9Lj11SC.cjs");
5
5
  const require_link = require("./link-DlKm8bEr.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
@@ -293,6 +293,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
293
293
  options?.signal?.throwIfAborted();
294
294
  const parsedUrl = new URL(url);
295
295
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new require_url.UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
296
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new require_url.UrlError("URLs with credentials are not allowed");
296
297
  const currentUrl = parsedUrl.href;
297
298
  if (!skipPreloadedContexts && currentUrl in require_contexts.preloadedContexts) {
298
299
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
@@ -1,8 +1,8 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request-3qhu9psJ.cjs");
2
+ const require_request = require("./request-B5PYS32O.cjs");
3
3
  const require_url = require("./url-BNakuZ8k.cjs");
4
4
  const require_contexts = require("./contexts-C9Lj11SC.cjs");
5
- const require_docloader = require("./docloader-DzwnQe1e.cjs");
5
+ const require_docloader = require("./docloader-D5A8bwJx.cjs");
6
6
  const require_jsonld = require("./jsonld-CmDhHNTx.cjs");
7
7
  let node_zlib = require("node:zlib");
8
8
  let _logtape_logtape = require("@logtape/logtape");
@@ -2376,6 +2376,52 @@ const unsupportedUrls = [
2376
2376
  "file:///collection",
2377
2377
  `data:application/ld+json,${encodeURIComponent(JSON.stringify({ id: "https://example.com/data" }))}`
2378
2378
  ];
2379
+ function isCredentialUrlError(error) {
2380
+ return error instanceof require_url.UrlError && error.reason === "disallowed" && error.message === "URLs with credentials are not allowed";
2381
+ }
2382
+ (0, _fedify_fixture.test)("getDocumentLoader() rejects URLs with credentials", async (t) => {
2383
+ const start = "https://8.8.8.8/start";
2384
+ const target = "https://user:pass@8.8.8.8/document";
2385
+ for (const allowPrivateAddress of [false, true]) {
2386
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress });
2387
+ for (const path of [
2388
+ "direct",
2389
+ "redirect",
2390
+ "Link",
2391
+ "HTML"
2392
+ ]) await t.step(`${path}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
2393
+ await withFetchStub((url) => {
2394
+ if (url !== start) return Response.json({});
2395
+ if (path === "redirect") return Response.redirect(target, 302);
2396
+ if (path === "HTML") return new Response(`<link rel="alternate" type="application/activity+json" href="${target}">`, { headers: { "Content-Type": "text/html" } });
2397
+ return new Response("", { headers: {
2398
+ "Content-Type": "text/plain",
2399
+ Link: `<${target}>; rel="alternate"; type="application/activity+json"`
2400
+ } });
2401
+ }, async (fetched) => {
2402
+ await (0, node_assert.rejects)(loader(path === "direct" ? target : start), isCredentialUrlError);
2403
+ (0, node_assert.deepStrictEqual)(fetched, path === "direct" ? [] : [start]);
2404
+ });
2405
+ });
2406
+ for (const url of [
2407
+ "https://user@8.8.8.8/document",
2408
+ "https://:pass@8.8.8.8/document",
2409
+ "https://%75ser:p%40ss@8.8.8.8/document",
2410
+ "http://user:pass@8.8.8.8/document"
2411
+ ]) await t.step(`${url}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
2412
+ await withFetchStub(() => Response.json({}), async (fetched) => {
2413
+ await (0, node_assert.rejects)(loader(url), isCredentialUrlError);
2414
+ (0, node_assert.deepStrictEqual)(fetched, []);
2415
+ });
2416
+ });
2417
+ for (const url of ["https://:@8.8.8.8/document", "https://8.8.8.8/@user?q=@pass"]) await t.step(`allows ${url}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
2418
+ await withFetchStub(() => Response.json({ done: true }), async (fetched) => {
2419
+ (0, node_assert.deepStrictEqual)((await loader(url)).document, { done: true });
2420
+ (0, node_assert.deepStrictEqual)(fetched, [new URL(url).href]);
2421
+ });
2422
+ });
2423
+ }
2424
+ });
2379
2425
  function isUnsupportedProtocolError(error, url) {
2380
2426
  return error instanceof require_url.UrlError && error.message === `Unsupported protocol: ${new URL(url).protocol}` && error.reason === "disallowed";
2381
2427
  }
@@ -1,7 +1,7 @@
1
- import { t as FetchError } from "./request-BHqGsrJF.mjs";
1
+ import { t as FetchError } from "./request-ClluSsbr.mjs";
2
2
  import { n as UrlError } from "./url-DMxmp7ZG.mjs";
3
3
  import { n as cid_v1_default, t as preloadedContexts } from "./contexts-duRA_dDC.mjs";
4
- import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader--jQ3XcCi.mjs";
4
+ import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader-BwMNJWDL.mjs";
5
5
  import { t as jsonld_default } from "./jsonld-DweIJ07b.mjs";
6
6
  import { gzipSync } from "node:zlib";
7
7
  import { configure, reset } from "@logtape/logtape";
@@ -2394,6 +2394,52 @@ const unsupportedUrls = [
2394
2394
  "file:///collection",
2395
2395
  `data:application/ld+json,${encodeURIComponent(JSON.stringify({ id: "https://example.com/data" }))}`
2396
2396
  ];
2397
+ function isCredentialUrlError(error) {
2398
+ return error instanceof UrlError && error.reason === "disallowed" && error.message === "URLs with credentials are not allowed";
2399
+ }
2400
+ test("getDocumentLoader() rejects URLs with credentials", async (t) => {
2401
+ const start = "https://8.8.8.8/start";
2402
+ const target = "https://user:pass@8.8.8.8/document";
2403
+ for (const allowPrivateAddress of [false, true]) {
2404
+ const loader = getDocumentLoader({ allowPrivateAddress });
2405
+ for (const path of [
2406
+ "direct",
2407
+ "redirect",
2408
+ "Link",
2409
+ "HTML"
2410
+ ]) await t.step(`${path}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
2411
+ await withFetchStub((url) => {
2412
+ if (url !== start) return Response.json({});
2413
+ if (path === "redirect") return Response.redirect(target, 302);
2414
+ if (path === "HTML") return new Response(`<link rel="alternate" type="application/activity+json" href="${target}">`, { headers: { "Content-Type": "text/html" } });
2415
+ return new Response("", { headers: {
2416
+ "Content-Type": "text/plain",
2417
+ Link: `<${target}>; rel="alternate"; type="application/activity+json"`
2418
+ } });
2419
+ }, async (fetched) => {
2420
+ await rejects(loader(path === "direct" ? target : start), isCredentialUrlError);
2421
+ deepStrictEqual(fetched, path === "direct" ? [] : [start]);
2422
+ });
2423
+ });
2424
+ for (const url of [
2425
+ "https://user@8.8.8.8/document",
2426
+ "https://:pass@8.8.8.8/document",
2427
+ "https://%75ser:p%40ss@8.8.8.8/document",
2428
+ "http://user:pass@8.8.8.8/document"
2429
+ ]) await t.step(`${url}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
2430
+ await withFetchStub(() => Response.json({}), async (fetched) => {
2431
+ await rejects(loader(url), isCredentialUrlError);
2432
+ deepStrictEqual(fetched, []);
2433
+ });
2434
+ });
2435
+ for (const url of ["https://:@8.8.8.8/document", "https://8.8.8.8/@user?q=@pass"]) await t.step(`allows ${url}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
2436
+ await withFetchStub(() => Response.json({ done: true }), async (fetched) => {
2437
+ deepStrictEqual((await loader(url)).document, { done: true });
2438
+ deepStrictEqual(fetched, [new URL(url).href]);
2439
+ });
2440
+ });
2441
+ }
2442
+ });
2397
2443
  function isUnsupportedProtocolError(error, url) {
2398
2444
  return error instanceof UrlError && error.message === `Unsupported protocol: ${new URL(url).protocol}` && error.reason === "disallowed";
2399
2445
  }
@@ -1,6 +1,6 @@
1
- const require_request = require("./request-3qhu9psJ.cjs");
1
+ const require_request = require("./request-B5PYS32O.cjs");
2
2
  const require_url = require("./url-BNakuZ8k.cjs");
3
- const require_body = require("./body-DVcRd85t.cjs");
3
+ const require_body = require("./body-DLo6Wfqk.cjs");
4
4
  const require_digest = require("./digest-3FeH2Y-Q.cjs");
5
5
  //#region src/portable-media.ts
6
6
  function resolveTimeout(value, fallback) {
@@ -1,6 +1,6 @@
1
- import { r as getUserAgent } from "./request-BHqGsrJF.mjs";
1
+ import { r as getUserAgent } from "./request-ClluSsbr.mjs";
2
2
  import { g as parseGatewayUrl, x as validatePublicUrl } from "./url-DMxmp7ZG.mjs";
3
- import { r as readBoundedBytes } from "./body-Djuimvjd.mjs";
3
+ import { r as readBoundedBytes } from "./body-B6FmobUg.mjs";
4
4
  import { a as verifyDigestMultibase, i as parseHashlink, r as parseDigestMultibase } from "./digest-COC7xDiQ.mjs";
5
5
  //#region src/portable-media.ts
6
6
  function resolveTimeout(value, fallback) {
@@ -1,5 +1,5 @@
1
1
  const require_digest = require("./digest-3FeH2Y-Q.cjs");
2
- const require_portable_media = require("./portable-media-LtEUNA6B.cjs");
2
+ const require_portable_media = require("./portable-media-BhuGLllk.cjs");
3
3
  let node_assert_strict = require("node:assert/strict");
4
4
  let node_test = require("node:test");
5
5
  let node_http = require("node:http");
@@ -1,5 +1,5 @@
1
1
  import { n as createHashlink, t as computeDigestMultibase } from "./digest-COC7xDiQ.mjs";
2
- import { t as fetchPortableMedia } from "./portable-media-BHBIOsiA.mjs";
2
+ import { t as fetchPortableMedia } from "./portable-media-DudjnSMO.mjs";
3
3
  import { deepStrictEqual, equal, rejects } from "node:assert/strict";
4
4
  import { test } from "node:test";
5
5
  import { createServer } from "node:http";
@@ -4,7 +4,7 @@ let node_process = require("node:process");
4
4
  node_process = require_rolldown_runtime.__toESM(node_process, 1);
5
5
  //#region deno.json
6
6
  var name = "@fedify/vocab-runtime";
7
- var version = "2.5.0-dev.2424+97214632";
7
+ var version = "2.5.0-dev.2432+f56118e1";
8
8
  //#endregion
9
9
  //#region src/request.ts
10
10
  /**
@@ -2,7 +2,7 @@ import { n as UrlError } from "./url-DMxmp7ZG.mjs";
2
2
  import process from "node:process";
3
3
  //#region deno.json
4
4
  var name = "@fedify/vocab-runtime";
5
- var version = "2.5.0-dev.2424+97214632";
5
+ var version = "2.5.0-dev.2432+f56118e1";
6
6
  //#endregion
7
7
  //#region src/request.ts
8
8
  /**
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request-3qhu9psJ.cjs");
2
+ const require_request = require("./request-B5PYS32O.cjs");
3
3
  const require_url = require("./url-BNakuZ8k.cjs");
4
4
  let node_test = require("node:test");
5
5
  let node_process = require("node:process");
@@ -1,4 +1,4 @@
1
- import { i as isTransientFetchError, n as createActivityPubRequest, r as getUserAgent, s as version, t as FetchError } from "./request-BHqGsrJF.mjs";
1
+ import { i as isTransientFetchError, n as createActivityPubRequest, r as getUserAgent, s as version, t as FetchError } from "./request-ClluSsbr.mjs";
2
2
  import { n as UrlError } from "./url-DMxmp7ZG.mjs";
3
3
  import { test } from "node:test";
4
4
  import process from "node:process";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.5.0-dev.2424+97214632",
3
+ "version": "2.5.0-dev.2432+f56118e1",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1621,6 +1621,90 @@ const unsupportedUrls = [
1621
1621
  }`,
1622
1622
  ];
1623
1623
 
1624
+ function isCredentialUrlError(error: unknown): boolean {
1625
+ return error instanceof UrlError && error.reason === "disallowed" &&
1626
+ error.message === "URLs with credentials are not allowed";
1627
+ }
1628
+
1629
+ test("getDocumentLoader() rejects URLs with credentials", async (t) => {
1630
+ const start = "https://8.8.8.8/start";
1631
+ const target = "https://user:pass@8.8.8.8/document";
1632
+ for (const allowPrivateAddress of [false, true]) {
1633
+ const loader = getDocumentLoader({ allowPrivateAddress });
1634
+ for (const path of ["direct", "redirect", "Link", "HTML"]) {
1635
+ await t.step(
1636
+ `${path}, allowPrivateAddress=${allowPrivateAddress}`,
1637
+ async () => {
1638
+ await withFetchStub(
1639
+ (url) => {
1640
+ if (url !== start) return Response.json({});
1641
+ if (path === "redirect") return Response.redirect(target, 302);
1642
+ if (path === "HTML") {
1643
+ return new Response(
1644
+ `<link rel="alternate" type="application/activity+json" href="${target}">`,
1645
+ { headers: { "Content-Type": "text/html" } },
1646
+ );
1647
+ }
1648
+ return new Response("", {
1649
+ headers: {
1650
+ "Content-Type": "text/plain",
1651
+ Link:
1652
+ `<${target}>; rel="alternate"; type="application/activity+json"`,
1653
+ },
1654
+ });
1655
+ },
1656
+ async (fetched) => {
1657
+ await rejects(
1658
+ loader(path === "direct" ? target : start),
1659
+ isCredentialUrlError,
1660
+ );
1661
+ deepStrictEqual(fetched, path === "direct" ? [] : [start]);
1662
+ },
1663
+ );
1664
+ },
1665
+ );
1666
+ }
1667
+ for (
1668
+ const url of [
1669
+ "https://user@8.8.8.8/document",
1670
+ "https://:pass@8.8.8.8/document",
1671
+ "https://%75ser:p%40ss@8.8.8.8/document",
1672
+ "http://user:pass@8.8.8.8/document",
1673
+ ]
1674
+ ) {
1675
+ await t.step(
1676
+ `${url}, allowPrivateAddress=${allowPrivateAddress}`,
1677
+ async () => {
1678
+ await withFetchStub(() => Response.json({}), async (fetched) => {
1679
+ await rejects(loader(url), isCredentialUrlError);
1680
+ deepStrictEqual(fetched, []);
1681
+ });
1682
+ },
1683
+ );
1684
+ }
1685
+ for (
1686
+ const url of [
1687
+ "https://:@8.8.8.8/document",
1688
+ "https://8.8.8.8/@user?q=@pass",
1689
+ ]
1690
+ ) {
1691
+ await t.step(
1692
+ `allows ${url}, allowPrivateAddress=${allowPrivateAddress}`,
1693
+ async () => {
1694
+ await withFetchStub(
1695
+ () => Response.json({ done: true }),
1696
+ async (fetched) => {
1697
+ const document = await loader(url);
1698
+ deepStrictEqual(document.document, { done: true });
1699
+ deepStrictEqual(fetched, [new URL(url).href]);
1700
+ },
1701
+ );
1702
+ },
1703
+ );
1704
+ }
1705
+ }
1706
+ });
1707
+
1624
1708
  function isUnsupportedProtocolError(error: unknown, url: string): boolean {
1625
1709
  return error instanceof UrlError &&
1626
1710
  error.message === `Unsupported protocol: ${new URL(url).protocol}` &&
package/src/docloader.ts CHANGED
@@ -561,6 +561,11 @@ export function getDocumentLoader(
561
561
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
562
562
  throw new UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
563
563
  }
564
+ // When merging forward, keep this guard independent of allowPrivateAddress:
565
+ // private-address opt-in must not permit credentials on any followed URL.
566
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") {
567
+ throw new UrlError("URLs with credentials are not allowed");
568
+ }
564
569
  const currentUrl = parsedUrl.href;
565
570
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
566
571
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });