package security // audit.go - 审计日志的核心类型与接口. // // AuditSink 定义在 pkg/security 而非 pkg/engine,原因: // pkg/tools/builtin 需要同时引用 SecretGuard 和 AuditSink, // 而 pkg/engine 已经被 pkg/tools/builtin 作为上层引用, // 将 AuditSink 放在 pkg/security 避免了循环导入. // // 升华改进(ELEVATED): 早期设计没有本地持久化审计日志,所有操作数据上报 Statsig. // 离线/嵌入式/合规场景完全无法审计.我们用接口分离关注点: // - AuditSink 接口定义"写入行为" // - LocalAuditSink(在 pkg/engine/audit_local.go)提供本地 JSONL 实现 // - 远端上报实现可由外部注入(零依赖约束不被打破) // // 替代方案:<直接写日志到 logger> - 否决原因:日志是给人看的,审计是给系统消费的, // 格式/查询/合规要求完全不同. import "time" // AuditEntry 是一条操作审计记录. // // 设计原则: // - 所有字段均为基础类型,可直接 JSON 序列化 // - Resource 不做路径哈希(与早期实现的 filePathHash 不同)-- // 本地审计日志没有隐私泄露风险,完整路径更便于排查 // - Extra 字段支持跨行业扩展,不修改核心 schema type AuditEntry struct { // 操作上下文 SessionID string `json:"session_id"` // 会话唯一 ID(可为空) TurnNumber int `json:"turn_number"` // 对话轮次(0 表示未知) Timestamp time.Time `json:"timestamp"` // 操作发生时间(UTC) // 操作信息 ToolName string `json:"tool_name"` // 工具名称,如 "Write","Edit","Bash" Operation string `json:"operation"` // 操作类型:"write"/"edit"/"read"/"execute" Resource string `json:"resource"` // 资源标识(文件路径,URL,数据库表名等) // 操作者(多用户/SaaS 场景使用,单用户可为空) ActorID string `json:"actor_id,omitempty"` // 审计结果 Outcome string `json:"outcome"` // "allowed"/"blocked"/"error" Reason string `json:"reason,omitempty"` // 拦截/错误原因,如 "secret_detected:github-pat" // 可扩展字段(跨行业专用). // 仓储场景示例:{"sku": "SKU-001", "qty_before": "100", "qty_after": "80"} // 医疗场景示例:{"patient_id_hash": "abc123", "phi_type": "diagnosis"} // 金融场景示例:{"account_hash": "xyz", "amount": "50000", "currency": "CNY"} Extra map[string]string `json:"extra,omitempty"` } // AuditSink 是审计落地的核心接口. // // 升华改进(ELEVATED): 只写接口,不暴露读取/修改方法. // 这样可以将来接入 WORM 存储(Write Once Read Many),满足合规要求. // 任何实现都应该是 append-only 的. // // Shape: synchronous callback. Engine calls Write(entry) synchronously per // auditable operation; the sink persists to its backend and returns error // on failure. // // 形态: 同步回调. 引擎对每次需审计的操作同步调 Write(entry), sink 持久化 // 到后端, 失败返 error. type AuditSink interface { // Write 写入一条审计记录. // 实现必须是线程安全的(多个 goroutine 可能并发写入). // 返回 error 表示写入失败,调用方可以决定是否重试或降级. Write(entry AuditEntry) error // Close 刷新缓冲区并释放资源. // 对于同步写入实现,Close 可以是空操作. Close() error } // NoopAuditSink 是空实现--不记录任何审计信息. // 用于:测试场景,明确不需要审计的嵌入式场景. // // 精妙之处(CLEVER): 与 NoopSecretGuard 同理,显式 Noop 比 nil 更安全. // 调用方传入 security.NoopAuditSink{} 而非 nil,不会有 nil 解引用问题. type NoopAuditSink struct{} func (NoopAuditSink) Write(AuditEntry) error { return nil } func (NoopAuditSink) Close() error { return nil } // CompositeAuditSink 将多个 AuditSink 叠加. // // 升华改进(ELEVATED): 生产环境通常需要多路输出: // - 本地 JSONL 文件(合规存档) // - 远端日志服务(实时告警) // - 数据库(可查询审计) // // CompositeAuditSink 让它们各司其职,无需每个实现内部做多路分发. // 替代方案:<每个 Sink 内部做多路分发> - 否决原因:重复劳动,违反单一职责. type CompositeAuditSink struct { sinks []AuditSink } // NewCompositeAuditSink 创建多路复合 AuditSink. func NewCompositeAuditSink(sinks ...AuditSink) *CompositeAuditSink { return &CompositeAuditSink{sinks: sinks} } // Write 将记录写入所有子 Sink,收集所有错误. // 精妙之处(CLEVER): 即使某个 Sink 写入失败,其余 Sink 仍然继续写入. // 审计不应因为单个后端故障而整体失败. // 如果有多个错误,只返回最后一个(调用方通常只关心"是否有错误"). func (c *CompositeAuditSink) Write(entry AuditEntry) error { var lastErr error for _, s := range c.sinks { if err := s.Write(entry); err != nil { lastErr = err } } return lastErr } // Close 关闭所有子 Sink. func (c *CompositeAuditSink) Close() error { var lastErr error for _, s := range c.sinks { if err := s.Close(); err != nil { lastErr = err } } return lastErr }