Repository navigation
fix(replay): rebuild canvas args only from known constructors, behind a config option - #39
Merged
Merged
Conversation
Match a serialized canvas arg's `rr_type` against the constructors the recorder emits (`record/observers/canvas/serialize-args.ts`) instead of resolving the name off `window`: the nine typed arrays, `DataView`, `ImageData`, `ArrayBuffer` (normally serialized as base64, but reachable in `args` form from older clients and nested inside `DataView` args), and `Array` (the nested-arg wrapper older clients emit, covered by the existing `preloadAllImages` tests). Unknown types deserialize to `null` and log a warning, so a missing entry is visible in the replayer console rather than a silently blank canvas. `null` rather than a throw keeps one odd arg from taking down preload for an entire session; the existing `warnCanvasMutationFailed` handler covers the downstream failure. `ImageBitmap`, `HTMLImageElement`, `Blob` and WebGL variable references are unaffected -- those branches never reach the constructor path. No recorder changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The allowlist from the previous commit now applies only when the new
`enforceCanvasArgAllowlist` player config option is set, so an embedding app
can roll it out — and turn it back off if a recording shape turns up that the
list doesn't cover — without shipping a new bundle. Defaults to false, which
leaves current behavior unchanged.
Threaded from `playerConfig` through the canvas mutation dispatcher into
`deserializeArg`, covering both the mutation path
(`replay/canvas/{index,2d,webgl}.ts`) and the eager preload path
(`deserializeAndPreloadCanvasEvents`). Also added to
`SERIALIZABLE_CONFIG_KEYS`, so the option survives the postMessage boundary
into the embedded replayer host; without that entry it would be silently
dropped for origin-isolated replay.
Recording is untouched — this is a replay-side option only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mayberryzane
marked this pull request as ready for review
August 18, 2026 20:16
abelonogov-ld
approved these changes
Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
deserializeArgrebuilt a serialized canvas arg by looking itsrr_typeup onwindowand calling it as a constructor. This adds an explicit set of the constructors the recorder actually emits (seerecord/observers/canvas/serialize-args.tsfor the matching serialization):DataView,ImageDataArrayBuffer— normally serialized as base64, but reachable inargsform from older clients and nested insideDataViewargsArray— the nested-arg wrapper older clients emit (covered by the existingpreloadAllImagestests)Anything else deserializes to
nulland logs[replayer] refusing to construct canvas arg of unknown type: <name>, so a missing entry shows up in the replayer console instead of a silently blank canvas.nullrather than a throw is deliberate:deserializeArgruns insidepreloadAllImagesand inside an un-caughtPromise.allinreplay/canvas/2d.ts, so one odd arg shouldn't take down preload for an entire session. The existingwarnCanvasMutationFailedhandler already covers the downstream canvas call failing.Opt-in
The second commit puts this behind a new
enforceCanvasArgAllowlistplayer config option, defaulting tofalse— current behavior is unchanged unless an embedding app opts in, so it can be rolled out and rolled back without shipping a new bundle.It's threaded from
playerConfigthrough the canvas mutation dispatcher intodeserializeArg, covering both the mutation path (replay/canvas/{index,2d,webgl}.ts) and the eager preload path (deserializeAndPreloadCanvasEvents) — the latter matters because preload caches its deserialized args intocanvasEventMap, which the mutation path then reuses.It's also in
SERIALIZABLE_CONFIG_KEYS, so the option survives the postMessage boundary into the embedded replayer host. Without that entrypickSerializableConfigwould silently drop it and origin-isolated replay would never see it.Replay-side only — nothing in
record/changes.Not affected
ImageBitmap,HTMLImageElement,Bloband the WebGL variable references are handled by therr_type === 'ImageBitmap',src,dataandindexbranches, none of which reach the constructor path. There are tests asserting those still work with enforcement on.Tests
packages/rrweb/test/replay/deserialize-args.test.ts— 33 tests, up from 13:ImageData,DataViewover a base64ArrayBuffer, and theArraywrapper all round-trip; thesrcanddatabranches are untouchednull, including nested in an arg list, and a string argument to an unknown type is never evaluatedtest/replay/embedded.test.ts—pickSerializableConfigkeeps the new key.The negative tests were confirmed failing against the unpatched deserializer before the fix went in, and the whole thing was exercised against the built UMD bundle in Chrome 115 with both flag states.
Verified locally on macOS:
vitest run test/replay test/replayer.test.ts— 140 passed, 1 skipped, incl. the webgl replay, embedded-replayer e2e, and painted-canvas-in-iframe teststsc -noEmitclean;eslint packages/rrweb/srcadds no new warnings;prettier --checkcleanWorth noting: the existing
preloadAllImagessuite is what surfaced theArraywrapper, which an allowlist derived fromserialize-args.tsalone would have missed.🤖 Generated with Claude Code
Note
Overview
Canvas replay deserialization no longer treats every serialized
rr_typeas awindowconstructor.deserializeArgnow supports an optional allowlist of recorder-emitted types (typed arrays,DataView,ImageData,ArrayBuffer,Array); when enforcement is on, unknown types returnnulland log a replayer warning instead of instantiating arbitrary globals.The behavior is gated by a new
enforceCanvasArgAllowlistplayer config (defaultfalse). The flag is passed through 2D/WebGL canvas mutation paths, eagerdeserializeAndPreloadCanvasEventspreload (socanvasEventMapstays consistent), andSERIALIZABLE_CONFIG_KEYSfor embedded replay overpostMessage.Tests cover allowlisted round-trips, rejection of types like
Function/Object(including nested args and non-evaluated string payloads), and unchanged behavior when enforcement is off.Reviewed by Cursor Bugbot for commit 56fce23. Bugbot is set up for automated code reviews on this repo. Configure here.