Unverified Commit 75036491 authored by Hong Minhee's avatar Hong Minhee
Browse files

Merge tag '0.11.1'

Fedify 0.11.1
parents 99d302f3 85b9b756
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@
    "RSASSA-PKCS1",
    "setext",
    "spki",
    "SSRF",
    "subproperty",
    "superproperty",
    "supertypes",
+56 −0
Original line number Diff line number Diff line
@@ -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:

@@ -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
--------------

@@ -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
--------------

@@ -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
-------------

+31 −0
Original line number Diff line number Diff line
@@ -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.");
@@ -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) => {
@@ -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) => {
+3 −0
Original line number Diff line number Diff line
@@ -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"]);

@@ -136,6 +137,7 @@ export async function fetchDocumentLoader(
      documentUrl: url,
    };
  }
  await validatePublicUrl(url);
  const request = createRequest(url);
  logRequest(request);
  const response = await fetch(request, {
@@ -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);

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