Skip to content

[Bug] Pinning domain sometimes throws an uncaught cross-origin SecurityError #133

Closed

Description

@lindsey-cstk

When trying to pin the domain for site that has embedded cross-origin iframes, the extension continuously spins and says "Searching for Lytics JavaScript SDK" even though the tag has been installed on the site.

Lytics Developer Tools Extension Active
lyticsdev ::: tag :: {msg: 'initialized'}
lyticsdev ::: tag :: found jstag                ← tag detected fine
...
Uncaught SecurityError: Failed to read a named property 'toJSON' from 'Window':
Blocked a frame with origin "https://***************.com" from accessing a cross-origin frame.
    at o (index.js:12)
    at h.dispatchEvent (index.js:12)
    at entityReady (latest.min.js:2)
    at getConfig (index.js:1)
    at jstagReady (index.js:1)
    at init (index.js:1)
...
lyticsdev ::: tag :: invalid action: undefined  ← then loops forever

What's happening
After found jstag, the extension reads config on entityReady and serializes its payload. JSON.stringify walks into a cross-origin iframe's Window, the browser blocks the access, and it throws an uncaught SecurityError mid-handshake. The connect never completes, so the panel spins.

The window reference is introduced inside the extension's own serializer in src/pages/tagLink/tagLink.tsx (dispatchEvent → safeStringify, lines 103–124). That safeStringify guards circular references (via a WeakSet) but has no protection against a value whose property access throws — and JSON.stringify(jstag.config) / JSON.stringify(entity) walks a graph that transitively reaches a cross-origin frame's Window.

Fix (extension side)
A JSON.stringify replacer can't fix this: JSON.stringify reads .toJSON on each value before the replacer runs, so the cross-origin access throws first (verified). The fix must keep the Window out of the call entirely:

  1. Wrap safeStringify/dispatchEvent in try/catch so a serialization failure can't abort the handshake (stops the infinite spin).
  2. Serialize plain data, not live tag objects... either project the explicit fields the panel needs, or deep-clone with a per-property try/catch so cross-origin reads are caught at the read step:
function safeClone(value, seen = new WeakSet()) {
  if (value === null || typeof value !== 'object') return value;
  try { if (value === value.window || value.self === value) return undefined; } catch { return undefined; }
  if (seen.has(value)) return undefined;
  seen.add(value);
  if (Array.isArray(value)) return value.map(v => safeClone(v, seen));
  const out = {};
  for (const k in value) {
    try { const v = value[k]; if (typeof v !== 'function') out[k] = safeClone(v, seen); }
    catch { /* skip unreadable (cross-origin) property */ }
  }
  return out;
}

Activity

  1. github-actions commented on Jun 18, 2026

    @github-actions

    Thank you for your contribution. We will check and reply to you as soon as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions