Files
HomeAgent/internal/kernelmod/agent
JianFeeeee 1d007e1d49 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)。
2026-10-30 23:54:08 +08:00
..