Loading .vscode/settings.json +1 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ "RSASSA-PKCS1", "setext", "spki", "SSRF", "subproperty", "superproperty", "supertypes", Loading CHANGES.md +56 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,15 @@ To be released. - The last parameter of `Federation.sendActivity()` method is no longer optional. Also, it now takes the required `contextData` option. - Fixed a SSRF vulnerability in the built-in document loader. [[CVE-2024-39687]] - The `fetchDocumentLoader()` function now throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - The `getAuthenticatedDocumentLoader()` function now returns a document loader that throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - Added more log messages using the [LogTape] library. Currently the below logger categories are used: Loading @@ -65,6 +74,21 @@ To be released. [#85]: https://github.com/dahlia/fedify/issues/85 Version 0.11.1 -------------- Released on July 5, 2024. - Fixed a SSRF vulnerability in the built-in document loader. [[CVE-2024-39687]] - The `fetchDocumentLoader()` function now throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - The `getAuthenticatedDocumentLoader()` function now returns a document loader that throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. Version 0.11.0 -------------- Loading Loading @@ -248,6 +272,21 @@ Released on June 29, 2024. [#80]: https://github.com/dahlia/fedify/pull/80 Version 0.10.1 -------------- Released on July 5, 2024. - Fixed a SSRF vulnerability in the built-in document loader. [[CVE-2024-39687]] - The `fetchDocumentLoader()` function now throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - The `getAuthenticatedDocumentLoader()` function now returns a document loader that throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. Version 0.10.0 -------------- Loading Loading @@ -409,6 +448,23 @@ is now distributed under the [MIT License] to encourage wider adoption. [x-forwarded-fetch]: https://github.com/dahlia/x-forwarded-fetch Version 0.9.2 ------------- Released on July 5, 2024. - Fixed a SSRF vulnerability in the built-in document loader. [[CVE-2024-39687]] - The `fetchDocumentLoader()` function now throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - The `getAuthenticatedDocumentLoader()` function now returns a document loader that throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. [CVE-2024-39687]: https://github.com/dahlia/fedify/security/advisories/GHSA-p9cg-vqcc-grcx Version 0.9.1 ------------- Loading runtime/docloader.test.ts +31 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ import { getAuthenticatedDocumentLoader, kvCache, } from "./docloader.ts"; import { UrlError } from "./url.ts"; test("new FetchError()", () => { const e = new FetchError("https://example.com/", "An error message."); Loading Loading @@ -72,6 +73,20 @@ test("fetchDocumentLoader()", async (t) => { }); } }); await t.step("deny non-HTTP/HTTPS", async () => { await assertRejects( () => fetchDocumentLoader("ftp://localhost"), UrlError, ); }); await t.step("deny private network", async () => { await assertRejects( () => fetchDocumentLoader("https://localhost"), UrlError, ); }); }); test("getAuthenticatedDocumentLoader()", async (t) => { Loading Loading @@ -104,6 +119,22 @@ test("getAuthenticatedDocumentLoader()", async (t) => { }); mf.uninstall(); await t.step("deny non-HTTP/HTTPS", async () => { const loader = await getAuthenticatedDocumentLoader({ keyId: new URL("https://example.com/key2"), privateKey: rsaPrivateKey2, }); assertRejects(() => loader("ftp://localhost"), UrlError); }); await t.step("deny private network", async () => { const loader = await getAuthenticatedDocumentLoader({ keyId: new URL("https://example.com/key2"), privateKey: rsaPrivateKey2, }); assertRejects(() => loader("http://localhost"), UrlError); }); }); test("kvCache()", async (t) => { Loading runtime/docloader.ts +3 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ import type { KvKey, KvStore } from "../federation/kv.ts"; import { signRequest } from "../sig/http.ts"; import { validateCryptoKey } from "../sig/key.ts"; import preloadedContexts from "./contexts.ts"; import { validatePublicUrl } from "./url.ts"; const logger = getLogger(["fedify", "runtime", "docloader"]); Loading Loading @@ -136,6 +137,7 @@ export async function fetchDocumentLoader( documentUrl: url, }; } await validatePublicUrl(url); const request = createRequest(url); logRequest(request); const response = await fetch(request, { Loading Loading @@ -169,6 +171,7 @@ export function getAuthenticatedDocumentLoader( ): DocumentLoader { validateCryptoKey(identity.privateKey); async function load(url: string): Promise<RemoteDocument> { await validatePublicUrl(url); let request = createRequest(url); request = await signRequest(request, identity.privateKey, identity.keyId); logRequest(request); Loading runtime/url.test.ts 0 → 100644 +62 −0 Original line number Diff line number Diff line import { assert } from "@std/assert/assert"; import { assertEquals } from "@std/assert/assert-equals"; import { assertFalse } from "@std/assert/assert-false"; import { assertRejects } from "@std/assert/assert-rejects"; import { test } from "../testing/mod.ts"; import { expandIPv6Address, isValidPublicIPv4Address, isValidPublicIPv6Address, UrlError, validatePublicUrl, } from "./url.ts"; test("validatePublicUrl()", async () => { await assertRejects(() => validatePublicUrl("ftp://localhost"), UrlError); await assertRejects( // cSpell: disable () => validatePublicUrl("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="), // cSpell: enable UrlError, ); await assertRejects(() => validatePublicUrl("https://localhost"), UrlError); await assertRejects(() => validatePublicUrl("https://127.0.0.1"), UrlError); await assertRejects(() => validatePublicUrl("https://[::1]"), UrlError); }); test("isValidPublicIPv4Address()", () => { assert(isValidPublicIPv4Address("8.8.8.8")); // Google DNS assertFalse(isValidPublicIPv4Address("192.168.1.1")); // private assertFalse(isValidPublicIPv4Address("127.0.0.1")); // localhost assertFalse(isValidPublicIPv4Address("10.0.0.1")); // private assertFalse(isValidPublicIPv4Address("127.16.0.1")); // private assertFalse(isValidPublicIPv4Address("169.254.0.1")); // link-local }); test("isValidPublicIPv6Address()", () => { assert(isValidPublicIPv6Address("2001:db8::1")); assertFalse(isValidPublicIPv6Address("::1")); // localhost assertFalse(isValidPublicIPv6Address("fc00::1")); // ULA assertFalse(isValidPublicIPv6Address("fe80::1")); // link-local assertFalse(isValidPublicIPv6Address("ff00::1")); // multicast assertFalse(isValidPublicIPv6Address("::")); // unspecified }); test("expandIPv6Address()", () => { assertEquals( expandIPv6Address("::"), "0000:0000:0000:0000:0000:0000:0000:0000", ); assertEquals( expandIPv6Address("::1"), "0000:0000:0000:0000:0000:0000:0000:0001", ); assertEquals( expandIPv6Address("2001:db8::"), "2001:0db8:0000:0000:0000:0000:0000:0000", ); assertEquals( expandIPv6Address("2001:db8::1"), "2001:0db8:0000:0000:0000:0000:0000:0001", ); }); Loading
.vscode/settings.json +1 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ "RSASSA-PKCS1", "setext", "spki", "SSRF", "subproperty", "superproperty", "supertypes", Loading
CHANGES.md +56 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,15 @@ To be released. - The last parameter of `Federation.sendActivity()` method is no longer optional. Also, it now takes the required `contextData` option. - Fixed a SSRF vulnerability in the built-in document loader. [[CVE-2024-39687]] - The `fetchDocumentLoader()` function now throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - The `getAuthenticatedDocumentLoader()` function now returns a document loader that throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - Added more log messages using the [LogTape] library. Currently the below logger categories are used: Loading @@ -65,6 +74,21 @@ To be released. [#85]: https://github.com/dahlia/fedify/issues/85 Version 0.11.1 -------------- Released on July 5, 2024. - Fixed a SSRF vulnerability in the built-in document loader. [[CVE-2024-39687]] - The `fetchDocumentLoader()` function now throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - The `getAuthenticatedDocumentLoader()` function now returns a document loader that throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. Version 0.11.0 -------------- Loading Loading @@ -248,6 +272,21 @@ Released on June 29, 2024. [#80]: https://github.com/dahlia/fedify/pull/80 Version 0.10.1 -------------- Released on July 5, 2024. - Fixed a SSRF vulnerability in the built-in document loader. [[CVE-2024-39687]] - The `fetchDocumentLoader()` function now throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - The `getAuthenticatedDocumentLoader()` function now returns a document loader that throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. Version 0.10.0 -------------- Loading Loading @@ -409,6 +448,23 @@ is now distributed under the [MIT License] to encourage wider adoption. [x-forwarded-fetch]: https://github.com/dahlia/x-forwarded-fetch Version 0.9.2 ------------- Released on July 5, 2024. - Fixed a SSRF vulnerability in the built-in document loader. [[CVE-2024-39687]] - The `fetchDocumentLoader()` function now throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. - The `getAuthenticatedDocumentLoader()` function now returns a document loader that throws an error when the given URL is not an HTTP or HTTPS URL or refers to a private network address. [CVE-2024-39687]: https://github.com/dahlia/fedify/security/advisories/GHSA-p9cg-vqcc-grcx Version 0.9.1 ------------- Loading
runtime/docloader.test.ts +31 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ import { getAuthenticatedDocumentLoader, kvCache, } from "./docloader.ts"; import { UrlError } from "./url.ts"; test("new FetchError()", () => { const e = new FetchError("https://example.com/", "An error message."); Loading Loading @@ -72,6 +73,20 @@ test("fetchDocumentLoader()", async (t) => { }); } }); await t.step("deny non-HTTP/HTTPS", async () => { await assertRejects( () => fetchDocumentLoader("ftp://localhost"), UrlError, ); }); await t.step("deny private network", async () => { await assertRejects( () => fetchDocumentLoader("https://localhost"), UrlError, ); }); }); test("getAuthenticatedDocumentLoader()", async (t) => { Loading Loading @@ -104,6 +119,22 @@ test("getAuthenticatedDocumentLoader()", async (t) => { }); mf.uninstall(); await t.step("deny non-HTTP/HTTPS", async () => { const loader = await getAuthenticatedDocumentLoader({ keyId: new URL("https://example.com/key2"), privateKey: rsaPrivateKey2, }); assertRejects(() => loader("ftp://localhost"), UrlError); }); await t.step("deny private network", async () => { const loader = await getAuthenticatedDocumentLoader({ keyId: new URL("https://example.com/key2"), privateKey: rsaPrivateKey2, }); assertRejects(() => loader("http://localhost"), UrlError); }); }); test("kvCache()", async (t) => { Loading
runtime/docloader.ts +3 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ import type { KvKey, KvStore } from "../federation/kv.ts"; import { signRequest } from "../sig/http.ts"; import { validateCryptoKey } from "../sig/key.ts"; import preloadedContexts from "./contexts.ts"; import { validatePublicUrl } from "./url.ts"; const logger = getLogger(["fedify", "runtime", "docloader"]); Loading Loading @@ -136,6 +137,7 @@ export async function fetchDocumentLoader( documentUrl: url, }; } await validatePublicUrl(url); const request = createRequest(url); logRequest(request); const response = await fetch(request, { Loading Loading @@ -169,6 +171,7 @@ export function getAuthenticatedDocumentLoader( ): DocumentLoader { validateCryptoKey(identity.privateKey); async function load(url: string): Promise<RemoteDocument> { await validatePublicUrl(url); let request = createRequest(url); request = await signRequest(request, identity.privateKey, identity.keyId); logRequest(request); Loading
runtime/url.test.ts 0 → 100644 +62 −0 Original line number Diff line number Diff line import { assert } from "@std/assert/assert"; import { assertEquals } from "@std/assert/assert-equals"; import { assertFalse } from "@std/assert/assert-false"; import { assertRejects } from "@std/assert/assert-rejects"; import { test } from "../testing/mod.ts"; import { expandIPv6Address, isValidPublicIPv4Address, isValidPublicIPv6Address, UrlError, validatePublicUrl, } from "./url.ts"; test("validatePublicUrl()", async () => { await assertRejects(() => validatePublicUrl("ftp://localhost"), UrlError); await assertRejects( // cSpell: disable () => validatePublicUrl("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="), // cSpell: enable UrlError, ); await assertRejects(() => validatePublicUrl("https://localhost"), UrlError); await assertRejects(() => validatePublicUrl("https://127.0.0.1"), UrlError); await assertRejects(() => validatePublicUrl("https://[::1]"), UrlError); }); test("isValidPublicIPv4Address()", () => { assert(isValidPublicIPv4Address("8.8.8.8")); // Google DNS assertFalse(isValidPublicIPv4Address("192.168.1.1")); // private assertFalse(isValidPublicIPv4Address("127.0.0.1")); // localhost assertFalse(isValidPublicIPv4Address("10.0.0.1")); // private assertFalse(isValidPublicIPv4Address("127.16.0.1")); // private assertFalse(isValidPublicIPv4Address("169.254.0.1")); // link-local }); test("isValidPublicIPv6Address()", () => { assert(isValidPublicIPv6Address("2001:db8::1")); assertFalse(isValidPublicIPv6Address("::1")); // localhost assertFalse(isValidPublicIPv6Address("fc00::1")); // ULA assertFalse(isValidPublicIPv6Address("fe80::1")); // link-local assertFalse(isValidPublicIPv6Address("ff00::1")); // multicast assertFalse(isValidPublicIPv6Address("::")); // unspecified }); test("expandIPv6Address()", () => { assertEquals( expandIPv6Address("::"), "0000:0000:0000:0000:0000:0000:0000:0000", ); assertEquals( expandIPv6Address("::1"), "0000:0000:0000:0000:0000:0000:0000:0001", ); assertEquals( expandIPv6Address("2001:db8::"), "2001:0db8:0000:0000:0000:0000:0000:0000", ); assertEquals( expandIPv6Address("2001:db8::1"), "2001:0db8:0000:0000:0000:0000:0000:0001", ); });