mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 09:17:31 +00:00
fix(security): 工具策略闸真的接上了 —— 根因是依赖图少一条边,不是少一行赋值
## 缺陷
生产日志实证:`[security] module enabled (31 variables)`、`[device] ready:
34 tools, security=true`,691 个日志文件里 `denied by policy` 命中 **0 次**。
`executeToolCallInner` 里的策略闸读 `a.security`,而它**永远是 nil**:
codegraph_callers("SetSecurity") → 4 个调用方,全是 security_toolgate_test.go
全仓唯一给 a.security 赋值的行:security_profile.go(就在 SetSecurity 体内)
buildAgent 完全不读 k.Security;AgentConfig 里也没有 Security 字段
## 为什么判据全绿而生产是坏的
`security_toolgate_test.go` 的 4 条判据**自己调了 a.SetSecurity(...)** ——
它们把缺失的那根线补上了,所以永远绿。测的是「接线之后行为对」,
不是「生产有没有接线」。这是「机制 ≠ 接线」最纯粹的样本。
## 根因:模块边界没连上,不是少一行
`kernelmod/agent` 的 Requires() **没有声明 FacSecurity**。依赖图因此没有
理由把 security 的产物交给 agent —— 只有声明了的 device 拿到了。
即使不声明,security 也在第 1 层、agent 在第 4 层,`ks.Security` 恰好已被
填好,运行期照样能读到。**这正是它藏了整轮发布的原因**:那个"恰好"是偶然
(security 只依赖 config),不是设计。一旦 security 将来加了自己的依赖,
图不会拦,只会静默给出 nil。
所以只补 `agent.SetSecurity(k.Security)` 是错的:那会让代码读一个图不保证的
字段。必须同时改三处。
## 改动
① kernelmod/agent: Requires 加 FacSecurity(让图知道这条边)
② AgentConfig 加 Security 字段;New() 写 a.security;buildAgent 传 k.Security
③ Init 里 ks.Security == nil 时**响亮失败**(与 device 模块对 ks.Security
的检查同型)—— 不能把 nil 一路传下去变成静默放行
④ 子 agent 继承:resident / rtchild 各自 New(AgentConfig{...}),原先从构造起
a.security 就是 nil。它们是**最需要闸门**的执行体(驻留子长期无人盯、
rtchild 通话中一边听一边执行工具),却恰恰完全没有闸门。
## 判据(全部通过变异验证,删掉修复必红)
新增 internal/kernelmod/all/security_wiring_test.go(钉**装配**,不是钉注入):
| 判据 | 变异 |
|---|---|
| TestAgentDeclaresSecurityDependency | 从 Requires 删 FacSecurity ⇒ 红 |
| TestAgentAssemblyWiresSecurity | buildAgent 传 nil ⇒ 红;New() 不写 a.security ⇒ 红 |
| TestSubAgentsInheritSecurity | 删 resident/rtchild 的 Security ⇒ 红 |
| TestAgentAssemblyRefusesNilSecurity | 删 Init 的 nil 检查 ⇒ 红 |
`TestAgentAssemblyWiresSecurity` 走**真实登记表装配**(真 SQLite / 真配置库 /
真 20 模块 6 层),并对一个**未注册**的工具名配黑名单后调真实 `ExecuteTool`:
闸门生效 ⇒ "not permitted";闸门失效 ⇒ "does not exist"。两者文本不同,
无从混淆,因此不需要注册探针工具。
## 实测
生产形态探针(配置写入 `core.security.tool_deny=cmd_run` 后启动真实二进制):
[security] global policy applied: deny=[cmd_run] allow=[] sandbox=false
Agent.ExecuteTool("cmd_run") → denied (Tool cmd_run is not permitted:
tool "cmd_run" is blocked by security policy (denylist entry "cmd_run"))
分层未变(3+10+3+1+2+1 = 20 模块 6 层,digest 14b4f6f7),
build / vet / go test ./... / -race(kernelmod/all + agent/core + security) 全绿。
## 顺带
gofmt 漂移清零(原 1 个 rtchild_route_test.go)。
This commit is contained in:
@ -369,6 +369,31 @@ type AgentConfig struct {
|
||||
// nil 或空 = **完整授权**(默认);非空 = 白名单,只允许列出的输出通道。
|
||||
// 父 agent 创建驻留子时用它收窄子的输出能力。
|
||||
AllowedOutputs []string
|
||||
// Security 是安全防火墙运行时(security 模块产出,可 nil)。
|
||||
//
|
||||
// ## 为什么它是**装配期字段**而不是事后 Set
|
||||
//
|
||||
// 这个字段曾经只能靠 `SetSecurity()` 注入,而生产装配链里**没有任何
|
||||
// 地方调用它** —— 唯一的调用方是 4 条测试。于是 `securityDecisionFor`
|
||||
// 在生产中恒走 nil 分支、恒放行,而策略表本身完全正确:这是
|
||||
// 「机制 ≠ 接线」的教科书样本(GUI 无拒绝记录,看起来像"没人违规")。
|
||||
//
|
||||
// ★ 根因在**依赖图**,不在这行赋值:`agent.Requires()` 没声明
|
||||
// `FacSecurity`,图就没有理由把 security 的产物交给 agent(只有声明了
|
||||
// 的 device 拿到了)。所以修复必须同时改三处:
|
||||
// ① kernelmod/agent 的 Requires() 加 FacSecurity
|
||||
// ② 这里加字段 + New() 赋值 + buildAgent 传 k.Security
|
||||
// ③ 装配级判据(删 ① 或 ② 必须红)
|
||||
// 只做 ②不做 ① 会让代码读 `k.Security` 而图不保证它已就绪 ——
|
||||
// 今天恰好对(security 在第 1 层)是运气,不是设计。
|
||||
//
|
||||
// ## 子 agent 必须继承
|
||||
//
|
||||
// 驻留子 / rtchild 各自 `New(AgentConfig{...})`,不继承就等于子 agent
|
||||
// 完全没有闸门(而它们恰是"能指挥设备、能写记忆"的高风险执行体)。
|
||||
// 见 resident.go / rtchild.go 里 `Security: a.security`。
|
||||
Security *security.Runtime
|
||||
|
||||
// IsRealtime 标记本 agent 是 **rtchild**(实时子 agent,plan.md 十一)。
|
||||
//
|
||||
// 置 true 后:
|
||||
@ -539,8 +564,11 @@ func New(cfg AgentConfig) *Agent {
|
||||
embeddingProvider: cfg.EmbeddingProvider,
|
||||
embeddingError: cfg.EmbeddingError,
|
||||
fusionCfg: cfg.FusionCfg,
|
||||
noMergeMarkers: make(map[string]int),
|
||||
lastInput: make(map[string]time.Time),
|
||||
// ★ 安全运行时在**构造期**落位(不再依赖事后 SetSecurity)。
|
||||
// 为 nil 表示调用方显式不启用(独立工具/单测),不是"忘了接线"。
|
||||
security: cfg.Security,
|
||||
noMergeMarkers: make(map[string]int),
|
||||
lastInput: make(map[string]time.Time),
|
||||
}
|
||||
|
||||
// 输入路由:inputch 是可分配资源,划给某个 agent 后输入**只**流向那个 agent
|
||||
|
||||
Reference in New Issue
Block a user