From 1d9f402b57b20e8bed53c3653a1f4709565a0745 Mon Sep 17 00:00:00 2001 From: Kian Jamali Date: Fri, 21 Nov 2025 02:16:12 +0000 Subject: [PATCH] Fix trusted types violation --- packages/auth/package.json | 1 + .../src/platform_browser/iframe/gapi.test.ts | 3 ++- .../auth/src/platform_browser/iframe/gapi.ts | 4 ++- packages/auth/src/platform_browser/index.ts | 14 +++++----- .../auth/src/platform_browser/load_js.test.ts | 20 +++++++------- packages/auth/src/platform_browser/load_js.ts | 26 ++++++++++--------- .../recaptcha_enterprise_verifier.ts | 5 ++-- .../recaptcha/recaptcha_loader.ts | 12 ++++----- yarn.lock | 5 ++++ 9 files changed, 52 insertions(+), 38 deletions(-) diff --git a/packages/auth/package.json b/packages/auth/package.json index 2d9ed76a1e0..d0ab7bbc3b4 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -127,6 +127,7 @@ "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", + "safevalues": "1.2.0", "tslib": "^2.1.0" }, "license": "Apache-2.0", diff --git a/packages/auth/src/platform_browser/iframe/gapi.test.ts b/packages/auth/src/platform_browser/iframe/gapi.test.ts index 4a14e332e4b..0823b5551a9 100644 --- a/packages/auth/src/platform_browser/iframe/gapi.test.ts +++ b/packages/auth/src/platform_browser/iframe/gapi.test.ts @@ -26,6 +26,7 @@ import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; import { _window } from '../auth_window'; import * as js from '../load_js'; import { _loadGapi, _resetLoader } from './gapi'; +import { unwrapResourceUrl } from 'safevalues'; use(sinonChai); use(chaiAsPromised); @@ -41,7 +42,7 @@ describe('platform_browser/iframe/gapi', () => { beforeEach(async () => { loadJsStub = sinon.stub(js, '_loadJS').callsFake(url => { - onJsLoad(url.split('onload=')[1]); + onJsLoad(unwrapResourceUrl(url).split('onload=')[1]); return Promise.resolve(new Event('load')); }); diff --git a/packages/auth/src/platform_browser/iframe/gapi.ts b/packages/auth/src/platform_browser/iframe/gapi.ts index 7279c2f61e0..d154556422d 100644 --- a/packages/auth/src/platform_browser/iframe/gapi.ts +++ b/packages/auth/src/platform_browser/iframe/gapi.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { appendParams } from 'safevalues'; + import { AuthErrorCode } from '../../core/errors'; import { _createError } from '../../core/util/assert'; import { Delay } from '../../core/util/delay'; @@ -104,7 +106,7 @@ function loadGapi(auth: AuthInternal): Promise { }; // Load GApi loader. return js - ._loadJS(`${js._gapiScriptUrl()}?onload=${cbName}`) + ._loadJS(appendParams(js._gapiScriptUrl(), new Map([['onload', cbName]]))) .catch(e => reject(e)); } }).catch(error => { diff --git a/packages/auth/src/platform_browser/index.ts b/packages/auth/src/platform_browser/index.ts index f94525bfeb7..440d382bdaf 100644 --- a/packages/auth/src/platform_browser/index.ts +++ b/packages/auth/src/platform_browser/index.ts @@ -16,6 +16,8 @@ */ import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; +import { TrustedResourceUrl, trustedResourceUrl } from 'safevalues'; +import { setScriptSrc } from 'safevalues/dom'; import { initializeAuth, @@ -120,11 +122,11 @@ function getScriptParentElement(): HTMLDocument | HTMLHeadElement { } _setExternalJSProvider({ - loadJS(url: string): Promise { + loadJS(url: TrustedResourceUrl): Promise { // TODO: consider adding timeout support & cancellation return new Promise((resolve, reject) => { - const el = document.createElement('script'); - el.setAttribute('src', url); + const el = document.createElement('script') as HTMLScriptElement; + setScriptSrc(el, url); el.onload = resolve; el.onerror = e => { const error = _createError(AuthErrorCode.INTERNAL_ERROR); @@ -137,10 +139,10 @@ _setExternalJSProvider({ }); }, - gapiScript: 'https://apis.google.com/js/api.js', - recaptchaV2Script: 'https://www.google.com/recaptcha/api.js', + gapiScript: trustedResourceUrl`https://apis.google.com/js/api.js`, + recaptchaV2Script: trustedResourceUrl`https://www.google.com/recaptcha/api.js`, recaptchaEnterpriseScript: - 'https://www.google.com/recaptcha/enterprise.js?render=' + trustedResourceUrl`https://www.google.com/recaptcha/enterprise.js` }); registerAuth(ClientPlatform.BROWSER); diff --git a/packages/auth/src/platform_browser/load_js.test.ts b/packages/auth/src/platform_browser/load_js.test.ts index 972fb292065..b3ed65eba07 100644 --- a/packages/auth/src/platform_browser/load_js.test.ts +++ b/packages/auth/src/platform_browser/load_js.test.ts @@ -26,6 +26,8 @@ import { } from './load_js'; import { _createError } from '../core/util/assert'; import { AuthErrorCode } from '../core/errors'; +import { TrustedResourceUrl, trustedResourceUrl, unwrapResourceUrl } from 'safevalues'; +import { setScriptSrc } from 'safevalues/dom'; use(sinonChai); @@ -41,10 +43,10 @@ describe('platform-browser/load_js', () => { describe('_loadJS', () => { it('sets the appropriate properties', () => { _setExternalJSProvider({ - loadJS(url: string): Promise { + loadJS(url: TrustedResourceUrl): Promise { return new Promise((resolve, reject) => { const el = document.createElement('script'); - el.setAttribute('src', url); + setScriptSrc(el, url); el.onload = resolve; el.onerror = e => { const error = _createError(AuthErrorCode.INTERNAL_ERROR); @@ -55,20 +57,18 @@ describe('platform-browser/load_js', () => { el.charset = 'UTF-8'; }); }, - gapiScript: 'https://gapiScript', - recaptchaV2Script: 'https://recaptchaV2Script', - recaptchaEnterpriseScript: 'https://recaptchaEnterpriseScript' + gapiScript: trustedResourceUrl`https://gapiScript`, + recaptchaV2Script: trustedResourceUrl`https://recaptchaV2Script`, + recaptchaEnterpriseScript: trustedResourceUrl`https://recaptchaEnterpriseScript` }); const el = document.createElement('script'); sinon.stub(el); // Prevent actually setting the src attribute sinon.stub(document, 'createElement').returns(el); + const testUrl = trustedResourceUrl`http://localhost/url`; // eslint-disable-next-line @typescript-eslint/no-floating-promises - _loadJS('http://localhost/url'); - expect(el.setAttribute).to.have.been.calledWith( - 'src', - 'http://localhost/url' - ); + _loadJS(testUrl); + expect(el.src).to.eq(unwrapResourceUrl(testUrl)); expect(el.type).to.eq('text/javascript'); expect(el.charset).to.eq('UTF-8'); }); diff --git a/packages/auth/src/platform_browser/load_js.ts b/packages/auth/src/platform_browser/load_js.ts index b7eb0ce4690..13fe92a9eda 100644 --- a/packages/auth/src/platform_browser/load_js.ts +++ b/packages/auth/src/platform_browser/load_js.ts @@ -15,40 +15,42 @@ * limitations under the License. */ +import { TrustedResourceUrl, trustedResourceUrl } from 'safevalues'; + interface ExternalJSProvider { - loadJS(url: string): Promise; - recaptchaV2Script: string; - recaptchaEnterpriseScript: string; - gapiScript: string; + loadJS(url: TrustedResourceUrl): Promise; + recaptchaV2Script: TrustedResourceUrl; + recaptchaEnterpriseScript: TrustedResourceUrl; + gapiScript: TrustedResourceUrl; } let externalJSProvider: ExternalJSProvider = { - async loadJS() { + loadJS(url: TrustedResourceUrl): Promise { throw new Error('Unable to load external scripts'); }, - recaptchaV2Script: '', - recaptchaEnterpriseScript: '', - gapiScript: '' + recaptchaV2Script: trustedResourceUrl``, + recaptchaEnterpriseScript: trustedResourceUrl``, + gapiScript: trustedResourceUrl`` }; export function _setExternalJSProvider(p: ExternalJSProvider): void { externalJSProvider = p; } -export function _loadJS(url: string): Promise { +export function _loadJS(url: TrustedResourceUrl): Promise { return externalJSProvider.loadJS(url); } -export function _recaptchaV2ScriptUrl(): string { +export function _recaptchaV2ScriptUrl(): TrustedResourceUrl { return externalJSProvider.recaptchaV2Script; } -export function _recaptchaEnterpriseScriptUrl(): string { +export function _recaptchaEnterpriseScriptUrl(): TrustedResourceUrl { return externalJSProvider.recaptchaEnterpriseScript; } -export function _gapiScriptUrl(): string { +export function _gapiScriptUrl(): TrustedResourceUrl { return externalJSProvider.gapiScript; } diff --git a/packages/auth/src/platform_browser/recaptcha/recaptcha_enterprise_verifier.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_enterprise_verifier.ts index 53796664de6..d7fd3ce797e 100644 --- a/packages/auth/src/platform_browser/recaptcha/recaptcha_enterprise_verifier.ts +++ b/packages/auth/src/platform_browser/recaptcha/recaptcha_enterprise_verifier.ts @@ -25,6 +25,7 @@ import { RecaptchaAuthProvider, EnforcementState } from '../../api'; +import { appendParams, unwrapResourceUrl } from 'safevalues'; import { Auth } from '../../model/public_types'; import { AuthInternal } from '../../model/auth'; @@ -142,8 +143,8 @@ export class RecaptchaEnterpriseVerifier { return; } let url = jsHelpers._recaptchaEnterpriseScriptUrl(); - if (url.length !== 0) { - url += siteKey; + if (unwrapResourceUrl(url).toString().length !== 0) { + url = appendParams(url, new Map([['render', siteKey]])); } jsHelpers ._loadJS(url) diff --git a/packages/auth/src/platform_browser/recaptcha/recaptcha_loader.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_loader.ts index 9e6def3cb88..f0ed2daf9a3 100644 --- a/packages/auth/src/platform_browser/recaptcha/recaptcha_loader.ts +++ b/packages/auth/src/platform_browser/recaptcha/recaptcha_loader.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { querystring } from '@firebase/util'; +import { appendParams } from 'safevalues'; import { AuthErrorCode } from '../../core/errors'; import { _assert, _createError } from '../../core/util/assert'; @@ -90,11 +90,11 @@ export class ReCaptchaLoaderImpl implements ReCaptchaLoader { resolve(recaptcha); }; - const url = `${jsHelpers._recaptchaV2ScriptUrl()}?${querystring({ - onload: _JSLOAD_CALLBACK, - render: 'explicit', - hl - })}`; + const url = appendParams(jsHelpers._recaptchaV2ScriptUrl(), new Map([ + ['onload', _JSLOAD_CALLBACK], + ['render', 'explicit'], + ['hl', hl] + ])); jsHelpers._loadJS(url).catch(() => { clearTimeout(networkTimeout); diff --git a/yarn.lock b/yarn.lock index 213ecd4d2c9..3f09c4f823d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14237,6 +14237,11 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +safevalues@1.2.0: + version "1.2.0" + resolved "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/safevalues/-/safevalues-1.2.0.tgz#f9e646d6ebf31788004ef192d2a7d646c9896bb2" + integrity sha512-zIsuhjYvJCjfsfjoim2ab6gLKFYAnTiDSJGh0cC3T44L/4kNLL90hBG2BzrXPrHA3f8Ms8FSJ1mljKH5dVR1cw== + sauce-connect-launcher@^1.2.7: version "1.3.2" resolved "https://registry.npmjs.org/sauce-connect-launcher/-/sauce-connect-launcher-1.3.2.tgz#dfc675a258550809a8eaf457eb9162b943ddbaf0"