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

Merge tag '1.9.2'

Fedify 1.9.2
parents 4ac6d43b 3114e4ad
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
@@ -48,6 +48,26 @@ To be released.
[#493]: https://github.com/fedify-dev/fedify/pull/493


Version 1.9.2
-------------

Released on December 20, 2025.

### @fedify/fedify

 -  Fixed a ReDoS (Regular Expression Denial of Service) vulnerability in
    the document loader's HTML parsing.  An attacker-controlled server could
    respond with a malicious HTML payload that blocked the event loop.
    [[CVE-2025-68475]]

### @fedify/sqlite

 -  Fixed `SyntaxError: Identifier 'Temporal' has already been declared` error
    that occurred when using `SqliteKvStore` on Node.js or Bun.  The error
    was caused by duplicate `Temporal` imports during the build process.
    [[#487]]


Version 1.9.1
-------------

@@ -365,6 +385,28 @@ Released on October 14, 2025.
    CommonJS-based Node.js applications.  [[#429], [#431]]


Version 1.8.15
--------------

Released on December 20, 2025.

### @fedify/fedify

 -  Fixed a ReDoS (Regular Expression Denial of Service) vulnerability in
    the document loader's HTML parsing.  An attacker-controlled server could
    respond with a malicious HTML payload that blocked the event loop.
    [[CVE-2025-68475]]

### @fedify/sqlite

 -  Fixed `SyntaxError: Identifier 'Temporal' has already been declared` error
    that occurred when using `SqliteKvStore` on Node.js or Bun.  The error
    was caused by duplicate `Temporal` imports during the build process.
    [[#487]]

[#487]: https://github.com/fedify-dev/fedify/issues/487


Version 1.8.14
--------------

@@ -800,6 +842,17 @@ the versioning.
[iTerm]: https://iterm2.com/


Version 1.7.14
--------------

Released on December 20, 2025.

 -  Fixed a ReDoS (Regular Expression Denial of Service) vulnerability in
    the document loader's HTML parsing.  An attacker-controlled server could
    respond with a malicious HTML payload that blocked the event loop.
    [[CVE-2025-68475]]


Version 1.7.13
--------------

@@ -985,6 +1038,19 @@ Released on June 25, 2025.
[#252]: https://github.com/fedify-dev/fedify/pull/252


Version 1.6.13
--------------

Released on December 20, 2025.

 -  Fixed a ReDoS (Regular Expression Denial of Service) vulnerability in
    the document loader's HTML parsing.  An attacker-controlled server could
    respond with a malicious HTML payload that blocked the event loop.
    [[CVE-2025-68475]]

[CVE-2025-68475]: https://github.com/fedify-dev/fedify/security/advisories/GHSA-rchf-xwx2-hm93


Version 1.6.12
--------------

+19 −0
Original line number Diff line number Diff line
@@ -127,9 +127,28 @@ const federation = createFederation<void>({

:::

> [!TIP]
> If you are using Bun, you may encounter a type error in your IDE because
> TypeScript's language server runs on Node.js and resolves the
> `@fedify/sqlite` module using Node.js [conditional exports] instead of
> Bun's.  To fix this, add the following to your *tsconfig.json*:
>
> ~~~~ json
> {
>   "compilerOptions": {
>     "moduleResolution": "bundler",
>     "customConditions": ["bun"]
>   }
> }
> ~~~~
>
> This tells TypeScript to use Bun's module resolution when resolving
> [conditional exports].

[`SqliteKvStore`]: https://jsr.io/@fedify/sqlite/doc/kv/~/SqliteKvStore
[`node:sqlite`]: https://nodejs.org/api/sqlite.html
[`bun:sqlite`]: https://bun.com/docs/api/sqlite
[conditional exports]: https://nodejs.org/api/packages.html#conditional-exports

### `DenoKvStore` (Deno only)

+29 −1
Original line number Diff line number Diff line
import { assertEquals, assertRejects, assertThrows } from "@std/assert";
import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert";
import fetchMock from "fetch-mock";
import process from "node:process";
import metadata from "../../deno.json" with { type: "json" };
@@ -366,6 +366,34 @@ test("getDocumentLoader()", async (t) => {
    );
  });

  // Regression test for ReDoS vulnerability (CVE-2025-68475)
  // Malicious HTML payload: <a a="b" a="b" ... (unclosed tag)
  // With the vulnerable regex, this causes catastrophic backtracking
  const maliciousPayload = "<a" + ' a="b"'.repeat(30) + " ";

  fetchMock.get("https://example.com/redos", {
    body: maliciousPayload,
    headers: { "Content-Type": "text/html; charset=utf-8" },
  });

  await t.step("ReDoS resistance (CVE-2025-68475)", async () => {
    const start = performance.now();
    // The malicious HTML will fail JSON parsing, but the important thing is
    // that it should complete quickly (not hang due to ReDoS)
    await assertRejects(
      () => fetchDocumentLoader("https://example.com/redos"),
      SyntaxError,
    );
    const elapsed = performance.now() - start;

    // Should complete in under 1 second. With the vulnerable regex,
    // this would take 14+ seconds for 30 repetitions.
    assert(
      elapsed < 1000,
      `Potential ReDoS vulnerability detected: ${elapsed}ms (expected < 1000ms)`,
    );
  });

  fetchMock.hardReset();
});

+46 −28
Original line number Diff line number Diff line
@@ -255,21 +255,38 @@ export async function getRemoteDocument(
      contentType === "application/xhtml+xml" ||
      contentType?.startsWith("application/xhtml+xml;"))
  ) {
    const p =
      /<(a|link)((\s+[a-z][a-z:_-]*=("[^"]*"|'[^']*'|[^\s>]+))+)\s*\/?>/ig;
    const p2 = /\s+([a-z][a-z:_-]*)=("([^"]*)"|'([^']*)'|([^\s>]+))/ig;
    // Security: Limit HTML response size to mitigate ReDoS attacks
    const MAX_HTML_SIZE = 1024 * 1024; // 1MB
    const html = await response.text();
    let m: RegExpExecArray | null;
    const rawAttribs: string[] = [];
    while ((m = p.exec(html)) !== null) rawAttribs.push(m[2]);
    for (const rawAttrs of rawAttribs) {
      let m2: RegExpExecArray | null;
    if (html.length > MAX_HTML_SIZE) {
      logger.warn(
        "HTML response too large, skipping alternate link discovery: {url}",
        { url: documentUrl, size: html.length },
      );
      document = JSON.parse(html);
    } else {
      // Safe regex patterns without nested quantifiers to prevent ReDoS
      // (CVE-2025-68475)
      // Step 1: Extract <a ...> or <link ...> tags
      const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
      // Step 2: Parse attributes
      const attrPattern =
        /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;

      let tagMatch: RegExpExecArray | null;
      while ((tagMatch = tagPattern.exec(html)) !== null) {
        const tagContent = tagMatch[2];
        let attrMatch: RegExpExecArray | null;
        const attribs: Record<string, string> = {};
      while ((m2 = p2.exec(rawAttrs)) !== null) {
        const key = m2[1].toLowerCase();
        const value = m2[3] ?? m2[4] ?? m2[5] ?? "";

        // Reset regex state for attribute parsing
        attrPattern.lastIndex = 0;
        while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
          const key = attrMatch[1].toLowerCase();
          const value = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
          attribs[key] = value;
        }

        if (
          attribs.rel === "alternate" && "type" in attribs && (
            attribs.type === "application/activity+json" ||
@@ -286,6 +303,7 @@ export async function getRemoteDocument(
        }
      }
      document = JSON.parse(html);
    }
  } else {
    document = await response.json();
  }
+0 −2
Original line number Diff line number Diff line
@@ -7,8 +7,6 @@
    "./kv": "./src/kv.ts"
  },
  "imports": {
    "@fedify/sqlite": "./src/mod.ts",
    "@fedify/sqlite/": "./src/",
    "#sqlite": "./src/sqlite.node.ts"
  },
  "exclude": [
Loading