-
Notifications
You must be signed in to change notification settings - Fork 7
feat(security): 统一工具执行安全拦截并打通 Ask 审批闭环 #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
166b314
2433233
e5086ae
208dff8
7fe203f
6095be6
6287b13
369f9c5
52b3028
dfbc7d8
fddeebb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| //go:build !windows | ||
|
|
||
| package main | ||
|
|
||
| // setUTF8Mode 在非 Windows 系统上是空操作。 | ||
| // Linux 和 macOS 默认使用 UTF-8 编码,无需额外设置。 | ||
| func setUTF8Mode() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,15 @@ | |
| - 对不确定的内容明确说明假设 | ||
| - 默认使用中文回答 | ||
|
|
||
| 你可以调用edit,grep,list,read,write工具,规范和opencode保持一致 | ||
| 安全与工具执行要求(必须遵守): | ||
| - 对可能有副作用的操作(尤其是 bash、写文件、网络请求),先进行风险判断,再执行。 | ||
| - 当命中安全策略为 deny 时,必须明确拒绝并解释原因,不得尝试绕过。 | ||
| - 当命中安全策略为 ask 时,先向用户确认,再继续执行。 | ||
| - 执行 bash 前必须说明命令目的、影响范围与关键参数;优先使用只读、可回滚、最小权限命令。 | ||
| - 严禁执行高危破坏性命令(如无边界删除、系统级破坏、恶意下载执行等)。 | ||
| - 涉及路径操作时,默认限定在当前工作区内,不对工作区外路径进行读写。 | ||
|
|
||
| 你可以调用edit,grep,list,read,write,bash工具,规范和opencode保持一致 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 模型知道 opencode 是什么吗?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 收到,豆包写的提示词,我这就改 |
||
|
|
||
| 当需要查看指定目录下的文件 / 目录结构时,调用 list 工具。 | ||
| 必填参数:path(目标目录路径,如/home/project); | ||
|
|
@@ -29,10 +37,16 @@ | |
| 必填参数:filePath(目标文件路径)old_str(待替换的旧文本)、new_str(替换后的新文本); | ||
| 可选参数:backup(是否创建文件备份,布尔值,默认 true) | ||
|
|
||
| 当需要在工作区目录内执行任意终端命令、脚本、系统操作时,调用 bash 工具。 | ||
| 必填参数:command(待执行的 bash 命令字符串,如 grep 'error' ./log) | ||
| 可选参数:workdir(命令执行目录,字符串,默认工作区根目录),timeout(命令超时时间,整数,单位毫秒,默认 120000),description(命令用途说明,字符串,默认空) | ||
|
Comment on lines
+40
to
+42
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这些不应该放在系统提示词里,你们的工具定义中已经有 tool params 的 schema 了吧
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 是的,历史遗留,这个pr就处理
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 看了一下,工具定义我还没加到上下文,这个可能要等到下个pr修,这个pr管不来这么多
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 可以,这些问题。下一个PR要做的事情,都可以先记录一个issue |
||
|
|
||
| 你必须严格按照以下JSON格式返回工具调用指令,不要返回其他内容: | ||
| { | ||
| "tool": "工具名称(如list/read/bash)", | ||
| "params": {"参数名": "参数值"}, | ||
| "thought": "你调用该工具的原因" | ||
| } | ||
| } | ||
|
|
||
| 调用工具时,你只能严格遵守json格式,不能输出多余字符,一次只能调用一个工具 | ||
| 输出json完成后,主动终止对话,等待工具响应 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. “主动终止对话,等待工具响应” 这个不是模型的行为吧(这个 loop 本质上还是我们自己控制的) 所以这里你告诉模型这些信息,它也理解不了,甚至可能会是一种噪音。
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 明白了,主要是开始调试没有Todo list的时候有一些思考模型不会终止流式对话,会出现2次输出,为了方便调试加的,这就删
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 看了一下,工具定义我还没加到上下文,这个可能要等到下个pr修,这个pr管不来这么多
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
这个评论不是在这里的吧? |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| package tools | ||
|
|
||
| import ( | ||
| "errors" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "go-llm-demo/internal/server/domain" | ||
| ) | ||
|
|
||
| type mockSecurityChecker struct { | ||
| action domain.Action | ||
| } | ||
|
|
||
| func (m mockSecurityChecker) Check(_ string, _ string) domain.Action { | ||
| return m.action | ||
| } | ||
|
|
||
| func TestBashTool_Run_DeniedBySecurity(t *testing.T) { | ||
| SetSecurityChecker(mockSecurityChecker{action: domain.ActionDeny}) | ||
| defer SetSecurityChecker(nil) | ||
|
|
||
| result := (&BashTool{}).Run(map[string]interface{}{ | ||
| "command": "echo hello", | ||
| }) | ||
|
|
||
| if result == nil { | ||
| t.Fatal("result should not be nil") | ||
| } | ||
| if result.Success { | ||
| t.Fatal("expected bash execution to be denied") | ||
| } | ||
| if !strings.Contains(result.Error, "安全策略拒绝执行") { | ||
| t.Fatalf("unexpected error: %s", result.Error) | ||
| } | ||
| if result.Metadata["securityAction"] != string(domain.ActionDeny) { | ||
| t.Fatalf("unexpected security action: %#v", result.Metadata["securityAction"]) | ||
| } | ||
| } | ||
|
|
||
| func TestBashTool_Run_AskBySecurity(t *testing.T) { | ||
| SetSecurityChecker(mockSecurityChecker{action: domain.ActionAsk}) | ||
| defer SetSecurityChecker(nil) | ||
|
|
||
| result := (&BashTool{}).Run(map[string]interface{}{ | ||
| "command": "go build ./...", | ||
| }) | ||
|
|
||
| if result == nil { | ||
| t.Fatal("result should not be nil") | ||
| } | ||
| if result.Success { | ||
| t.Fatal("expected bash execution to require confirmation") | ||
| } | ||
| if !strings.Contains(result.Error, "需要用户确认") { | ||
| t.Fatalf("unexpected error: %s", result.Error) | ||
| } | ||
| if result.Metadata["securityAction"] != string(domain.ActionAsk) { | ||
| t.Fatalf("unexpected security action: %#v", result.Metadata["securityAction"]) | ||
| } | ||
| } | ||
|
|
||
| func TestPreferredShellCommandFallsBackToCmdOnWindows(t *testing.T) { | ||
| lookup := func(name string) (string, error) { | ||
| if name == "cmd.exe" { | ||
| return name, nil | ||
| } | ||
| return "", errors.New("not found") | ||
| } | ||
|
|
||
| shell, args := preferredShellCommand("windows", "echo hello", lookup, "powershell", []string{"-Command", "echo hello"}) | ||
| if shell != "cmd.exe" { | ||
| t.Fatalf("expected cmd.exe fallback, got %q", shell) | ||
| } | ||
| if len(args) != 2 || args[0] != "/C" || args[1] != "echo hello" { | ||
| t.Fatalf("unexpected cmd.exe args: %#v", args) | ||
| } | ||
| } | ||
|
|
||
| func TestPreferredShellCommandFallsBackToShWithoutBash(t *testing.T) { | ||
| lookup := func(name string) (string, error) { | ||
| if name == "sh" { | ||
| return name, nil | ||
| } | ||
| return "", errors.New("not found") | ||
| } | ||
|
|
||
| shell, args := preferredShellCommand("linux", "echo hello", lookup, "bash", []string{"-lc", "echo hello"}) | ||
| if shell != "sh" { | ||
| t.Fatalf("expected sh fallback, got %q", shell) | ||
| } | ||
| if len(args) != 2 || args[0] != "-c" || args[1] != "echo hello" { | ||
| t.Fatalf("unexpected sh args: %#v", args) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“go-llm-demo” 这个表述或许应该换了,可以在下一个PR中处理。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok,等下一个pr处理掉