feat: 增加共享 Agent Browser 调试能力
需求:在 AI 编程会话中让用户与 Agent 共享同一浏览器页面,并查看控制台与网络信息。 实现:新增沙箱浏览器内核、Host API/渲染器面板、OpenCode 工具接入及安全边界测试。
This commit is contained in:
108
electron/agent-browser/cdp-guard.ts
Normal file
108
electron/agent-browser/cdp-guard.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { AgentBrowserFault } from './fault';
|
||||
|
||||
const BLOCKED_METHODS = new Set([
|
||||
'DOM.getFileInfo',
|
||||
'DOM.setFileInputFiles',
|
||||
'Page.crash',
|
||||
'Page.setDownloadBehavior',
|
||||
'Security.handleCertificateError',
|
||||
'Security.setDisableNetworkAccessForOrigins',
|
||||
'Security.setIgnoreCertificateErrors',
|
||||
'Security.setOverrideCertificateErrors',
|
||||
]);
|
||||
|
||||
const BLOCKED_DOMAINS = [
|
||||
'Cast.',
|
||||
'DeviceAccess.',
|
||||
'Extensions.',
|
||||
'Tethering.',
|
||||
] as const;
|
||||
|
||||
const URL_FIELDS_BY_METHOD = new Map<string, string[]>([
|
||||
['Fetch.continueRequest', ['url']],
|
||||
['Network.continueInterceptedRequest', ['url']],
|
||||
['Network.loadNetworkResource', ['url']],
|
||||
['Page.navigate', ['url']],
|
||||
]);
|
||||
|
||||
function hasExternalTargetReference(params: Record<string, unknown> | undefined): boolean {
|
||||
return Boolean(params && (
|
||||
typeof params.targetId === 'string'
|
||||
|| typeof params.browserContextId === 'string'
|
||||
));
|
||||
}
|
||||
|
||||
function isAllowedPageUrl(value: string): boolean {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return url.protocol === 'http:' || url.protocol === 'https:';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentBrowserCdpGuard {
|
||||
assertAllowed(
|
||||
method: string,
|
||||
params: Record<string, unknown> | undefined,
|
||||
sessionRef: string | undefined,
|
||||
childSessions: ReadonlySet<string>,
|
||||
ioHandles: ReadonlySet<string>,
|
||||
): void {
|
||||
const blockedHostDomain = (
|
||||
(method.startsWith('Browser.') && method !== 'Browser.getVersion')
|
||||
|| method.startsWith('SystemInfo.')
|
||||
|| method.startsWith('Memory.')
|
||||
|| method.startsWith('Tracing.')
|
||||
|| BLOCKED_DOMAINS.some((domain) => method.startsWith(domain))
|
||||
);
|
||||
if (BLOCKED_METHODS.has(method) || blockedHostDomain) {
|
||||
throw new AgentBrowserFault(
|
||||
'CDP_METHOD_BLOCKED',
|
||||
`CDP method ${method} 可能影响 Makelore 宿主,已阻止。`,
|
||||
false,
|
||||
);
|
||||
}
|
||||
if (method.startsWith('Target.')) {
|
||||
throw new AgentBrowserFault(
|
||||
'TARGET_DENIED',
|
||||
'Target 域由开发浏览器托管,不能直接操作其他 Electron 页面。',
|
||||
false,
|
||||
);
|
||||
}
|
||||
if (hasExternalTargetReference(params)) {
|
||||
throw new AgentBrowserFault(
|
||||
'TARGET_DENIED',
|
||||
'不能指定开发浏览器之外的 CDP target。',
|
||||
false,
|
||||
);
|
||||
}
|
||||
for (const field of URL_FIELDS_BY_METHOD.get(method) ?? []) {
|
||||
const value = params?.[field];
|
||||
if (typeof value === 'string' && !isAllowedPageUrl(value)) {
|
||||
throw new AgentBrowserFault(
|
||||
'CDP_METHOD_BLOCKED',
|
||||
`${method} 只允许访问 HTTP 或 HTTPS 地址。`,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (sessionRef && !childSessions.has(sessionRef)) {
|
||||
throw new AgentBrowserFault(
|
||||
'TARGET_DENIED',
|
||||
'CDP session 不属于当前开发浏览器。',
|
||||
false,
|
||||
);
|
||||
}
|
||||
if (method.startsWith('IO.')) {
|
||||
const handle = params?.handle;
|
||||
if (typeof handle !== 'string' || !ioHandles.has(handle)) {
|
||||
throw new AgentBrowserFault(
|
||||
'TARGET_DENIED',
|
||||
'IO handle 不属于当前开发浏览器会话。',
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user