feat: add proxy pcexec tool

This commit is contained in:
nav
2026-03-30 11:22:26 +00:00
parent 4a8a4b01cb
commit 98a75a50d3
3 changed files with 83 additions and 5 deletions

View File

@@ -12,14 +12,17 @@
### 2. 扩展 `openclaw.plugin.json` ### 2. 扩展 `openclaw.plugin.json`
需要在 `openclaw.plugin.json` 中新增配置字段: 需要在 `openclaw.plugin.json` 中新增配置字段:
- `config.proxy-allowlist` - `config.proxyAllowlist`
兼容性说明:
- 如有需要,也可兼容读取 `proxy-allowlist` 作为别名
用途: 用途:
- 用于声明哪些 agent 允许调用 `proxy-pcexec` - 用于声明哪些 agent 允许调用 `proxy-pcexec`
- 只有在该 allowlist 中的 agent才具备调用该工具的权限 - 只有在该 allowlist 中的 agent才具备调用该工具的权限
建议约束: 建议约束:
- `config.proxy-allowlist` 应为 agent 标识列表 - `config.proxyAllowlist` 应为 agent 标识列表
- `allowlist` 仅支持精确匹配,不支持通配、分组或模糊匹配 - `allowlist` 仅支持精确匹配,不支持通配、分组或模糊匹配
- 若调用方不在 allowlist 中,应直接拒绝调用 - 若调用方不在 allowlist 中,应直接拒绝调用
- 默认配置应偏保守;未配置时建议视为不允许任何 agent 调用 - 默认配置应偏保守;未配置时建议视为不允许任何 agent 调用
@@ -46,7 +49,7 @@
### 权限校验 ### 权限校验
调用 `proxy-pcexec` 时应至少进行以下校验: 调用 `proxy-pcexec` 时应至少进行以下校验:
1. 校验调用方 agent 是否在 `config.proxy-allowlist` 中(精确匹配) 1. 校验调用方 agent 是否在 `config.proxyAllowlist` 中(精确匹配)
2. 校验 `proxy-for` 是否存在且非空 2. 校验 `proxy-for` 是否存在且非空
3. 不要求 `proxy-for` 必须是已注册或已知 agent-id可自由填写 3. 不要求 `proxy-for` 必须是已注册或已知 agent-id可自由填写
4. 通过校验后,再执行与 `pcexec` 等价的命令执行流程 4. 通过校验后,再执行与 `pcexec` 等价的命令执行流程
@@ -76,5 +79,5 @@
## 已明确的设计结论 ## 已明确的设计结论
- `proxy-for` 可以随意填写,不要求必须是已注册 agent - `proxy-for` 可以随意填写,不要求必须是已注册 agent
- 日志需要记录 `executor``proxy-for` - 日志需要记录 `executor``proxy-for`
- `config.proxy-allowlist` 仅支持精确匹配 - `config.proxyAllowlist` 仅支持精确匹配
- allowlist 中的 agent 可以代理任意 agent不需要额外的 `proxy-for` 限制 - allowlist 中的 agent 可以代理任意 agent不需要额外的 `proxy-for` 限制

View File

@@ -26,6 +26,12 @@ function resolveOpenclawPath(config?: { openclawProfilePath?: string }): string
return require('path').join(home, '.openclaw'); 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 // Plugin registration function
function register(api: any, config?: any) { function register(api: any, config?: any) {
const logger = api.logger || { info: console.log, error: console.error }; 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...'); logger.info('PaddedCell plugin initializing...');
const openclawPath = resolveOpenclawPath(config); const openclawPath = resolveOpenclawPath(config);
const proxyAllowlist = resolveProxyAllowlist(config);
const binDir = require('path').join(openclawPath, 'bin'); const binDir = require('path').join(openclawPath, 'bin');
// Register pcexec tool — pass a FACTORY function that receives context // 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 // Register safe_restart tool
api.registerTool((ctx: any) => { api.registerTool((ctx: any) => {
const agentId = ctx.agentId; const agentId = ctx.agentId;

View File

@@ -9,7 +9,12 @@
"properties": { "properties": {
"enabled": { "type": "boolean", "default": true }, "enabled": { "type": "boolean", "default": true },
"secretMgrPath": { "type": "string", "default": "" }, "secretMgrPath": { "type": "string", "default": "" },
"openclawProfilePath": { "type": "string", "default": "" } "openclawProfilePath": { "type": "string", "default": "" },
"proxyAllowlist": {
"type": "array",
"items": { "type": "string" },
"default": []
}
} }
} }
} }