Skip to content

Commit 8dca102

Browse files
authored
fix(browser): Improve browser extension error message check (#12146)
Make our check for when we abort and log an error due to `Sentry.init` being used in a browser extension a bit more fine-gained. In particular, we now do not abort the SDK initialization if we detect that the SDK is running in a browser-extension dedicated window (e.g. a URL starting with `chrome-extension://`).
1 parent 83c255a commit 8dca102

File tree

2 files changed

+53
-18
lines changed

2 files changed

+53
-18
lines changed

packages/browser/src/sdk.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,32 @@ function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOptions {
6060
return { ...defaultOptions, ...optionsArg };
6161
}
6262

63+
type ExtensionProperties = {
64+
chrome?: Runtime;
65+
browser?: Runtime;
66+
};
67+
type Runtime = {
68+
runtime?: {
69+
id?: string;
70+
};
71+
};
72+
6373
function shouldShowBrowserExtensionError(): boolean {
64-
const windowWithMaybeChrome = WINDOW as typeof WINDOW & { chrome?: { runtime?: { id?: string } } };
65-
const isInsideChromeExtension =
66-
windowWithMaybeChrome &&
67-
windowWithMaybeChrome.chrome &&
68-
windowWithMaybeChrome.chrome.runtime &&
69-
windowWithMaybeChrome.chrome.runtime.id;
70-
71-
const windowWithMaybeBrowser = WINDOW as typeof WINDOW & { browser?: { runtime?: { id?: string } } };
72-
const isInsideBrowserExtension =
73-
windowWithMaybeBrowser &&
74-
windowWithMaybeBrowser.browser &&
75-
windowWithMaybeBrowser.browser.runtime &&
76-
windowWithMaybeBrowser.browser.runtime.id;
77-
78-
return !!isInsideBrowserExtension || !!isInsideChromeExtension;
74+
const windowWithMaybeExtension = WINDOW as typeof WINDOW & ExtensionProperties;
75+
76+
const extensionKey = windowWithMaybeExtension.chrome ? 'chrome' : 'browser';
77+
const extensionObject = windowWithMaybeExtension[extensionKey];
78+
79+
const runtimeId = extensionObject && extensionObject.runtime && extensionObject.runtime.id;
80+
const href = (WINDOW.location && WINDOW.location.href) || '';
81+
82+
const extensionProtocols = ['chrome-extension:', 'moz-extension:', 'ms-browser-extension:'];
83+
84+
// Running the SDK in a dedicated extension page and calling Sentry.init is fine; no risk of data leakage
85+
const isDedicatedExtensionPage =
86+
!!runtimeId && WINDOW === WINDOW.top && extensionProtocols.some(protocol => href.startsWith(`${protocol}//`));
87+
88+
return !!runtimeId && !isDedicatedExtensionPage;
7989
}
8090

8191
/**

packages/browser/test/unit/sdk.test.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,16 @@ describe('init', () => {
135135
new MockIntegration('MockIntegration 0.2'),
136136
];
137137

138+
const originalLocation = WINDOW.location || {};
139+
138140
const options = getDefaultBrowserOptions({ dsn: PUBLIC_DSN, defaultIntegrations: DEFAULT_INTEGRATIONS });
139141

140142
afterEach(() => {
141143
Object.defineProperty(WINDOW, 'chrome', { value: undefined, writable: true });
142144
Object.defineProperty(WINDOW, 'browser', { value: undefined, writable: true });
143145
});
144146

145-
it('should log a browser extension error if executed inside a Chrome extension', () => {
147+
it('logs a browser extension error if executed inside a Chrome extension', () => {
146148
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
147149

148150
Object.defineProperty(WINDOW, 'chrome', {
@@ -160,7 +162,7 @@ describe('init', () => {
160162
consoleErrorSpy.mockRestore();
161163
});
162164

163-
it('should log a browser extension error if executed inside a Firefox/Safari extension', () => {
165+
it('logs a browser extension error if executed inside a Firefox/Safari extension', () => {
164166
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
165167

166168
Object.defineProperty(WINDOW, 'browser', { value: { runtime: { id: 'mock-extension-id' } }, writable: true });
@@ -175,7 +177,30 @@ describe('init', () => {
175177
consoleErrorSpy.mockRestore();
176178
});
177179

178-
it('should not log a browser extension error if executed inside regular browser environment', () => {
180+
it.each(['chrome-extension', 'moz-extension', 'ms-browser-extension'])(
181+
"doesn't log a browser extension error if executed inside an extension running in a dedicated page (%s)",
182+
extensionProtocol => {
183+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
184+
185+
// @ts-expect-error - this is a hack to simulate a dedicated page in a browser extension
186+
delete WINDOW.location;
187+
// @ts-expect-error - this is a hack to simulate a dedicated page in a browser extension
188+
WINDOW.location = {
189+
href: `${extensionProtocol}://mock-extension-id/dedicated-page.html`,
190+
};
191+
192+
Object.defineProperty(WINDOW, 'browser', { value: { runtime: { id: 'mock-extension-id' } }, writable: true });
193+
194+
init(options);
195+
196+
expect(consoleErrorSpy).toBeCalledTimes(0);
197+
198+
consoleErrorSpy.mockRestore();
199+
WINDOW.location = originalLocation;
200+
},
201+
);
202+
203+
it("doesn't log a browser extension error if executed inside regular browser environment", () => {
179204
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
180205

181206
init(options);

0 commit comments

Comments
 (0)