Skip to content

[DO NOT MERGE] feat(core): add opt-in assertion instrumentation - #6592

Draft
antonis wants to merge 11 commits into
mainfrom
feat/loud-invariants
Draft

[DO NOT MERGE] feat(core): add opt-in assertion instrumentation#6592
antonis wants to merge 11 commits into
mainfrom
feat/loud-invariants

fix(core): harden assertion transform against Error shadowing, __prot…

162eefb
Select commit
Loading
Failed to load commit list.
@sentry/warden / warden completed Aug 19, 2026

8 issues

Medium

Unvalidated `rethrowPragmas` option can crash Babel on malformed config - `packages/core/src/js/tools/sentryAssertionBabelPlugin.ts:567-568`

Add a defensive Array.isArray(options.rethrowPragmas) check before calling .includes() — a non-array value (e.g., a string passed via Metro config) causes a build-time TypeError.

Module-level reportedSites set is never reset between tests - `packages/core/test/assertion.test.ts:24-27`

beforeEach clears mocks but leaves the module-level reportedSites set intact, so tests that pass siteId pollute shared state and can flake in watch mode.

Unbounded values iteration in flattenValues allows DoS via large objects - `packages/core/src/js/assertion.ts:225-237`

flattenValues iterates over every key in values without a count cap and calls JSON.stringify(values) on the full object before truncation, allowing a large input object to exhaust CPU and memory.

collectValueIdentifiers bypasses TDZ guard when AST nodes lack location info - `packages/core/src/js/tools/sentryAssertionBabelPlugin.ts:327-334`

When the assertion condition node has no start (e.g., synthesized by an earlier Babel plugin), the TDZ guard in collectValueIdentifiers is skipped entirely and textually-after let/const bindings are captured. Reading them on the report path crashes with a ReferenceError.

ensureErrorBinding alias crashes when globalThis is shadowed by const/let - `packages/core/src/js/tools/sentryAssertionBabelPlugin.ts:483-490`

The hoisted var _Error initializer can throw a ReferenceError if the module contains a const globalThis or let globalThis declaration, because typeof on a TDZ binding throws instead of returning 'undefined'.

Low

Overly broad node_modules path detection may skip first-party files - `packages/core/src/js/tools/sentryAssertionBabelPlugin.ts:359`

filename.includes('node_modules') matches any path containing that substring, so a first-party file whose path happens to contain node_modules (e.g., node_modules_helper.js or a node_modules_workspace/ directory) can be incorrectly treated as a dependency and skipped when includeNodeModules is false or absent. This contradicts the stated intent that first-party code is instrumented by default.

Error alias fallback can throw ReferenceError in TDZ on pre-globalThis runtimes - `packages/core/src/js/tools/sentryAssertionBabelPlugin.ts:485`

The hoisted Error alias falls back to bare Error when globalThis is absent. On ES6–ES2019 runtimes that support let/const TDZ but lack globalThis, a module-level let Error causes the fallback to throw ReferenceError instead of creating the alias.

Module-level `reportedSites` set in `captureAssertionViolation` has no size bound, risking memory exhaustion with dynamic `siteId` values - `packages/core/test/integrations/reactnativeerrorhandlers.test.ts:211`

captureAssertionViolation uses a module-level Set (reportedSites) that only grows and is never cleared or capped. While the Babel transform generates static siteId values (filename:line:column) that are bounded by the number of assertion call sites, the function is also a public API exported from @sentry/react-native. A manual caller passing dynamically-generated siteId strings with once: true (the default) can cause the set to grow without bound over the lifetime of the JS runtime, leading to gradual memory exhaustion.

Also found at:

  • packages/core/test/tools/sentryAssertionBabelPlugin.test.ts:104
4 skills analyzed
Skill Findings Duration Cost
security-review 0 3m 46s $1.63
code-review 4 31m 24s $3.98
find-bugs 4 37m 12s $9.83
gha-security-review 0 33m 20s $0.10

⏱ 105m 42s · 18.1M in / 1.0M out · $15.54