mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-01 20:01:35 +00:00
fix(mcp): advertise apps per-request for user connections and tear down navigated sandbox frames
User connections now advertise the io.modelcontextprotocol/ui capability using the per-request appsEnabled resolved from resolveAllowlists rather than the static base flag, so a tenant/role/user override of mcpSettings.apps is honored at capability negotiation. The sandbox proxy treats inner-frame navigation as a teardown signal: after the initial blob load, any further load means the allow-scripts app navigated its own frame, so the proxy marks it navigated, revokes the blob, removes the frame, and gates both forwarding paths on that flag. This stops proxied host responses from reaching a navigated page and stops a navigated page from relaying messages to the host.
This commit is contained in:
parent
87341c67c0
commit
1a70dce24b
2 changed files with 24 additions and 4 deletions
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
let innerFrame = null;
|
||||
let innerFrameBlobUrl = null;
|
||||
let innerFrameNavigated = false;
|
||||
let readyInterval = null;
|
||||
const SANDBOX_PREFIX = 'ui/notifications/sandbox-';
|
||||
|
||||
|
|
@ -84,13 +85,13 @@
|
|||
createInnerFrame(msg.params);
|
||||
return;
|
||||
}
|
||||
if (innerFrame && innerFrame.contentWindow) {
|
||||
if (innerFrame && innerFrame.contentWindow && !innerFrameNavigated) {
|
||||
innerFrame.contentWindow.postMessage(msg, '*');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (innerFrame && event.source === innerFrame.contentWindow) {
|
||||
if (innerFrame && !innerFrameNavigated && event.source === innerFrame.contentWindow) {
|
||||
if (msg.method && msg.method.startsWith(SANDBOX_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -111,6 +112,25 @@
|
|||
|
||||
innerFrame = document.createElement('iframe');
|
||||
|
||||
// The first load is the blob document we created. A `allow-scripts` app can navigate its own
|
||||
// frame; any further load means it left our document, so stop proxying and tear it down
|
||||
// rather than forward host responses (proxied MCP data) to the navigated page.
|
||||
innerFrameNavigated = false;
|
||||
let frameLoads = 0;
|
||||
innerFrame.addEventListener('load', () => {
|
||||
frameLoads += 1;
|
||||
if (frameLoads <= 1) {
|
||||
return;
|
||||
}
|
||||
innerFrameNavigated = true;
|
||||
if (innerFrameBlobUrl) {
|
||||
URL.revokeObjectURL(innerFrameBlobUrl);
|
||||
innerFrameBlobUrl = null;
|
||||
}
|
||||
innerFrame.remove();
|
||||
innerFrame = null;
|
||||
});
|
||||
|
||||
// Strip allow-same-origin from the inner frame regardless of what the host requested.
|
||||
// Full cross-origin isolation requires the sandbox proxy to be served from a different
|
||||
// origin than the host; stripping allow-same-origin provides partial mitigation when
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ export abstract class UserConnectionManager {
|
|||
graphTokenResolver,
|
||||
});
|
||||
const registry = MCPServersRegistry.getInstance();
|
||||
const { allowedDomains, allowedAddresses, useSSRFProtection } =
|
||||
const { allowedDomains, allowedAddresses, useSSRFProtection, appsEnabled } =
|
||||
await registry.resolveAllowlists({ userId: user?.id, role: user?.role });
|
||||
await this.assertResolvedRuntimeConfigAllowed({
|
||||
config: runtimeConfig,
|
||||
|
|
@ -472,7 +472,7 @@ export abstract class UserConnectionManager {
|
|||
useSSRFProtection,
|
||||
allowedDomains,
|
||||
allowedAddresses,
|
||||
enableApps: registry.getAppsEnabled(),
|
||||
enableApps: appsEnabled,
|
||||
ephemeralConnection,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue