From b4e0495e2ee5685524a6da8ba93a8ff73d4ca326 Mon Sep 17 00:00:00 2001 From: "Stephanie Elliott (via MelvinBot)" Date: Thu, 12 Mar 2026 02:41:27 +0000 Subject: [PATCH 1/2] Fix iframe communication failure under strict-origin-isolation When Chrome's strict-origin-isolation flag is enabled, three operations in the expensifyIframeify plugin throw SecurityError: 1. document.domain setter (blocked under origin isolation) 2. window.parent.location access (cross-origin property access denied) 3. contentWindow.location access (cross-origin property access denied) This causes the deposit-only bank account flow (and any other iframe communication between www.expensify.com and secure.expensify.com) to get stuck on a loading screen indefinitely. Changes: - Wrap document.domain setter in try/catch so it degrades gracefully - Add try/catch around window.parent.location access with fallback to inferring the parent origin from the allowedCommunications whitelist - Add try/catch around contentWindow.location access with fallback to extracting the origin from the iframe's src attribute Co-authored-by: Stephanie Elliott --- lib/jquery.expensifyIframify.js | 54 ++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/lib/jquery.expensifyIframify.js b/lib/jquery.expensifyIframify.js index c8978960..b5962883 100644 --- a/lib/jquery.expensifyIframify.js +++ b/lib/jquery.expensifyIframify.js @@ -164,16 +164,48 @@ export default { // Sending message from the iFrame to the parent // Only post a message if this is in an iFrame if (!postToIframe && window.parent !== window) { - targetOrigin = `${window.parent.location.protocol}//${window.parent.location.hostname}`; - log('posting message to parent', targetOrigin, msg); - window.parent.postMessage(msg, targetOrigin); + try { + targetOrigin = `${window.parent.location.protocol}//${window.parent.location.hostname}`; + } catch (e) { + // window.parent.location throws SecurityError under strict-origin-isolation. + // Fall back to inferring the parent origin from allowedCommunications. + const allowedTargets = allowedCommunications[settings.origin]; + if (allowedTargets) { + for (let i = 0; i < allowedTargets.length; i += 1) { + if (allowedTargets[i] !== settings.origin) { + targetOrigin = allowedTargets[i]; + break; + } + } + } + } + if (targetOrigin) { + log('posting message to parent', targetOrigin, msg); + window.parent.postMessage(msg, targetOrigin); + } } // Sending message from the parent to the iFrame if (postToIframe && iframeElement[0].contentWindow) { - targetOrigin = `${iframeElement[0].contentWindow.location.protocol}//${iframeElement[0].contentWindow.location.hostname}`; - log('posting message to iframe', targetOrigin, msg); - iframeElement[0].contentWindow.postMessage(msg, targetOrigin); + try { + targetOrigin = `${iframeElement[0].contentWindow.location.protocol}//${iframeElement[0].contentWindow.location.hostname}`; + } catch (e) { + // contentWindow.location throws SecurityError under strict-origin-isolation. + // Fall back to extracting the origin from the iframe's src attribute. + try { + const src = iframeElement.attr('src') || iframeElement[0].src; + if (src) { + const url = new URL(src, window.location.href); + targetOrigin = `${url.protocol}//${url.hostname}`; + } + } catch (urlError) { + log('could not determine iframe origin', urlError); + } + } + if (targetOrigin) { + log('posting message to iframe', targetOrigin, msg); + iframeElement[0].contentWindow.postMessage(msg, targetOrigin); + } } } @@ -387,8 +419,14 @@ export default { const subdomain = domainArray.shift(); const domainWithoutSubdomain = domainArray.join('.'); - // There are some browsers that don't support document.domain so we have to manually create it - document.domain = domainWithoutSubdomain; + // Attempt to set document.domain for same-site iframe communication. + // This is blocked when Chrome's strict-origin-isolation flag is enabled + // or when the deprecated document.domain setter is fully removed. + try { + document.domain = domainWithoutSubdomain; + } catch (e) { + // Silently ignore - cross-origin communication will rely on postMessage + } iframeElement = this; // eslint-disable-line consistent-this if (!wasInitalized) { From 1b8b811a7a600b73b0404b52f61a621909bbc5dd Mon Sep 17 00:00:00 2001 From: "Stephanie Elliott (via MelvinBot)" Date: Thu, 12 Mar 2026 03:54:20 +0000 Subject: [PATCH 2/2] Simplify strict-origin-isolation fallback to use '*' targetOrigin Replace the allowedCommunications lookup and iframe src parsing fallbacks with a simple '*' targetOrigin. This is safe because handleWindowMessage independently validates event.origin on the receiving side. Co-authored-by: Stephanie Elliott --- lib/jquery.expensifyIframify.js | 36 ++++++++------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/lib/jquery.expensifyIframify.js b/lib/jquery.expensifyIframify.js index b5962883..e55ddcdb 100644 --- a/lib/jquery.expensifyIframify.js +++ b/lib/jquery.expensifyIframify.js @@ -168,21 +168,11 @@ export default { targetOrigin = `${window.parent.location.protocol}//${window.parent.location.hostname}`; } catch (e) { // window.parent.location throws SecurityError under strict-origin-isolation. - // Fall back to inferring the parent origin from allowedCommunications. - const allowedTargets = allowedCommunications[settings.origin]; - if (allowedTargets) { - for (let i = 0; i < allowedTargets.length; i += 1) { - if (allowedTargets[i] !== settings.origin) { - targetOrigin = allowedTargets[i]; - break; - } - } - } - } - if (targetOrigin) { - log('posting message to parent', targetOrigin, msg); - window.parent.postMessage(msg, targetOrigin); + // Using '*' is safe because handleWindowMessage validates event.origin independently. + targetOrigin = '*'; } + log('posting message to parent', targetOrigin, msg); + window.parent.postMessage(msg, targetOrigin); } // Sending message from the parent to the iFrame @@ -191,21 +181,11 @@ export default { targetOrigin = `${iframeElement[0].contentWindow.location.protocol}//${iframeElement[0].contentWindow.location.hostname}`; } catch (e) { // contentWindow.location throws SecurityError under strict-origin-isolation. - // Fall back to extracting the origin from the iframe's src attribute. - try { - const src = iframeElement.attr('src') || iframeElement[0].src; - if (src) { - const url = new URL(src, window.location.href); - targetOrigin = `${url.protocol}//${url.hostname}`; - } - } catch (urlError) { - log('could not determine iframe origin', urlError); - } - } - if (targetOrigin) { - log('posting message to iframe', targetOrigin, msg); - iframeElement[0].contentWindow.postMessage(msg, targetOrigin); + // Using '*' is safe because handleWindowMessage validates event.origin independently. + targetOrigin = '*'; } + log('posting message to iframe', targetOrigin, msg); + iframeElement[0].contentWindow.postMessage(msg, targetOrigin); } }