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
69 changes: 69 additions & 0 deletions packages/worker/client/sentry-browser-filters.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
filterFathomRemoveChildNullSentryEvent,
filterFirefoxDomPermissionDeniedSentryEvent,
filterMetaMaskExtensionSentryEvent,
filterOgTypeMetaQuerySelectorContentSentryEvent,
filterTwitterInAppBrowserConfigSentryEvent,
} from './sentry-browser-filters.ts'

Expand Down Expand Up @@ -370,4 +371,72 @@ test('browser Sentry filters drop AbortError and Firefox Xray noise and keep rea
},
}),
).toBeNull()

// Injected unguarded og:type meta probe (issue 7660258027 /
// KODY-CLOUDFLARE-46). Requires both the Safari evaluating wording and a
// `global code` frame — same message from app bundles stays.
expect(
filterOgTypeMetaQuerySelectorContentSentryEvent({
exception: {
values: [
{
type: 'TypeError',
value:
"null is not an object (evaluating 'document.querySelector(\"meta[property='og:type']\").content')",
stacktrace: {
frames: [
{
function: 'global code',
filename: '/guides/what-is-kody',
abs_path: 'https://heykody.app/guides/what-is-kody',
},
],
},
},
],
},
}),
).toBeNull()
expect(
filterOgTypeMetaQuerySelectorContentSentryEvent({
exception: {
values: [
{
type: 'TypeError',
value:
"null is not an object (evaluating 'document.querySelector(\"meta[property='og:type']\").content')",
stacktrace: {
frames: [
{
function: 'applyDocumentHead',
filename: 'https://heykody.app/assets/document-head.js',
},
],
},
},
],
},
}),
).not.toBeNull()
expect(
filterBrowserSentryEvent({
exception: {
values: [
{
type: 'TypeError',
value:
"TypeError: null is not an object (evaluating 'document.querySelector(\"meta[property='og:type']\").content')",
stacktrace: {
frames: [
{
function: 'global code',
absPath: 'https://heykody.app/guides/what-is-kody',
},
],
},
},
],
},
}),
).toBeNull()
})
64 changes: 64 additions & 0 deletions packages/worker/client/sentry-browser-filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,62 @@ export function filterTwitterInAppBrowserConfigSentryEvent<
return event
}

/**
* Injected page scripts (Mobile Safari / in-app browsers / content scripts)
* sometimes probe Open Graph tags with an unguarded
* `document.querySelector("meta[property='og:type']").content`. When the page
* has no managed OG tags (guides and many authenticated routes), WebKit throws
* and attributes the frame to `global code` on the document URL — never a Kody
* bundle. Signature from production issue 7660258027 / KODY-CLOUDFLARE-46.
*
* Kody only writes `og:type` in document-head / SSR; it never reads it this
* way. Match is intentionally narrow: Safari "null is not an object
* (evaluating 'document.querySelector(…og:type…).content')" wording AND a
* `global code` stack function. Never blanket-drop bare `.content` TypeErrors
* from app code.
*/
const ogTypeMetaQuerySelectorContentMessage =
/^(?:TypeError:\s*)?null is not an object \(evaluating ['"]document\.querySelector\([^)]*meta\[property=['"]og:type['"]\][^)]*\)\.content['"]\)$/i

export function isOgTypeMetaQuerySelectorContentMessage(message: string) {
return ogTypeMetaQuerySelectorContentMessage.test(message.trim())
}

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

export function isOgTypeMetaQuerySelectorContentSentryEvent(
event: SentryErrorEventLike,
originalException?: unknown,
) {
const hasOgTypeMessage =
isOgTypeMetaQuerySelectorContentError(originalException) ||
sentryEventMessages(event).some(
(message) =>
typeof message === 'string' &&
isOgTypeMetaQuerySelectorContentMessage(message),
)
if (!hasOgTypeMessage) return false
return sentryEventStackFrameFunctions(event).some(
(name) => name === 'global code',
)
}

export function filterOgTypeMetaQuerySelectorContentSentryEvent<
T extends SentryErrorEventLike,
>(event: T, originalException?: unknown): T | null {
if (isOgTypeMetaQuerySelectorContentSentryEvent(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 Down Expand Up @@ -482,5 +538,13 @@ export function filterBrowserSentryEvent<T extends SentryErrorEventLike>(
) {
return null
}
if (
filterOgTypeMetaQuerySelectorContentSentryEvent(
event,
originalException,
) === null
) {
return null
}
return event
}
12 changes: 7 additions & 5 deletions packages/worker/client/sentry-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ export function initBrowserSentry(config: SentryClientConfig) {
// permission-denied from Replay/hydration on restricted nodes,
// injected wallet/`__firefox__` globals, Fathom beacon
// removeChild-on-null, Chrome extension IPC "Object Not Found
// Matching Id…", MetaMask inpage connect failures, and Twitter/X
// in-app browser chrome `CONFIG` ReferenceErrors) — see
// filterBrowserSentryEvent / KODY-CLOUDFLARE-23 /
// Matching Id…", MetaMask inpage connect failures, Twitter/X
// in-app browser chrome `CONFIG` ReferenceErrors, and injected
// unguarded `meta[property='og:type']` probes from `global code`) —
// see filterBrowserSentryEvent / KODY-CLOUDFLARE-23 /
// KODY-CLOUDFLARE-3Q / KODY-CLOUDFLARE-3S / KODY-CLOUDFLARE-3X /
// KODY-CLOUDFLARE-43 / issues 7639685398, 7648833360, 7648833403,
// 7653117289, 7655189301, 7658961865, 7659616372.
// KODY-CLOUDFLARE-43 / KODY-CLOUDFLARE-46 / issues 7639685398,
// 7648833360, 7648833403, 7653117289, 7655189301, 7658961865,
// 7659616372, 7660258027.
beforeSend(event, hint) {
return filterBrowserSentryEvent(event, hint.originalException)
},
Expand Down
Loading