fix(mcp): use window.location.origin as trusted sandbox origin

The previous approach derived trustedOrigin from document.referrer at
startup and fell back to '*' when referrer was empty, with a lazy-set
from the first incoming message as a further fallback. Both paths leave
a window where notifyReady broadcasts to all frames or the origin can
be set by an untrusted first message.

The sandbox is always served same-origin with LibreChat (/api/mcp/sandbox),
so window.location.origin is always the exact parent origin. This replaces
the referrer parse and lazy-set entirely: trustedOrigin is a const set at
parse time, notifyReady uses it directly, and the message handler rejects
any message whose origin does not match without fallback.
This commit is contained in:
Dustin Healy 2026-06-23 18:47:28 -07:00
parent 90b2d7a1ab
commit ac2812ba2f

View file

@ -18,26 +18,21 @@
let readyInterval = null; let readyInterval = null;
const SANDBOX_PREFIX = 'ui/notifications/sandbox-'; const SANDBOX_PREFIX = 'ui/notifications/sandbox-';
// Derive the trusted origin from the referrer at startup. // The sandbox is always served same-origin with LibreChat, so window.location.origin
// The sandbox is always served same-origin, so document.referrer is the LibreChat page URL. // is the exact expected parent origin. No referrer fallback or lazy-set needed.
let trustedOrigin = null; const trustedOrigin = window.location.origin;
try {
if (document.referrer) {
trustedOrigin = new URL(document.referrer).origin;
}
} catch {}
function notifyReady() { function notifyReady() {
window.parent.postMessage( window.parent.postMessage(
{ jsonrpc: '2.0', method: 'ui/notifications/sandbox-proxy-ready', params: {} }, { jsonrpc: '2.0', method: 'ui/notifications/sandbox-proxy-ready', params: {} },
trustedOrigin || '*' trustedOrigin
); );
if (!readyInterval) { if (!readyInterval) {
readyInterval = setInterval(() => { readyInterval = setInterval(() => {
if (!innerFrame) { if (!innerFrame) {
window.parent.postMessage( window.parent.postMessage(
{ jsonrpc: '2.0', method: 'ui/notifications/sandbox-proxy-ready', params: {} }, { jsonrpc: '2.0', method: 'ui/notifications/sandbox-proxy-ready', params: {} },
trustedOrigin || '*' trustedOrigin
); );
} }
}, 500); }, 500);
@ -54,9 +49,7 @@
if (!msg || msg.jsonrpc !== '2.0') return; if (!msg || msg.jsonrpc !== '2.0') return;
if (event.source === window.parent) { if (event.source === window.parent) {
if (!trustedOrigin) { if (event.origin !== trustedOrigin) {
trustedOrigin = event.origin;
} else if (event.origin !== trustedOrigin) {
return; return;
} }
@ -76,9 +69,7 @@
if (msg.method && msg.method.startsWith(SANDBOX_PREFIX)) { if (msg.method && msg.method.startsWith(SANDBOX_PREFIX)) {
return; return;
} }
if (trustedOrigin) { window.parent.postMessage(msg, trustedOrigin);
window.parent.postMessage(msg, trustedOrigin);
}
} }
}); });