From 98a75a50d31710b701bcc44158fc2d491d858183 Mon Sep 17 00:00:00 2001 From: nav Date: Mon, 30 Mar 2026 11:22:26 +0000 Subject: [PATCH 1/4] 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": [] + } } } } From a2b965094d7d02aee4621ec779ba0336027a7d21 Mon Sep 17 00:00:00 2001 From: nav Date: Mon, 30 Mar 2026 11:38:05 +0000 Subject: [PATCH 2/4] chore: log proxy pcexec auth context --- plugin/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugin/index.ts b/plugin/index.ts index 554a5e8..4f8ced0 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -112,6 +112,10 @@ function register(api: any, config?: any) { async execute(_id: string, params: any) { const command = params.command; const proxyFor = params['proxy-for']; + logger.info('proxy-pcexec auth check', { + agentId, + proxyAllowlist, + }); if (!command) { throw new Error('Missing required parameter: command'); } From 1ac75f429c7cc1afc8309ed968724729f86cb67b Mon Sep 17 00:00:00 2001 From: nav Date: Mon, 30 Mar 2026 11:47:42 +0000 Subject: [PATCH 3/4] fix: load plugin config from api.pluginConfig --- plugin/index.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/plugin/index.ts b/plugin/index.ts index 4f8ced0..6bd04b8 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -26,6 +26,10 @@ function resolveOpenclawPath(config?: { openclawProfilePath?: string }): string return require('path').join(home, '.openclaw'); } +function getPluginConfig(api: any): Record { + return ((api?.pluginConfig as Record | undefined) || {}); +} + function resolveProxyAllowlist(config?: { proxyAllowlist?: unknown; 'proxy-allowlist'?: unknown }): string[] { const value = config?.proxyAllowlist ?? config?.['proxy-allowlist']; if (!Array.isArray(value)) return []; @@ -33,13 +37,14 @@ function resolveProxyAllowlist(config?: { proxyAllowlist?: unknown; 'proxy-allow } // Plugin registration function -function register(api: any, config?: any) { +function register(api: any) { const logger = api.logger || { info: console.log, error: console.error }; logger.info('PaddedCell plugin initializing...'); - const openclawPath = resolveOpenclawPath(config); - const proxyAllowlist = resolveProxyAllowlist(config); + const pluginConfig = getPluginConfig(api); + const openclawPath = resolveOpenclawPath(pluginConfig as { openclawProfilePath?: string }); + const proxyAllowlist = resolveProxyAllowlist(pluginConfig as { proxyAllowlist?: unknown; 'proxy-allowlist'?: unknown }); const binDir = require('path').join(openclawPath, 'bin'); // Register pcexec tool — pass a FACTORY function that receives context @@ -112,10 +117,6 @@ function register(api: any, config?: any) { async execute(_id: string, params: any) { const command = params.command; const proxyFor = params['proxy-for']; - logger.info('proxy-pcexec auth check', { - agentId, - proxyAllowlist, - }); if (!command) { throw new Error('Missing required parameter: command'); } From 36f3c93484ba688040447f36a0e6a1d2b4a15b1f Mon Sep 17 00:00:00 2001 From: nav Date: Mon, 30 Mar 2026 11:56:52 +0000 Subject: [PATCH 4/4] fix: preserve existing plugin config on install --- install.mjs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/install.mjs b/install.mjs index fb607bb..78eab90 100755 --- a/install.mjs +++ b/install.mjs @@ -343,12 +343,21 @@ async function configure() { const plugins = getOpenclawConfig('plugins', {}); plugins.entries = plugins.entries || {}; + + const existingEntry = plugins.entries[PLUGIN_NAME] || {}; + const existingConfig = existingEntry.config || {}; + const defaultConfig = { enabled: true, secretMgrPath, openclawProfilePath: openclawPath }; + plugins.entries[PLUGIN_NAME] = { - enabled: true, - config: { enabled: true, secretMgrPath, openclawProfilePath: openclawPath }, + ...existingEntry, + enabled: existingEntry.enabled ?? true, + config: { + ...defaultConfig, + ...existingConfig, + }, }; setOpenclawConfig('plugins', plugins); - logOk('Plugin entry configured'); + logOk('Plugin entry configured (preserved existing config, added missing defaults)'); } catch (err) { logWarn(`Config failed: ${err.message}`); }