Exec 安全模型
wesgine 子进程执行安全:进程组管理、输出排空、环境净化与行长保护。
单一 Spawn 原语(INV-EXEC-PROC-01)
生产 Go 一律 proc.Command(ctx, ...):
import "internal/infra/proc"
cmd := proc.Command(ctx, "go", "build", "./...")
内置保障
| 能力 | 说明 |
|---|---|
| 进程组 | Setpgid 建独立进程组 |
| Cancel | SIGKILL 整个进程组 |
| WaitDelay | 给 Wait() 封顶 |
禁止
// ❌ 裸 exec.Command
exec.Command("go", "build")
exec.CommandContext(ctx, "go", "build")
exec.Cmd{Path: "go"}
// ❌ 改写 Cancel / WaitDelay
cmd := proc.Command(ctx, ...)
cmd.Cancel = nil // 恢复原缺陷
cmd.WaitDelay = 0 // 恢复原缺陷
豁免
3 个文件带义务:
| 文件 | 义务 |
|---|---|
tool/host_shell.go | 自设 Cancel + WaitDelay |
adapter/stt/tool.go | 设 SysProcAttr 建组(脱离 ctx) |
进程组清理(INV-EXEC-11)
前台 exec 超时后必须 KillProcessGroup() 清理整个进程组。
ctx 结束
↓ KillProcessGroup()
↓ 整个进程组被杀
↓ 无逃逸的 grandchild
输出排空(INV-EXEC-DRAIN-01)
exec 输出读取必须无条件消费管道直到 EOF。
禁止 bufio.Scanner
// ❌ Scanner 在 1MB max-token 处停止 → 管道无人消费 → 死锁
scanner := bufio.NewScanner(stdout)
for scanner.Scan() { ... }
正确做法
// ✓ ReadSlice 循环 + cappedWriter
stdout 排空超时(INV-EXEC-12)
进程退出后 stdout 排空最多等 5s,超时强制 CloseOutput()。
环境净化
静态模式表(INV-SEC-04)
SanitizeEnv 两层清理:
- 静态模式表匹配(
*_KEY、*_SECRET、*_TOKEN等) SecretRef{source:env}变量名登记(INV-SEC-03)
出网脱敏(INV-SEC-02)
唯一执行点 config.RedactSecrets。
SecretRef.MarshalJSON 吐的是含明文的 storage view——落盘要值。
行长保护(INV-LINE-01)
bufio.Scanner 超限时 Scan() 返回 false——与 EOF 同值。
正确做法
| 场景 | 方案 |
|---|---|
| 内存输入 | bytes.Lines(零拷贝、无上限) |
| 流式输入 | internal/infra/lineio(超限报告,不结束流) |
| 网络 body | 显式 .Buffer(...) + 命名常量 |
截断处置
必须显式选择:
| 策略 | 适用 |
|---|---|
| 致命 | LLM delta(tool_call 参数是 JSON 片段流) |
| 跳过继续 | wesgine 事件(自包含,丢一条只赔一条) |
相关文档
- 治理模型 →
governance-model.md - File I/O →
file-io-model.md - Run 生命周期 →
run-lifecycle.md