event.origin check is present in the current springboard-ui build
(anaplan:version 1.0.37391, assets/index-BByDTUW_.js). This page is preserved as the original proof of concept from
13 July 2026. Running it against production today will not produce a toast — that is the fix working, not a broken PoC.postMessage. Left frame (13 July listener) executes the attacker's tool calls; right frame (today's listener) rejects the
identical payload. The fix is the only variable.
This page is served from origin — an origin that is not id1.app.anaplan.com.
It opens a victim's Springboard board designer (top-level navigation, so the victim's SameSite=Lax Anaplan session cookies flow and
the board loads authenticated), then sends cross-window postMessage tool calls to it.
On the build that was live when this was reported, the designer's message listener performed no event.origin check,
so those calls executed in the victim's authenticated session — a toast reading CROSS-ORIGIN-CSRF-PROOF appeared on the victim's board.
assets/index-DFriS-5T.js (the build named in the report; now HTTP 404)vy = async t => {
if (!t.data || !t.data.tool_call_id || t.data.type === Dye) return; // no origin comparison anywhere
const s = t.data.name;
...
const r = e.handlers[s]; // attacker-named handler
if (r) { const a = await r(o); ... } // invoked with attacker data
};
window.addEventListener("message", vy);
Preserved verbatim in an archived copy of the entry bundle downloaded 2026-07-10 00:31 UTC+2
(sha256 d5a86582f56abb5fc1b31e8938e43e4316e40d13aceb124b1e510b0e3837a60e, 4 886 072 bytes), three days before the report was filed.
assets/index-BByDTUW_.jsNy = async t => {
if (t.origin !== window.location.origin || !t.data || !t.data.tool_call_id || t.data.type === cSe) return;
...
};
window.addEventListener("message", Ny);
Verifiable unauthenticated, no test tenant required:
curl -s https://id1.app.anaplan.com/a/springboard-ui/ | grep -o 'assets/index-[^"]*\.js'
curl -s https://id1.app.anaplan.com/a/springboard-ui/assets/index-BByDTUW_.js \
| grep -o 'Ny=async t=>{if(t.origin!==window.location.origin[^;]\{0,80\}'
curl -sI https://id1.app.anaplan.com/a/springboard-ui/assets/index-DFriS-5T.js | head -1 # 404
| Sender origin | Messages delivered to the victim window | Handlers executed |
|---|---|---|
| this page (cross-origin) | 183 — delivery works fine | 0 — rejected at the origin gate |
https://id1.app.anaplan.com (same-origin control) | 1 | 1 — toast rendered |
Delivery was counted with a capture-phase message listener installed in the victim page before application code ran, so
it counts what reached the window regardless of what the app did with it. 183 attacker messages arrived and none were acted on — on the pre-fix
build the same traffic produced the toast.
Expected on the pre-fix build: a CROSS-ORIGIN-CSRF-PROOF toast on the victim's board within a few seconds of it finishing loading.
Expected on the current build: nothing. The messages are sent every 500 ms for ~30 s because the designer needs 5–15 s to mount and register the listener.
In the victim's Anaplan designer tab, open DevTools and paste this. It posts to the top window and to every same-origin iframe, and prints a diagnostic line so a silent no-op tells you why:
(() => {
const msg = { tool_call_id:"c1", name:"show_message", data:{ message:"SAME-ORIGIN-CONTROL" } };
const frames = [...document.querySelectorAll('iframe')]
.map(f => { try { return f.contentWindow } catch(e) { return null } }).filter(Boolean);
[window, ...frames].forEach(w => { try { w.postMessage(msg, location.origin) } catch(e) {} });
console.log('[control] origin:', location.origin,
'| designer mounted in this frame:', /Board designer|Exit designer/.test(document.body.innerText),
'| posted to', 1 + frames.length, 'window(s)');
})()
The toast renders. That proves the front_end_tool bridge and the show_message handler are still present and still
execute — the only thing that changed is that foreign origins are now rejected. On the pre-fix build, a message with a foreign
event.origin was processed identically to this one; that was the bug.
If no toast appears, check these three first — the listener genuinely isn't there yet:
| Symptom | Cause |
|---|---|
diagnostic prints designer mounted in this frame: false | The board is in view mode, or the designer hasn't finished mounting. The listener is registered only by the designer — the header must read “Board designer” with Preview / Exit designer / Save / Publish. |
| the tab shows the Anaplan login, or the board silently redirects | Session expired. The att cookie lives ~35 minutes; log in again and reopen the /edit URL. |
diagnostic prints posted to 1 window(s) on a page that embeds the designer | Wrong frame. Open the standalone designer URL /a/springboard-ui/app/{app}/boards/{page}/edit directly, or pick the springboard-ui frame in the DevTools context dropdown. |
The unmodified attacker page as submitted, on its own at /attacker.html — one button, no framing, nothing else.
This page collects and stores nothing. It only calls window.open() and postMessage() against the URL above.