Files
Logan.LiandCursor 2fd1624bec feat: 加固 Agent CLI 执行边界并更新模型目录
收紧 SSH 命令护栏、权限校验与远程执行会话,并同步主流大模型供应商列表。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 09:43:14 +08:00

120 lines
3.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ssh_execute.go - 远程命令执行工具 // Logan.Li
// 复用 aiops 既有 pkg/remoteexec(Go 原生 SSH),凭据由 controller 从 model.Server 构建后注入。
// 任务级复用 PersistentClient 长连接,避免每条命令重建 SSH 握手。
// 实现 DynamicRiskTool:依据具体命令动态评估风险,决定是否触发用户确认。
package tools
import (
"context"
"fmt"
"strings"
"time"
"ezops/pkg/remoteexec"
)
// SSHExecuteTool 远程命令执行工具 // Logan.Li
type SSHExecuteTool struct {
client *remoteexec.PersistentClient // 任务级 SSH 长连接
timeout time.Duration // 默认命令超时
}
const maxSSHCommandTimeout = 300 * time.Second
// NewSSHExecuteTool 创建远程执行工具(内部建立 SSH 长连接) // Logan.Li
func NewSSHExecuteTool(cfg remoteexec.SSHConfig, timeout time.Duration) (*SSHExecuteTool, error) {
if timeout <= 0 {
timeout = 60 * time.Second
}
client, err := remoteexec.NewPersistentClient(cfg)
if err != nil {
return nil, fmt.Errorf("SSH 长连接建立失败: %w", err)
}
return &SSHExecuteTool{client: client, timeout: timeout}, nil
}
// Close 释放 SSH 长连接 // Logan.Li
func (t *SSHExecuteTool) Close() error {
if t.client == nil {
return nil
}
return t.client.Close()
}
// Name 工具名 // Logan.Li
func (t *SSHExecuteTool) Name() string { return "ssh_execute" }
// Description 给 LLM 的工具说明 // Logan.Li
func (t *SSHExecuteTool) Description() string {
return "在远程目标服务器上执行 shell 命令并返回合并的 stdout+stderr 输出与退出码。" +
"用于排障(查看日志/进程/磁盘/网络)、安装与配置软件、重启服务等。" +
"高危命令(rm -rf /、mkfs、reboot 等)需用户确认。"
}
// Parameters JSON Schema 参数定义 // Logan.Li
func (t *SSHExecuteTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"command": map[string]interface{}{
"type": "string",
"description": "要执行的 shell 命令,例如 df -h、systemctl status nginx、tail -n 100 /var/log/messages",
},
"timeout": map[string]interface{}{
"type": "integer",
"description": "命令超时秒数(默认 60)",
},
},
"required": []string{"command"},
}
}
// RiskLevel 静态风险等级(高危) // Logan.Li
func (t *SSHExecuteTool) RiskLevel() RiskLevel { return RiskHigh }
// RiskLevelForArgs 依据命令动态评估风险 // Logan.Li
func (t *SSHExecuteTool) RiskLevelForArgs(args map[string]interface{}) RiskLevel {
return AssessBashRisk(StringParam(args, "command"))
}
// Execute 执行远程命令 // Logan.Li
func (t *SSHExecuteTool) Execute(ctx context.Context, args map[string]interface{}) (*Result, error) {
command := strings.TrimSpace(StringParam(args, "command"))
if command == "" {
return &Result{Success: false, Error: "参数 command 不能为空"}, nil
}
timeout := boundedSSHTimeout(t.timeout)
if sec := IntParam(args, "timeout"); sec > 0 {
timeout = boundedSSHTimeout(time.Duration(sec) * time.Second)
}
res, err := t.client.ExecContext(ctx, command, timeout)
if err != nil {
return &Result{Success: false, Error: fmt.Sprintf("SSH 连接失败: %v", err)}, nil
}
output := res.Output
if !strings.HasSuffix(output, "\n") && output != "" {
output += "\n"
}
output += fmt.Sprintf("[exit_code=%d]", res.ExitCode)
return &Result{
Success: res.Success,
Output: output,
Error: res.Error,
}, nil
}
// boundedSSHTimeout 将模型提供的超时限制在平台允许范围内。 // Logan.Li
func boundedSSHTimeout(timeout time.Duration) time.Duration {
if timeout <= 0 {
return time.Second
}
if timeout > maxSSHCommandTimeout {
return maxSSHCommandTimeout
}
return timeout
}