From 98a75a50d31710b701bcc44158fc2d491d858183 Mon Sep 17 00:00:00 2001 From: nav Date: Mon, 30 Mar 2026 11:22:26 +0000 Subject: [PATCH] feat: add proxy pcexec tool --- plans/PROXY_PC_EXEC.md | 11 +++--- plugin/index.ts | 70 +++++++++++++++++++++++++++++++++++++ plugin/openclaw.plugin.json | 7 +++- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/plans/PROXY_PC_EXEC.md b/plans/PROXY_PC_EXEC.md index f5c113d..af38b5e 100644 --- a/plans/PROXY_PC_EXEC.md +++ b/plans/PROXY_PC_EXEC.md @@ -12,14 +12,17 @@ ### 2. 扩展 `openclaw.plugin.json` 需要在 `openclaw.plugin.json` 中新增配置字段: -- `config.proxy-allowlist` +- `config.proxyAllowlist` + +兼容性说明: +- 如有需要,也可兼容读取 `proxy-allowlist` 作为别名 用途: - 用于声明哪些 agent 允许调用 `proxy-pcexec` - 只有在该 allowlist 中的 agent,才具备调用该工具的权限 建议约束: -- `config.proxy-allowlist` 应为 agent 标识列表 +- `config.proxyAllowlist` 应为 agent 标识列表 - `allowlist` 仅支持精确匹配,不支持通配、分组或模糊匹配 - 若调用方不在 allowlist 中,应直接拒绝调用 - 默认配置应偏保守;未配置时建议视为不允许任何 agent 调用 @@ -46,7 +49,7 @@ ### 权限校验 调用 `proxy-pcexec` 时应至少进行以下校验: -1. 校验调用方 agent 是否在 `config.proxy-allowlist` 中(精确匹配) +1. 校验调用方 agent 是否在 `config.proxyAllowlist` 中(精确匹配) 2. 校验 `proxy-for` 是否存在且非空 3. 不要求 `proxy-for` 必须是已注册或已知 agent-id,可自由填写 4. 通过校验后,再执行与 `pcexec` 等价的命令执行流程 @@ -76,5 +79,5 @@ ## 已明确的设计结论 - `proxy-for` 可以随意填写,不要求必须是已注册 agent - 日志需要记录 `executor` 和 `proxy-for` -- `config.proxy-allowlist` 仅支持精确匹配 +- `config.proxyAllowlist` 仅支持精确匹配 - allowlist 中的 agent 可以代理任意 agent,不需要额外的 `proxy-for` 限制 diff --git a/plugin/index.ts b/plugin/index.ts index 9c84e5b..554a5e8 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -26,6 +26,12 @@ function resolveOpenclawPath(config?: { openclawProfilePath?: string }): string return require('path').join(home, '.openclaw'); } +function resolveProxyAllowlist(config?: { proxyAllowlist?: unknown; 'proxy-allowlist'?: unknown }): string[] { + const value = config?.proxyAllowlist ?? config?.['proxy-allowlist']; + if (!Array.isArray(value)) return []; + return value.filter((item): item is string => typeof item === 'string'); +} + // Plugin registration function function register(api: any, config?: any) { const logger = api.logger || { info: console.log, error: console.error }; @@ -33,6 +39,7 @@ function register(api: any, config?: any) { logger.info('PaddedCell plugin initializing...'); const openclawPath = resolveOpenclawPath(config); + const proxyAllowlist = resolveProxyAllowlist(config); const binDir = require('path').join(openclawPath, 'bin'); // Register pcexec tool — pass a FACTORY function that receives context @@ -85,6 +92,69 @@ function register(api: any, config?: any) { }; }); + api.registerTool((ctx: any) => { + const agentId = ctx.agentId; + const workspaceDir = ctx.workspaceDir; + + return { + name: 'proxy-pcexec', + description: 'Safe exec with password sanitization using a proxied AGENT_ID', + parameters: { + type: 'object', + properties: { + command: { type: 'string', description: 'Command to execute' }, + cwd: { type: 'string', description: 'Working directory' }, + timeout: { type: 'number', description: 'Timeout in milliseconds' }, + 'proxy-for': { type: 'string', description: 'AGENT_ID value to inject for the subprocess' }, + }, + required: ['command', 'proxy-for'], + }, + async execute(_id: string, params: any) { + const command = params.command; + const proxyFor = params['proxy-for']; + if (!command) { + throw new Error('Missing required parameter: command'); + } + if (!proxyFor) { + throw new Error('Missing required parameter: proxy-for'); + } + if (!agentId || !proxyAllowlist.includes(agentId)) { + throw new Error('Current agent is not allowed to call proxy-pcexec'); + } + + logger.info('proxy-pcexec invoked', { + executor: agentId, + proxyFor, + command, + }); + + const currentPath = process.env.PATH || ''; + const newPath = currentPath.includes(binDir) + ? currentPath + : `${currentPath}:${binDir}`; + + const result = await pcexec(command, { + cwd: params.cwd || workspaceDir, + timeout: params.timeout, + env: { + AGENT_ID: String(proxyFor), + AGENT_WORKSPACE: workspaceDir || '', + AGENT_VERIFY, + PROXY_PCEXEC_EXECUTOR: agentId || '', + PCEXEC_PROXIED: 'true', + PATH: newPath, + }, + }); + + let output = result.stdout; + if (result.stderr) { + output += result.stderr; + } + return { content: [{ type: 'text', text: output }] }; + }, + }; + }); + // Register safe_restart tool api.registerTool((ctx: any) => { const agentId = ctx.agentId; diff --git a/plugin/openclaw.plugin.json b/plugin/openclaw.plugin.json index f6f98b6..3dd512d 100644 --- a/plugin/openclaw.plugin.json +++ b/plugin/openclaw.plugin.json @@ -9,7 +9,12 @@ "properties": { "enabled": { "type": "boolean", "default": true }, "secretMgrPath": { "type": "string", "default": "" }, - "openclawProfilePath": { "type": "string", "default": "" } + "openclawProfilePath": { "type": "string", "default": "" }, + "proxyAllowlist": { + "type": "array", + "items": { "type": "string" }, + "default": [] + } } } }