Loading CHANGES.md +88 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,23 @@ Version 1.3.4 To be released. - Fixed several security vulnerabilities of the `lookupWebFinger()` function. [[CVE-2025-23221]] - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the infinite number of redirects, which could lead to a denial of service attack. Now it follows up to 5 redirects. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to other than the HTTP/HTTPS schemes, which could lead to a security breach. Now it follows only the same scheme as the original request. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to the private network addresses, which could lead to a SSRF attack. Now it follows only the public network addresses. Version 1.3.3 ------------- Loading Loading @@ -153,6 +170,29 @@ Released on November 30, 2024. [#193]: https://github.com/dahlia/fedify/issues/193 Version 1.2.11 -------------- Released on January 21, 2025. - Fixed several security vulnerabilities of the `lookupWebFinger()` function. [[CVE-2025-23221]] - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the infinite number of redirects, which could lead to a denial of service attack. Now it follows up to 5 redirects. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to other than the HTTP/HTTPS schemes, which could lead to a security breach. Now it follows only the same scheme as the original request. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to the private network addresses, which could lead to a SSRF attack. Now it follows only the public network addresses. Version 1.2.10 -------------- Loading Loading @@ -377,6 +417,29 @@ Released on October 31, 2024. [#118]: https://github.com/dahlia/fedify/issues/118 Version 1.1.11 -------------- Released on January 21, 2025. - Fixed several security vulnerabilities of the `lookupWebFinger()` function. [[CVE-2025-23221]] - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the infinite number of redirects, which could lead to a denial of service attack. Now it follows up to 5 redirects. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to other than the HTTP/HTTPS schemes, which could lead to a security breach. Now it follows only the same scheme as the original request. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to the private network addresses, which could lead to a SSRF attack. Now it follows only the public network addresses. Version 1.1.10 -------------- Loading Loading @@ -642,6 +705,31 @@ Released on October 20, 2024. [#150]: https://github.com/dahlia/fedify/issues/150 Version 1.0.14 -------------- Released on January 21, 2025. - Fixed several security vulnerabilities of the `lookupWebFinger()` function. [[CVE-2025-23221]] - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the infinite number of redirects, which could lead to a denial of service attack. Now it follows up to 5 redirects. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to other than the HTTP/HTTPS schemes, which could lead to a security breach. Now it follows only the same scheme as the original request. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to the private network addresses, which could lead to a SSRF attack. Now it follows only the public network addresses. [CVE-2025-23221]: https://github.com/dahlia/fedify/security/advisories/GHSA-c59p-wq67-24wx Version 1.0.13 -------------- Loading src/runtime/url.ts +7 −1 Original line number Diff line number Diff line import type { LookupAddress } from "node:dns"; import { lookup } from "node:dns/promises"; import { isIP } from "node:net"; Loading Loading @@ -38,7 +39,12 @@ export async function validatePublicUrl(url: string): Promise<void> { } // To prevent SSRF via DNS rebinding, we need to resolve all IP addresses // and ensure that they are all public: const addresses = await lookup(hostname, { all: true }); let addresses: LookupAddress[]; try { addresses = await lookup(hostname, { all: true }); } catch { addresses = []; } for (const { address, family } of addresses) { if ( family === 4 && !isValidPublicIPv4Address(address) || Loading src/webfinger/lookup.test.ts +49 −1 Original line number Diff line number Diff line import { assertEquals } from "@std/assert"; import { assertEquals, assertRejects } from "@std/assert"; import { deadline } from "@std/async/deadline"; import * as mf from "mock_fetch"; import { UrlError } from "../runtime/url.ts"; import { test } from "../testing/mod.ts"; import type { ResourceDescriptor } from "./jrd.ts"; import { lookupWebFinger } from "./lookup.ts"; Loading Loading @@ -91,6 +93,52 @@ test("lookupWebFinger()", async (t) => { assertEquals(await lookupWebFinger("acct:johndoe@example.com"), expected); }); mf.mock( "GET@/.well-known/webfinger", (_) => new Response("", { status: 302, headers: { Location: "/.well-known/webfinger" }, }), ); await t.step("infinite redirection", async () => { const result = await deadline( lookupWebFinger("acct:johndoe@example.com"), 2000, ); assertEquals(result, null); }); mf.mock( "GET@/.well-known/webfinger", (_) => new Response("", { status: 302, headers: { Location: "ftp://example.com/" }, }), ); await t.step("redirection to different protocol", async () => { assertEquals(await lookupWebFinger("acct:johndoe@example.com"), null); }); mf.mock( "GET@/.well-known/webfinger", (_) => new Response("", { status: 302, headers: { Location: "https://localhost/" }, }), ); await t.step("redirection to private address", async () => { await assertRejects( () => lookupWebFinger("acct:johndoe@example.com"), UrlError, ); }); mf.uninstall(); }); Loading src/webfinger/lookup.ts +28 −1 Original line number Diff line number Diff line Loading @@ -10,10 +10,13 @@ import { getUserAgent, type GetUserAgentOptions, } from "../runtime/docloader.ts"; import { validatePublicUrl } from "../runtime/url.ts"; import type { ResourceDescriptor } from "./jrd.ts"; const logger = getLogger(["fedify", "webfinger", "lookup"]); const MAX_REDIRECTION = 5; // TODO: Make this configurable. /** * Options for {@link lookupWebFinger}. * @since 1.3.0 Loading Loading @@ -99,12 +102,14 @@ async function lookupWebFingerInternal( } let url = new URL(`${protocol}//${server}/.well-known/webfinger`); url.searchParams.set("resource", resource.href); let redirected = 0; while (true) { logger.debug( "Fetching WebFinger resource descriptor from {url}...", { url: url.href }, ); let response: Response; await validatePublicUrl(url.href); try { response = await fetch(url, { headers: { Loading @@ -126,10 +131,32 @@ async function lookupWebFingerInternal( response.status >= 300 && response.status < 400 && response.headers.has("Location") ) { url = new URL( redirected++; if (redirected >= MAX_REDIRECTION) { logger.error( "Too many redirections ({redirections}) while fetching WebFinger " + "resource descriptor.", { redirections: redirected }, ); return null; } const redirectedUrl = new URL( response.headers.get("Location")!, response.url == null || response.url === "" ? url : response.url, ); if (redirectedUrl.protocol !== url.protocol) { logger.error( "Redirected to a different protocol ({protocol} to " + "{redirectedProtocol}) while fetching WebFinger resource " + "descriptor.", { protocol: url.protocol, redirectedProtocol: redirectedUrl.protocol, }, ); return null; } url = redirectedUrl; continue; } if (!response.ok) { Loading Loading
CHANGES.md +88 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,23 @@ Version 1.3.4 To be released. - Fixed several security vulnerabilities of the `lookupWebFinger()` function. [[CVE-2025-23221]] - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the infinite number of redirects, which could lead to a denial of service attack. Now it follows up to 5 redirects. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to other than the HTTP/HTTPS schemes, which could lead to a security breach. Now it follows only the same scheme as the original request. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to the private network addresses, which could lead to a SSRF attack. Now it follows only the public network addresses. Version 1.3.3 ------------- Loading Loading @@ -153,6 +170,29 @@ Released on November 30, 2024. [#193]: https://github.com/dahlia/fedify/issues/193 Version 1.2.11 -------------- Released on January 21, 2025. - Fixed several security vulnerabilities of the `lookupWebFinger()` function. [[CVE-2025-23221]] - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the infinite number of redirects, which could lead to a denial of service attack. Now it follows up to 5 redirects. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to other than the HTTP/HTTPS schemes, which could lead to a security breach. Now it follows only the same scheme as the original request. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to the private network addresses, which could lead to a SSRF attack. Now it follows only the public network addresses. Version 1.2.10 -------------- Loading Loading @@ -377,6 +417,29 @@ Released on October 31, 2024. [#118]: https://github.com/dahlia/fedify/issues/118 Version 1.1.11 -------------- Released on January 21, 2025. - Fixed several security vulnerabilities of the `lookupWebFinger()` function. [[CVE-2025-23221]] - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the infinite number of redirects, which could lead to a denial of service attack. Now it follows up to 5 redirects. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to other than the HTTP/HTTPS schemes, which could lead to a security breach. Now it follows only the same scheme as the original request. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to the private network addresses, which could lead to a SSRF attack. Now it follows only the public network addresses. Version 1.1.10 -------------- Loading Loading @@ -642,6 +705,31 @@ Released on October 20, 2024. [#150]: https://github.com/dahlia/fedify/issues/150 Version 1.0.14 -------------- Released on January 21, 2025. - Fixed several security vulnerabilities of the `lookupWebFinger()` function. [[CVE-2025-23221]] - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the infinite number of redirects, which could lead to a denial of service attack. Now it follows up to 5 redirects. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to other than the HTTP/HTTPS schemes, which could lead to a security breach. Now it follows only the same scheme as the original request. - Fixed a security vulnerability where the `lookupWebFinger()` function had followed the redirects to the private network addresses, which could lead to a SSRF attack. Now it follows only the public network addresses. [CVE-2025-23221]: https://github.com/dahlia/fedify/security/advisories/GHSA-c59p-wq67-24wx Version 1.0.13 -------------- Loading
src/runtime/url.ts +7 −1 Original line number Diff line number Diff line import type { LookupAddress } from "node:dns"; import { lookup } from "node:dns/promises"; import { isIP } from "node:net"; Loading Loading @@ -38,7 +39,12 @@ export async function validatePublicUrl(url: string): Promise<void> { } // To prevent SSRF via DNS rebinding, we need to resolve all IP addresses // and ensure that they are all public: const addresses = await lookup(hostname, { all: true }); let addresses: LookupAddress[]; try { addresses = await lookup(hostname, { all: true }); } catch { addresses = []; } for (const { address, family } of addresses) { if ( family === 4 && !isValidPublicIPv4Address(address) || Loading
src/webfinger/lookup.test.ts +49 −1 Original line number Diff line number Diff line import { assertEquals } from "@std/assert"; import { assertEquals, assertRejects } from "@std/assert"; import { deadline } from "@std/async/deadline"; import * as mf from "mock_fetch"; import { UrlError } from "../runtime/url.ts"; import { test } from "../testing/mod.ts"; import type { ResourceDescriptor } from "./jrd.ts"; import { lookupWebFinger } from "./lookup.ts"; Loading Loading @@ -91,6 +93,52 @@ test("lookupWebFinger()", async (t) => { assertEquals(await lookupWebFinger("acct:johndoe@example.com"), expected); }); mf.mock( "GET@/.well-known/webfinger", (_) => new Response("", { status: 302, headers: { Location: "/.well-known/webfinger" }, }), ); await t.step("infinite redirection", async () => { const result = await deadline( lookupWebFinger("acct:johndoe@example.com"), 2000, ); assertEquals(result, null); }); mf.mock( "GET@/.well-known/webfinger", (_) => new Response("", { status: 302, headers: { Location: "ftp://example.com/" }, }), ); await t.step("redirection to different protocol", async () => { assertEquals(await lookupWebFinger("acct:johndoe@example.com"), null); }); mf.mock( "GET@/.well-known/webfinger", (_) => new Response("", { status: 302, headers: { Location: "https://localhost/" }, }), ); await t.step("redirection to private address", async () => { await assertRejects( () => lookupWebFinger("acct:johndoe@example.com"), UrlError, ); }); mf.uninstall(); }); Loading
src/webfinger/lookup.ts +28 −1 Original line number Diff line number Diff line Loading @@ -10,10 +10,13 @@ import { getUserAgent, type GetUserAgentOptions, } from "../runtime/docloader.ts"; import { validatePublicUrl } from "../runtime/url.ts"; import type { ResourceDescriptor } from "./jrd.ts"; const logger = getLogger(["fedify", "webfinger", "lookup"]); const MAX_REDIRECTION = 5; // TODO: Make this configurable. /** * Options for {@link lookupWebFinger}. * @since 1.3.0 Loading Loading @@ -99,12 +102,14 @@ async function lookupWebFingerInternal( } let url = new URL(`${protocol}//${server}/.well-known/webfinger`); url.searchParams.set("resource", resource.href); let redirected = 0; while (true) { logger.debug( "Fetching WebFinger resource descriptor from {url}...", { url: url.href }, ); let response: Response; await validatePublicUrl(url.href); try { response = await fetch(url, { headers: { Loading @@ -126,10 +131,32 @@ async function lookupWebFingerInternal( response.status >= 300 && response.status < 400 && response.headers.has("Location") ) { url = new URL( redirected++; if (redirected >= MAX_REDIRECTION) { logger.error( "Too many redirections ({redirections}) while fetching WebFinger " + "resource descriptor.", { redirections: redirected }, ); return null; } const redirectedUrl = new URL( response.headers.get("Location")!, response.url == null || response.url === "" ? url : response.url, ); if (redirectedUrl.protocol !== url.protocol) { logger.error( "Redirected to a different protocol ({protocol} to " + "{redirectedProtocol}) while fetching WebFinger resource " + "descriptor.", { protocol: url.protocol, redirectedProtocol: redirectedUrl.protocol, }, ); return null; } url = redirectedUrl; continue; } if (!response.ok) { Loading