Repository navigation
[Bug] Pinning domain sometimes throws an uncaught cross-origin SecurityError #133
Description
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:
- Wrap safeStringify/dispatchEvent in try/catch so a serialization failure can't abort the handshake (stops the infinite spin).
- 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
github-actions commentedon Jun 18, 2026
Thank you for your contribution. We will check and reply to you as soon as possible.