Skip to content

Fix ReDoS vulnerability in FirebaseError template replacement#10146

Open
macastelaz wants to merge 7 commits into
mainfrom
fix-util-redos
Open

Fix ReDoS vulnerability in FirebaseError template replacement#10146
macastelaz wants to merge 7 commits into
mainfrom
fix-util-redos

Conversation

@macastelaz

Copy link
Copy Markdown
Contributor

The Issue:
CodeQL correctly detected a Regular Expression Denial of Service (ReDoS) vulnerability.
In replaceTemplate, the template replacement logic used this regular expression to parse custom data variables:
const PATTERN = /{$([^}]+)}/g;

The + there is what we call "greedy." If the SDK received a malformed string lacking a closing } (e.g.,
{$aaaaaaaaaaaaaaaaaaaaaaaaaaaaa ), the regex engine would consume the whole string in [^}]+ , fail to match the
} , then repetitively backtrack one character at a time, resulting in an exponentially slow O(N²) freeze that
could block the main UI thread.

The Fix:
Bypass the regex engine completely. I implemented the exact same parsing logic in packages/util/src/errors.ts
using native, deterministic indexOf and substring pointer operations. This strictly bounds execution time to
O(N) linear time—making a ReDoS attack mathematically impossible.

@macastelaz macastelaz requested a review from a team as a code owner July 10, 2026 02:46
@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a592930

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
@firebase/util Patch
@firebase/ai Patch
@firebase/analytics-compat Patch
@firebase/analytics Patch
@firebase/app-check-compat Patch
@firebase/app-check Patch
@firebase/app-compat Patch
@firebase/app Patch
@firebase/auth-compat Patch
@firebase/auth Patch
@firebase/component Patch
@firebase/data-connect Patch
@firebase/database-compat Patch
@firebase/database-types Patch
@firebase/database Patch
firebase Patch
@firebase/firestore-compat Patch
@firebase/firestore Patch
@firebase/functions-compat Patch
@firebase/functions Patch
@firebase/installations-compat Patch
@firebase/installations Patch
@firebase/messaging-compat Patch
@firebase/messaging Patch
@firebase/performance-compat Patch
@firebase/performance Patch
@firebase/remote-config-compat Patch
@firebase/remote-config Patch
@firebase/storage-compat Patch
@firebase/storage Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces the regex-based template replacement in replaceTemplate with a manual string-parsing loop inside a try-catch block. Feedback was provided regarding a bug in the error handling: if an exception occurs during parsing, the function would return an empty or partially constructed string. A code suggestion was provided to ensure the original template is returned as a fallback in the catch block.

Comment thread packages/util/src/errors.ts Outdated
Comment on lines +130 to +153
let result = template;
try {
let ptr = 0;
result = '';
while (ptr < template.length) {
const start = template.indexOf('{$', ptr);
if (start === -1) {
result += template.substring(ptr);
break;
}
const end = template.indexOf('}', start + 2);
if (end === -1) {
result += template.substring(ptr);
break;
}
const key = template.substring(start + 2, end);
const value = data[key];
result += template.substring(ptr, start) + (value != null ? String(value) : `<${key}?>`);
ptr = end + 1;
}
} catch (e) {
// Should never happen, but fallback just in case
}
return result;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If an exception is thrown during the template parsing (for example, if data is null/undefined, or if String(value) throws), the catch block does nothing, and the function returns result.

Since result is initialized to '' at the start of the try block, any error will cause the function to return an empty string or a partially constructed string, completely losing the error message template and making debugging extremely difficult.

We should use a local variable for the building process and return the original template in the catch block as a safe fallback.

  try {
    let ptr = 0;
    let result = '';
    while (ptr < template.length) {
      const start = template.indexOf('{$', ptr);
      if (start === -1) {
        result += template.substring(ptr);
        break;
      }
      const end = template.indexOf('}', start + 2);
      if (end === -1) {
        result += template.substring(ptr);
        break;
      }
      const key = template.substring(start + 2, end);
      const value = data[key];
      result += template.substring(ptr, start) + (value != null ? String(value) : `<${key}?>`);
      ptr = end + 1;
    }
    return result;
  } catch (e) {
    // Should never happen, but fallback just in case
    return template;
  }

@macastelaz macastelaz requested review from a team as code owners July 10, 2026 04:00
@github-actions

Copy link
Copy Markdown
Contributor

Changeset File Check ⚠️

  • Warning: This PR modifies files in the following packages but they have not been included in the changeset file:%0A - @firebase/auth%0A%0A Make sure this was intentional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant