Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/breezy-symbols-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@firebase/util": patch
---

Fix ReDoS vulnerability in FirebaseError template replacement
4 changes: 4 additions & 0 deletions .github/workflows/test-changed-auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ jobs:
with:
node-version: 22.12.0
- name: Test setup and yarn install
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
run: |
cp config/ci.config.json config/project.json
yarn
Expand All @@ -116,6 +118,8 @@ jobs:
with:
node-version: 22.12.0
- name: Test setup and yarn install
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
run: |
cp config/ci.config.json config/project.json
yarn
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test-changed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ jobs:
- name: install Firefox stable
run: npx @puppeteer/browsers install firefox@stable
- name: Test setup and yarn install
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
run: |
cp config/ci.config.json config/project.json
yarn
Expand All @@ -95,6 +97,8 @@ jobs:
with:
node-version: 22.12.0
- name: Test setup and yarn install
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
run: |
cp config/ci.config.json config/project.json
yarn
Expand Down
13 changes: 13 additions & 0 deletions packages/auth/firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"emulators": {
"auth": {
"port": 9098
},
"hub": {
"port": 4402
},
"logging": {
"port": 4502
}
}
}
2 changes: 1 addition & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"@rollup/plugin-strip": "3.0.4",
"@types/express": "4.17.21",
"@types/selenium-webdriver": "4.35.6",
"chromedriver": "119.0.1",
"chromedriver": "^150.0.0",
"cookie-store": "4.0.0-next.4",
"rollup": "2.79.2",
"rollup-plugin-sourcemaps": "0.6.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ browserDescribe('WebDriver integration with FirebaseUI', driver => {
const page = await startUi();
await page.clickGuestSignIn();
await waitForLoggedInPage();
const snap = (await driver.getUserSnapshot()) as User;
const snap = (await driver.getUserSnapshot()) as unknown as User;
expect(snap.isAnonymous).to.be.true;
expect(snap.uid).to.be.a('string');
});
Expand All @@ -70,7 +70,7 @@ browserDescribe('WebDriver integration with FirebaseUI', driver => {
await driver.call(UiFunction.LOAD);
await startUi();
await waitForLoggedInPage();
const snap = (await driver.getUserSnapshot()) as User;
const snap = (await driver.getUserSnapshot()) as unknown as User;
expect(snap.isAnonymous).to.be.false;
expect(snap.displayName).to.eq('Bob Test');
expect(snap.email).to.eq('bob@bob.test');
Expand All @@ -97,7 +97,7 @@ browserDescribe('WebDriver integration with FirebaseUI', driver => {
// Now we're back. Firebase UI should handle the redirect result handoff
await driver.selectMainWindow();
await waitForLoggedInPage();
const snap = (await driver.getUserSnapshot()) as User;
const snap = (await driver.getUserSnapshot()) as unknown as User;
expect(snap.isAnonymous).to.be.false;
expect(snap.displayName).to.eq('Bob Test');
expect(snap.email).to.eq('bob@bob.test');
Expand Down Expand Up @@ -135,7 +135,7 @@ browserDescribe('WebDriver integration with FirebaseUI', driver => {
await page.clickSubmit();

await waitForLoggedInPage();
const snap = (await driver.getUserSnapshot()) as User;
const snap = (await driver.getUserSnapshot()) as unknown as User;
expect(snap.isAnonymous).to.be.false;
expect(snap.phoneNumber).to.eq(`+1${phoneNumber}`);
expect(snap.uid).to.be.a('string');
Expand All @@ -152,7 +152,7 @@ browserDescribe('WebDriver integration with FirebaseUI', driver => {
await page.clickSubmit();

await waitForLoggedInPage();
const snap = (await driver.getUserSnapshot()) as User;
const snap = (await driver.getUserSnapshot()) as unknown as User;
expect(snap.isAnonymous).to.be.false;
expect(snap.displayName).to.eq('Foo Test');
expect(snap.email).to.eq('foo@foo.test');
Expand Down
32 changes: 26 additions & 6 deletions packages/util/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,30 @@ export class ErrorFactory<
}

function replaceTemplate(template: string, data: ErrorData): string {
return template.replace(PATTERN, (_, key) => {
const value = data[key];
return value != null ? String(value) : `<${key}?>`;
});
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;
}
}

const PATTERN = /\{\$([^}]+)}/g;
13 changes: 13 additions & 0 deletions packages/util/test/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ describe('FirebaseError', () => {
);
});

it('falls back to template if template replacement throws', () => {
const e = ERROR_FACTORY.create('file-not-found', {
get file() {
throw new Error('accessing file throws');
}
} as any);
assert.equal((e as FirebaseError)?.code, 'fake/file-not-found');
assert.equal(
e.message,
"Fake: Could not find file: '{$file}' (fake/file-not-found)."
);
});

it('has stack', () => {
const e = ERROR_FACTORY.create('generic-error');

Expand Down
Loading
Loading