feat(security): 敏感清单补齐 design/11 §4 的 7 类(写数据/对外发消息/访问密钥/改配置,结构化判据)
This commit is contained in:
1 parent
509dc412eb
commit
85abb90067
6 files changed
+813
-18
No files matched your search
@@ -15,51 +15,124 @@ const (
|
||||
SensitiveExecCommand SensitiveOp = "执行命令"
|
||||
// SensitiveWriteFile 修改文件(file 工具的 write 动作)。
|
||||
SensitiveWriteFile SensitiveOp = "修改文件"
|
||||
// SensitiveWriteData 写数据:对数据存储的写入(数据库/存储文件的写、
|
||||
// 记忆与索引类工具的写动作)。
|
||||
SensitiveWriteData SensitiveOp = "写数据"
|
||||
// SensitiveOutboundMessage 对外发消息:把内容投出本进程的发送类工具
|
||||
// (邮件/短信/渠道消息/Webhook)。
|
||||
SensitiveOutboundMessage SensitiveOp = "对外发消息"
|
||||
// SensitiveSecretAccess 访问密钥与隐私:读写凭据、密钥材料与隐私数据
|
||||
// (`.env` / `models.json` / 渠道 token / SSH 私钥 / 本地数据库)。
|
||||
SensitiveSecretAccess SensitiveOp = "访问密钥与隐私"
|
||||
// SensitiveConfigChange 改配置与权限:改动实例配置与权限边界
|
||||
// (模型与渠道配置、MCP/插件清单、策略与角色定义)。
|
||||
SensitiveConfigChange SensitiveOp = "改配置与权限"
|
||||
// SensitiveNetwork 网络访问。只有会改变外部状态的写方法算敏感:
|
||||
// GET/HEAD/OPTIONS 属「检索」,可自主(design/11 §4)。
|
||||
SensitiveNetwork SensitiveOp = "网络访问"
|
||||
)
|
||||
|
||||
// SensitiveOps 按 design/11 §4 的顺序列出**全部 7 类**敏感操作。它是该清单在
|
||||
// 代码里的对账口径:测试断言 7 类齐全、且每一类都至少有一条结构化判据能命中。
|
||||
var SensitiveOps = []SensitiveOp{
|
||||
SensitiveExecCommand,
|
||||
SensitiveWriteFile,
|
||||
SensitiveWriteData,
|
||||
SensitiveOutboundMessage,
|
||||
SensitiveSecretAccess,
|
||||
SensitiveConfigChange,
|
||||
SensitiveNetwork,
|
||||
}
|
||||
|
||||
// sensitiveSummaryRunes 截断动作摘要:代审小字与审计只需一眼看出「想干什么」,
|
||||
// 同时限制进入前端/日志的文本块大小(也降低把长参数里的秘密整段搬进日志的机会)。
|
||||
const sensitiveSummaryRunes = 80
|
||||
|
||||
// SensitiveCall 判定一次工具调用是否属敏感操作(design/11 §4)。
|
||||
//
|
||||
// 判定完全结构化:只看工具名 + 参数,绝不由模型文本推断(design/11 §0 红线)。
|
||||
// 返回操作类别、动作摘要与是否敏感。清单与项目实际的工具面对应如下:
|
||||
// 判定完全结构化:只看工具名 + 参数(以及参数里的路径落点),绝不由模型文本
|
||||
// 推断(design/11 §0 红线)。判据分三层:
|
||||
//
|
||||
// 1. 具名工具的参数判据(`file` / `http` / `terminal`,见下表);
|
||||
// 2. 按**路径落点**细化 `file` 的类别(配置件 / 数据存储件 / 密钥件,
|
||||
// 见 sensitive_paths.go);
|
||||
// 3. 其余工具(MCP / 插件 / 生态新增)按**工具名**的名词+动词词族判定,
|
||||
// 并对「存储类工具 + 写动作参数」补一条参数判据(见 sensitive_names.go),
|
||||
// 判不出即非敏感。
|
||||
//
|
||||
// 具名工具的参数判据:
|
||||
//
|
||||
// terminal → 执行命令(命令原文进摘要)
|
||||
// file (action=write) → 修改文件(路径进摘要)
|
||||
// file (action=write) → 落点是配置件 ⇒ 改配置与权限;落点是数据存储件 ⇒
|
||||
// 写数据;其余 ⇒ 修改文件(路径进摘要)
|
||||
// file (read/list/search) → 落点是密钥件或数据存储件 ⇒ 访问密钥与隐私;
|
||||
// 其余 ⇒ 非敏感(design/11 §4「读文件可自主」)
|
||||
// http (method=POST) → 网络访问(URL 进摘要)
|
||||
//
|
||||
// 其余情况一律**不敏感**:file 的 read/list/search、http 的 GET/HEAD/OPTIONS、
|
||||
// knowledge / web-search / web-fetch(检索)、calculator / datetime / text /
|
||||
// json / markdown / random(纯计算)、vision / ocr / session_read(只读)——
|
||||
// 与 design/11 §4「非敏感(读文件 / 检索 / 查记忆)可自主」一致。
|
||||
// 其余情况一律**不敏感**:knowledge / web-search / web-fetch(检索)、
|
||||
// calculator / datetime / text / json / markdown / random(纯计算)、
|
||||
// vision / ocr / session_read(只读)——与 design/11 §4「非敏感(读文件 /
|
||||
// 检索 / 查记忆)可自主」一致。
|
||||
//
|
||||
// 未知工具(含 MCP 工具)同样不敏感:工具面已由 registry 的策略闸门管理,
|
||||
// 这里不新增「未知即拦截」的硬边界,否则任何新工具都会在主人会话里被拦死。
|
||||
// 判不准的新工具同样不敏感:工具面已由 registry 的策略闸门管理,这里不新增
|
||||
// 「未知即拦截」的硬边界,否则任何新工具都会在主人会话里被拦死。判据刻意保守
|
||||
// 的理由同上:误判把正常工具拉进代审会污染审计与前端小字。
|
||||
func SensitiveCall(name string, args map[string]any) (SensitiveOp, string, bool) {
|
||||
switch name {
|
||||
case "terminal":
|
||||
return SensitiveExecCommand, summarize("terminal", argString(args, "command")), true
|
||||
case "file":
|
||||
if argString(args, "action") != "write" {
|
||||
return "", "", false
|
||||
}
|
||||
return SensitiveWriteFile, summarize("file write", argString(args, "path")), true
|
||||
return fileCall(args)
|
||||
case "http":
|
||||
method := strings.ToUpper(strings.TrimSpace(argString(args, "method")))
|
||||
if method == "" || method == "GET" || method == "HEAD" || method == "OPTIONS" {
|
||||
return "", "", false
|
||||
return httpCall(args)
|
||||
}
|
||||
if op, ok := matchNameRule(name); ok {
|
||||
return op, summarize(name, argTarget(args)), true
|
||||
}
|
||||
if op, ok := matchStoreAction(name, args); ok {
|
||||
return op, summarize(name, argTarget(args)), true
|
||||
}
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// fileCall 按 `file` 的动作与**落点路径**给出类别(design/11 §4 的四类判据:
|
||||
// 修改文件 / 写数据 / 改配置与权限 / 访问密钥与隐私)。
|
||||
//
|
||||
// 未知或缺失 action 保持「不敏感」:真实工具会直接拒绝这种调用(
|
||||
// plugins/tools/io/file.go 的 switch 落到 default),它不是一次真实的副作用。
|
||||
func fileCall(args map[string]any) (SensitiveOp, string, bool) {
|
||||
action := argString(args, "action")
|
||||
target := argString(args, "path")
|
||||
switch action {
|
||||
case "write":
|
||||
switch {
|
||||
case isConfigArtifact(target):
|
||||
return SensitiveConfigChange, summarize("file write", target), true
|
||||
case isDataStoreArtifact(target):
|
||||
return SensitiveWriteData, summarize("file write", target), true
|
||||
default:
|
||||
return SensitiveWriteFile, summarize("file write", target), true
|
||||
}
|
||||
return SensitiveNetwork, summarize("http "+method, argString(args, "url")), true
|
||||
case "read", "list", "search":
|
||||
if isSecretArtifact(target) || isDataStoreArtifact(target) {
|
||||
return SensitiveSecretAccess, summarize("file "+action, target), true
|
||||
}
|
||||
return "", "", false
|
||||
default:
|
||||
return "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
// httpCall 按 HTTP 方法给出类别(design/11 §4):只有会改变外部状态的写方法
|
||||
// 算敏感,GET/HEAD/OPTIONS 与缺省方法(=GET)属检索,可自主。
|
||||
func httpCall(args map[string]any) (SensitiveOp, string, bool) {
|
||||
method := strings.ToUpper(strings.TrimSpace(argString(args, "method")))
|
||||
if method == "" || method == "GET" || method == "HEAD" || method == "OPTIONS" {
|
||||
return "", "", false
|
||||
}
|
||||
return SensitiveNetwork, summarize("http "+method, argString(args, "url")), true
|
||||
}
|
||||
|
||||
// summarize 拼接「动作 + 目标」摘要并按 sensitiveSummaryRunes 截断(不切开
|
||||
// 多字节字符);目标为空时只返回动作。
|
||||
func summarize(action, target string) string {
|
||||
|
||||
Reference in new issue
Block a user