Skip to content
Merged
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
104 changes: 104 additions & 0 deletions packages/worker/client/sentry-browser-filters.node.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test } from 'vitest'
import {
filterBrowserAbortSentryEvent,
filterBrowserInjectedGlobalNoiseSentryEvent,
filterBrowserSentryEvent,
filterFirefoxDomPermissionDeniedSentryEvent,
} from './sentry-browser-filters.ts'
Expand Down Expand Up @@ -110,6 +111,96 @@ test('browser Sentry filters drop AbortError and Firefox Xray noise and keep rea
}),
).not.toBeNull()

// Injected wallet / Firefox-iOS bridge globals only (issue 7648833360).
expect(
filterBrowserInjectedGlobalNoiseSentryEvent({
exception: {
values: [
{
type: 'TypeError',
value:
"undefined is not an object (evaluating 'window.ethereum.selectedAddress = undefined')",
},
],
},
}),
).toBeNull()
expect(
filterBrowserInjectedGlobalNoiseSentryEvent({
exception: {
values: [
{
type: 'TypeError',
value:
"undefined is not an object (evaluating 'window.__firefox__.reader')",
},
],
},
}),
).toBeNull()
expect(
filterBrowserInjectedGlobalNoiseSentryEvent({
exception: {
values: [
{
type: 'TypeError',
value:
"undefined is not an object (evaluating 'window.__firefox__.refresh_youtube_quality_2E41B6CA94114F3CB0CAF4E4DA93D5A8')",
},
],
},
}),
).toBeNull()
expect(
filterBrowserInjectedGlobalNoiseSentryEvent({
exception: {
values: [
{
type: 'ReferenceError',
value: "Can't find variable: __firefox__",
},
],
},
}),
).toBeNull()
expect(
filterBrowserInjectedGlobalNoiseSentryEvent(
{
exception: {
values: [{ type: 'Error', value: 'something else' }],
},
},
new TypeError(
"undefined is not an object (evaluating 'window.ethereum.selectedAddress = undefined')",
),
),
).toBeNull()
expect(
filterBrowserInjectedGlobalNoiseSentryEvent({
exception: {
values: [
{
type: 'TypeError',
value:
"undefined is not an object (evaluating 'window.someAppApi.foo')",
},
],
},
}),
).not.toBeNull()
expect(
filterBrowserInjectedGlobalNoiseSentryEvent({
exception: {
values: [
{
type: 'ReferenceError',
value: "Can't find variable: ethereum",
},
],
},
}),
).not.toBeNull()

const realBug = {
exception: {
values: [{ type: 'TypeError', value: 'TypeError: Failed to fetch' }],
Expand Down Expand Up @@ -140,4 +231,17 @@ test('browser Sentry filters drop AbortError and Firefox Xray noise and keep rea
},
}),
).toBeNull()
expect(
filterBrowserSentryEvent({
exception: {
values: [
{
type: 'TypeError',
value:
"undefined is not an object (evaluating 'window.ethereum.selectedAddress = undefined')",
},
],
},
}),
).toBeNull()
})
61 changes: 61 additions & 0 deletions packages/worker/client/sentry-browser-filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,61 @@ export function filterFirefoxDomPermissionDeniedSentryEvent<
return event
}

/**
* Wallet / browser-injected globals that throw from page `global code`, not
* from Kody bundles (no app code references these). Production signatures from
* Brave iOS issue 7648833360 and sibling 7648833403:
* - `undefined is not an object (evaluating 'window.ethereum…')`
* - `undefined is not an object (evaluating 'window.__firefox__…')`
* - `Can't find variable: __firefox__`
*
* Match is intentionally narrow: only these injected-global access patterns.
* Never blanket-drop TypeError / ReferenceError.
*/
export function isBrowserInjectedGlobalNoiseMessage(message: string) {
const withoutTypePrefix = message
.trim()
.replace(/^(?:TypeError|ReferenceError):\s*/i, '')
return (
/^undefined is not an object \(evaluating ['"]window\.(?:ethereum|__firefox__)\./i.test(
withoutTypePrefix,
) ||
/^Can'?t find variable: __firefox__\.?$/i.test(withoutTypePrefix) ||
/^Cannot find variable: __firefox__\.?$/i.test(withoutTypePrefix) ||
/^__firefox__ is not defined\.?$/i.test(withoutTypePrefix)
)
}

export function isBrowserInjectedGlobalNoiseError(error: unknown) {
if (typeof error === 'string') {
return isBrowserInjectedGlobalNoiseMessage(error)
}
if (typeof error !== 'object' || error === null) return false
if (!('message' in error) || typeof error.message !== 'string') return false
return isBrowserInjectedGlobalNoiseMessage(error.message)
}

export function isBrowserInjectedGlobalNoiseSentryEvent(
event: SentryErrorEventLike,
originalException?: unknown,
) {
if (isBrowserInjectedGlobalNoiseError(originalException)) return true
return sentryEventMessages(event).some(
(message) =>
typeof message === 'string' &&
isBrowserInjectedGlobalNoiseMessage(message),
)
}

export function filterBrowserInjectedGlobalNoiseSentryEvent<
T extends SentryErrorEventLike,
>(event: T, originalException?: unknown): T | null {
if (isBrowserInjectedGlobalNoiseSentryEvent(event, originalException)) {
return null
}
return event
}

/** Combined browser beforeSend / capture gate used by the client SDK. */
export function filterBrowserSentryEvent<T extends SentryErrorEventLike>(
event: T,
Expand All @@ -140,5 +195,11 @@ export function filterBrowserSentryEvent<T extends SentryErrorEventLike>(
) {
return null
}
if (
filterBrowserInjectedGlobalNoiseSentryEvent(event, originalException) ===
null
) {
return null
}
return event
}
7 changes: 6 additions & 1 deletion packages/worker/client/sentry-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
isBrowserAbortError,
isBrowserInjectedGlobalNoiseError,
isFirefoxDomPermissionDeniedError,
} from '#client/sentry-browser-filters.ts'
import {
Expand Down Expand Up @@ -44,7 +45,11 @@ let onWindowError: ((event: ErrorEvent) => void) | null = null
let onUnhandledRejection: ((event: PromiseRejectionEvent) => void) | null = null

function shouldIgnoreBufferedError(error: unknown) {
return isBrowserAbortError(error) || isFirefoxDomPermissionDeniedError(error)
return (
isBrowserAbortError(error) ||
isFirefoxDomPermissionDeniedError(error) ||
isBrowserInjectedGlobalNoiseError(error)
)
}

function enqueueBufferedError(error: unknown) {
Expand Down
5 changes: 3 additions & 2 deletions packages/worker/client/sentry-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ export function initBrowserSentry(config: SentryClientConfig) {
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 1.0,
// Drop expected browser noise (AbortError aborts, Firefox DOM
// permission-denied from Replay/hydration on restricted nodes) —
// see filterBrowserSentryEvent / KODY-CLOUDFLARE-23 / issue 7639685398.
// permission-denied from Replay/hydration on restricted nodes, and
// injected wallet/`__firefox__` globals) — see filterBrowserSentryEvent
// / KODY-CLOUDFLARE-23 / issues 7639685398, 7648833360, 7648833403.
beforeSend(event, hint) {
return filterBrowserSentryEvent(event, hint.originalException)
},
Expand Down
Loading