mirror of https://github.com/openclaw/openclaw.git
macOS: restrict canvas agent actions to trusted surfaces (#46790)
* macOS: restrict canvas agent actions to trusted surfaces * Changelog: note trusted macOS canvas actions * macOS: encode allowed canvas schemes as JSON
This commit is contained in:
parent
3cbf932413
commit
8e04d1fe15
|
|
@ -26,6 +26,11 @@ Docs: https://docs.openclaw.ai
|
||||||
- Z.AI/onboarding: add `glm-5-turbo` to the default Z.AI provider catalog so onboarding-generated configs expose the new model alongside the existing GLM defaults. (#46670) Thanks @tomsun28.
|
- Z.AI/onboarding: add `glm-5-turbo` to the default Z.AI provider catalog so onboarding-generated configs expose the new model alongside the existing GLM defaults. (#46670) Thanks @tomsun28.
|
||||||
- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#40146)
|
- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#40146)
|
||||||
- Plugins/install precedence: keep bundled plugins ahead of auto-discovered globals by default, but let an explicitly installed plugin record win its own duplicate-id tie so installed channel plugins load from `~/.openclaw/extensions` after `openclaw plugins install`.
|
- Plugins/install precedence: keep bundled plugins ahead of auto-discovered globals by default, but let an explicitly installed plugin record win its own duplicate-id tie so installed channel plugins load from `~/.openclaw/extensions` after `openclaw plugins install`.
|
||||||
|
- macOS/canvas actions: keep unattended local agent actions on trusted in-app canvas surfaces only, and stop exposing the deep-link fallback key to arbitrary page scripts. Thanks @vincentkoc.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Slack/interactive replies: preserve `channelData.slack.blocks` through live DM delivery and preview-finalized edits so Block Kit button and select directives render instead of falling back to raw text. Thanks @vincentkoc.
|
||||||
- Zalo/plugin runtime: export `resolveClientIp` from `openclaw/plugin-sdk/zalo` so installed builds no longer crash on startup when the webhook monitor loads from the packaged extension instead of the monorepo source tree. (#46549) Thanks @No898.
|
- Zalo/plugin runtime: export `resolveClientIp` from `openclaw/plugin-sdk/zalo` so installed builds no longer crash on startup when the webhook monitor loads from the packaged extension instead of the monorepo source tree. (#46549) Thanks @No898.
|
||||||
- CI/channel test routing: move the built-in channel suites into `test:channels` and keep them out of `test:extensions`, so extension CI no longer fails after the channel migration while targeted test routing still sends Slack, Signal, and iMessage suites to the right lane. (#46066) Thanks @scoootscooob.
|
- CI/channel test routing: move the built-in channel suites into `test:channels` and keep them out of `test:extensions`, so extension CI no longer fails after the channel migration while targeted test routing still sends Slack, Signal, and iMessage suites to the right lane. (#46066) Thanks @scoootscooob.
|
||||||
- Browser/profiles: drop the auto-created `chrome-relay` browser profile; users who need the Chrome extension relay must now create their own profile via `openclaw browser create-profile`. (#45777) Thanks @odysseus0.
|
- Browser/profiles: drop the auto-created `chrome-relay` browser profile; users who need the Chrome extension relay must now create their own profile via `openclaw browser create-profile`. (#45777) Thanks @odysseus0.
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,10 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
|
||||||
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
|
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||||
guard Self.allMessageNames.contains(message.name) else { return }
|
guard Self.allMessageNames.contains(message.name) else { return }
|
||||||
|
|
||||||
// Only accept actions from local Canvas content (not arbitrary web pages).
|
// Only accept actions from the in-app canvas scheme. Local-network HTTP
|
||||||
|
// pages are regular web content and must not get direct agent dispatch.
|
||||||
guard let webView = message.webView, let url = webView.url else { return }
|
guard let webView = message.webView, let url = webView.url else { return }
|
||||||
if let scheme = url.scheme, CanvasScheme.allSchemes.contains(scheme) {
|
guard let scheme = url.scheme, CanvasScheme.allSchemes.contains(scheme) else {
|
||||||
// ok
|
|
||||||
} else if Self.isLocalNetworkCanvasURL(url) {
|
|
||||||
// ok
|
|
||||||
} else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,10 +104,5 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func isLocalNetworkCanvasURL(_ url: URL) -> Bool {
|
|
||||||
LocalNetworkURLSupport.isLocalNetworkHTTPURL(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Formatting helpers live in OpenClawKit (`OpenClawCanvasA2UIAction`).
|
// Formatting helpers live in OpenClawKit (`OpenClawCanvasA2UIAction`).
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,21 +50,24 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
|
||||||
|
|
||||||
// Bridge A2UI "a2uiaction" DOM events back into the native agent loop.
|
// Bridge A2UI "a2uiaction" DOM events back into the native agent loop.
|
||||||
//
|
//
|
||||||
// Prefer WKScriptMessageHandler when WebKit exposes it, otherwise fall back to an unattended deep link
|
// Keep the bridge on the trusted in-app canvas scheme only, and do not
|
||||||
// (includes the app-generated key so it won't prompt).
|
// expose unattended deep-link credentials to page JavaScript.
|
||||||
canvasWindowLogger.debug("CanvasWindowController init building A2UI bridge script")
|
canvasWindowLogger.debug("CanvasWindowController init building A2UI bridge script")
|
||||||
let deepLinkKey = DeepLinkHandler.currentCanvasKey()
|
|
||||||
let injectedSessionKey = sessionKey.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "main"
|
let injectedSessionKey = sessionKey.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "main"
|
||||||
|
let allowedSchemesJSON = (
|
||||||
|
try? String(
|
||||||
|
data: JSONSerialization.data(withJSONObject: CanvasScheme.allSchemes),
|
||||||
|
encoding: .utf8)
|
||||||
|
) ?? "[]"
|
||||||
let bridgeScript = """
|
let bridgeScript = """
|
||||||
(() => {
|
(() => {
|
||||||
try {
|
try {
|
||||||
const allowedSchemes = \(String(describing: CanvasScheme.allSchemes));
|
const allowedSchemes = \(allowedSchemesJSON);
|
||||||
const protocol = location.protocol.replace(':', '');
|
const protocol = location.protocol.replace(':', '');
|
||||||
if (!allowedSchemes.includes(protocol)) return;
|
if (!allowedSchemes.includes(protocol)) return;
|
||||||
if (globalThis.__openclawA2UIBridgeInstalled) return;
|
if (globalThis.__openclawA2UIBridgeInstalled) return;
|
||||||
globalThis.__openclawA2UIBridgeInstalled = true;
|
globalThis.__openclawA2UIBridgeInstalled = true;
|
||||||
|
|
||||||
const deepLinkKey = \(Self.jsStringLiteral(deepLinkKey));
|
|
||||||
const sessionKey = \(Self.jsStringLiteral(injectedSessionKey));
|
const sessionKey = \(Self.jsStringLiteral(injectedSessionKey));
|
||||||
const machineName = \(Self.jsStringLiteral(InstanceIdentity.displayName));
|
const machineName = \(Self.jsStringLiteral(InstanceIdentity.displayName));
|
||||||
const instanceId = \(Self.jsStringLiteral(InstanceIdentity.instanceId));
|
const instanceId = \(Self.jsStringLiteral(InstanceIdentity.instanceId));
|
||||||
|
|
@ -104,24 +107,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = userAction.context ? (' ctx=' + JSON.stringify(userAction.context)) : '';
|
// Without the native handler, fail closed instead of exposing an
|
||||||
const message =
|
// unattended deep-link credential to page JavaScript.
|
||||||
'CANVAS_A2UI action=' + userAction.name +
|
|
||||||
' session=' + sessionKey +
|
|
||||||
' surface=' + userAction.surfaceId +
|
|
||||||
' component=' + (userAction.sourceComponentId || '-') +
|
|
||||||
' host=' + machineName.replace(/\\s+/g, '_') +
|
|
||||||
' instance=' + instanceId +
|
|
||||||
ctx +
|
|
||||||
' default=update_canvas';
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.set('message', message);
|
|
||||||
params.set('sessionKey', sessionKey);
|
|
||||||
params.set('thinking', 'low');
|
|
||||||
params.set('deliver', 'false');
|
|
||||||
params.set('channel', 'last');
|
|
||||||
params.set('key', deepLinkKey);
|
|
||||||
location.href = 'openclaw://agent?' + params.toString();
|
|
||||||
} catch {}
|
} catch {}
|
||||||
}, true);
|
}, true);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue