Skip to content

Sentry: SecurityError — removeEventListener from cross-origin Window #6791

@n-lark

Description

@n-lark

Sentry: SecurityError — removeEventListener from cross-origin Window

Sentry issue: https://flowfuse.sentry.io/issues/7303634221/

The Error

SecurityError: Failed to read a named property 'removeEventListener' from 'Window':
An attempt was made to break through the security policy of the user agent.

Observed route: Triggered when navigating away from /instance/:id/editor/*

Sentry breadcrumb example:

{
  "from": "/instance/9e930e58-c9b2-478a-a630-8aa27cc72a11/editor/devices",
  "to":   "/instance/9e930e58-c9b2-478a-a630-8aa27cc72a11/overview"
}

What the Error Means

A SecurityError: Failed to read a named property 'X' from 'Window' is thrown when code attempts to access a property on a cross-origin Window object — in this case the Node-RED editor iframe's contentWindow, which loads from instance.url (a different origin from the FlowFuse app). The browser's Same-Origin Policy blocks most property access on cross-origin windows, with the exception of a small allowlist (postMessage, close, focus, etc.).


Root Cause — PostHog Session Recording

PostHog's session recorder (@posthog/rrweb-record) is the likely source of this error. The Sentry stack trace shows the crash originating inside PostHog's rrweb-based recorder:

Crashed in non-app:
  @posthog/rrweb-record@0.0.45 — rrweb-record.js (isAttachIframe: true)
  LazyLoadedSessionRecording.stop
  sentryWrapped (@sentry/browser)

PostHog uses rrweb for DOM recording and by default attempts to attach to iframes — including cross-origin ones — to capture their content. When a navigation causes the editor to unmount, LazyLoadedSessionRecording.stop() tries to detach event listeners from the Node-RED iframe's contentWindow, which the browser blocks with a SecurityError because the iframe is cross-origin.

The isAttachIframe: true flag confirms PostHog had successfully attached to the iframe and was now failing to detach.

Supporting evidence from the Sentry breadcrumbs

5:37:30.491  Sentry Transaction  — user arrives at /editor/devices
5:37:32.051  Navigation          — /editor/devices → /overview
5:37:32.121  SecurityError       — 70ms after navigation starts

The ~70ms gap is consistent with synchronous teardown by the recording SDK during route transition.


The Fix

Clear the iframe src to about:blank in beforeUnmount, before PostHog's rrweb recorder attempts to detach its listeners. The fix applies to both editor wrapper components — one for hosted instances, one for remote/device instances — since both render a cross-origin iframe with the same name.

Files:

  • frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue
  • frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue
beforeUnmount () {
    // Clear the iframe src before unmount so PostHog's rrweb recorder can safely
    // detach its event listeners. Without this, rrweb throws a SecurityError when
    // calling contentWindow.removeEventListener on the cross-origin Node-RED iframe.
    if (this.$refs.iframe) {
        this.$refs.iframe.src = 'about:blank'
    }
},

about:blank is same-origin with any page, so by the time rrweb calls contentWindow.removeEventListener during teardown the iframe is no longer cross-origin and the browser allows the call. PostHog continues to record the iframe normally during the session — only the final frame as the user navigates away shows it going blank, which is imperceptible in practice.


Alternatives Considered

Option A — recordCrossOriginIframes: false in PostHog init

Setting session_recording: { recordCrossOriginIframes: false } in the PostHog init config (in forge/routes/ui/index.js) would prevent rrweb from ever attaching to the iframe, eliminating the SecurityError at the source. However, this was rejected because PostHog is currently successfully recording the Node-RED iframe and disabling cross-origin recording would silently drop that coverage. The beforeUnmount approach fixes the teardown issue without sacrificing any recording.

Option B — block: ['iframe'] in Sentry Replay

Configuring Sentry's Replay integration to block iframes (new Replay({ block: ['iframe'] })) would prevent Sentry Replay from attempting to access the iframe. However, Sentry Replay is a secondary suspect — the stack trace clearly shows PostHog's rrweb as the source. Changing the Sentry config would not fix the underlying PostHog teardown issue, just change which tool surfaces the error.

Option C — Upgrade posthog-js

The stack trace references @posthog/rrweb-record@0.0.45, which is a known buggy version (posthog-js#2533). However, the PostHog frontend script is loaded from the PostHog CDN (api_host + "/static/array.js") — it is not an npm dependency in this project. There is nothing to upgrade on our end; the version served is entirely controlled by PostHog and their issue remains open/unresolved.


Why This Error is Hard to Reproduce

Locally

The error requires the Node-RED editor iframe to be loaded from a different origin than the FlowFuse app. In local development this condition is typically not met — either there is no live instance running, or the iframe never loads. The error will only appear in staging or production where a real instance is running at a cross-origin URL (e.g. https://my-instance.flowfuse.cloud).

On staging / production

Even with a real instance, the error is only triggered when PostHog's session recorder is actively recording the session. PostHog's recording rate is configured separately from Sentry's, so the exact proportion of affected sessions depends on PostHog's sample rate. The 2 captured Sentry errors represent only a fraction of actual occurrences — Sentry only surfaces the error when both PostHog recording and Sentry error reporting are active simultaneously.

To verify the fix on staging, ensure PostHog session recording is active and navigate from the editor to the instance overview. The SecurityError should no longer appear in Sentry after deploying.


References

Epic/Story

No response

Have you provided an initial effort estimate for this issue?

I have provided an initial effort estimate

Metadata

Metadata

Assignees

Labels

taskA piece of work that isn't necessarily tied to a specific Epic or Story.time:2h

Type

No type

Projects

Status

In Progress

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions