Documentation
¶
Overview ¶
Package security provides security primitives: append-only audit logging (AuditSink) and secret detection (SecretGuard). Both are consumer-pluggable so the engine ships with safe default implementations (LocalAuditSink, basic SecretGuard) while SaaS / enterprise deployments can inject DB-backed or remote equivalents.
Consumer integration surfaces fall into three shapes. For the full taxonomy see `docs/api-reference.md` section "API 消费形态 / API Consumption Patterns".
Synchronous callback — form 3 (both interfaces this package exposes):
- AuditSink.Write(entry): engine calls synchronously per auditable operation; the sink persists to its backend and returns error on failure. Composable via CompositeAuditSink (local + remote fan-out).
- SecretGuard.Scan(content): engine calls synchronously before tool input / output crosses a trust boundary; returns detected secrets so the caller can redact or deny.
Package security 提供安全原语: 仅追加审计日志 (AuditSink) + 秘密检测 (SecretGuard). 两者都面向消费者可插拔: 引擎自带安全的默认实现 (LocalAuditSink, 基础 SecretGuard), SaaS / 企业部署可注入 DB / 远端 等价实现.
消费者接入面分三种形态. 完整分类见 `docs/api-reference.md` "API 消费形态 / API Consumption Patterns" 章节.
同步回调 (callback) —— 形态三 (本包两个接口都是这种):
- AuditSink.Write(entry): 引擎对每次需审计的操作同步调, sink 持久化 到后端, 失败返 error. 经 CompositeAuditSink 可组合 (本地 + 远端 fan-out).
- SecretGuard.Scan(content): 引擎在工具输入/输出跨越信任边界前同步调; 返回检测到的 secrets 让调用方脱敏或拒绝.
Package security 提供引擎层的安全能力:秘密扫描与审计日志.
设计定位:
这是一个零外部依赖的基础包,被 pkg/tools/builtin(工具层) 和 pkg/engine(引擎层)同时引用,不引入循环依赖.
核心能力:
- SecretGuard - 写入前扫描内容,检测 API key,密码等敏感信息
- AuditSink - 操作审计落地接口(本地文件,远端等实现由外部注入)
升华改进(ELEVATED): 早期设计只保护 TeamMem 同步路径, 设计出发点是"防止秘密被同步给团队成员". 我们把安全边界翻转:默认全路径扫描,调用方显式豁免(而不是显式开启). 这符合"安全默认,显式降级"原则,与 Go httptest.NewServer 等标准库哲学一致. 替代方案:<路径白名单--只扫描指定路径> - 否决原因:遗漏路径比豁免路径危险得多.
升华改进(ELEVATED): 早期设计用 ANT_KEY_PFX = ['sk','ant','api'].join('-') 在运行时 拼接 Anthropic key 前缀,目的是防止 JS bundle 被 gitleaks 扫描时误报. Go 编译产物无此问题(二进制不含源码),直接写明前缀即可,反而更易理解和审计. 替代方案:<保留运行时拼接> - 否决原因:在 Go 库中无意义,徒增阅读成本.
Index ¶
Constants ¶
const MaxScanBytes = 512 * 1024 // 512 KB
MaxScanBytes 是单次内容扫描的默认最大字节数(512 KB).
精妙之处(CLEVER): 早期设计中缺少内容大小限制--这是一个 bug. 如果 Agent 写入 100MB 的日志文件,45 条正则会在其上跑完整 pass, 理论上耗时可达秒级(Go regexp 是 O(n*m) 最坏情况,m 是规则数). 512KB 是合理上限:足以覆盖绝大多数配置文件,源码,日志片段; 超过此限制的文件(数据导出,二进制 base64 等)通常不含人工录入的 key. 超限时返回 ErrContentTooLarge,调用方决定是拒绝还是放行并记录警告.
Variables ¶
var ErrContentTooLarge = fmt.Errorf("security: content exceeds max scan size (%d bytes)", MaxScanBytes)
ErrContentTooLarge 在内容超过 MaxScanBytes 时由 Scan 返回. 调用方可以选择:
- 拒绝写入(保守)
- 放行并写入审计警告(宽松)
Functions ¶
func MatchLabels ¶
func MatchLabels(matches []SecretMatch) []string
MatchLabels 将 SecretMatch 列表转换为可读标签列表(用于错误消息).
func MatchRuleIDs ¶
func MatchRuleIDs(matches []SecretMatch) []string
MatchRuleIDs 将 SecretMatch 列表转换为规则 ID 列表(用于审计日志).
Types ¶
type AuditEntry ¶
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"`
}
AuditEntry 是一条操作审计记录.
设计原则:
- 所有字段均为基础类型,可直接 JSON 序列化
- Resource 不做路径哈希(与早期实现的 filePathHash 不同)-- 本地审计日志没有隐私泄露风险,完整路径更便于排查
- Extra 字段支持跨行业扩展,不修改核心 schema
type AuditSink ¶
type AuditSink interface {
// Write 写入一条审计记录.
// 实现必须是线程安全的(多个 goroutine 可能并发写入).
// 返回 error 表示写入失败,调用方可以决定是否重试或降级.
Write(entry AuditEntry) error
// Close 刷新缓冲区并释放资源.
// 对于同步写入实现,Close 可以是空操作.
Close() error
}
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 CompositeAuditSink ¶
type CompositeAuditSink struct {
// contains filtered or unexported fields
}
CompositeAuditSink 将多个 AuditSink 叠加.
升华改进(ELEVATED): 生产环境通常需要多路输出:
- 本地 JSONL 文件(合规存档)
- 远端日志服务(实时告警)
- 数据库(可查询审计)
CompositeAuditSink 让它们各司其职,无需每个实现内部做多路分发. 替代方案:<每个 Sink 内部做多路分发> - 否决原因:重复劳动,违反单一职责.
func NewCompositeAuditSink ¶
func NewCompositeAuditSink(sinks ...AuditSink) *CompositeAuditSink
NewCompositeAuditSink 创建多路复合 AuditSink.
func (*CompositeAuditSink) Write ¶
func (c *CompositeAuditSink) Write(entry AuditEntry) error
Write 将记录写入所有子 Sink,收集所有错误. 精妙之处(CLEVER): 即使某个 Sink 写入失败,其余 Sink 仍然继续写入. 审计不应因为单个后端故障而整体失败. 如果有多个错误,只返回最后一个(调用方通常只关心"是否有错误").
type DefaultSecretGuard ¶
type DefaultSecretGuard struct {
// ExemptPaths 是豁免路径列表(路径前缀,如 "testdata/","/tmp/fixtures/").
// 路径匹配任意一个前缀时跳过扫描,直接返回 nil, nil.
// 精妙之处(CLEVER): 用前缀匹配而非 glob,避免引入 glob 解析复杂度.
// 绝大多数豁免场景都是目录前缀(testdata/,fixtures/ 等),前缀足够.
ExemptPaths []string
// MaxBytes 覆盖默认的 MaxScanBytes.0 表示使用默认值.
MaxBytes int
// OnBlocked 是检测到秘密时的回调(可选).
// 由引擎层注入--在 engine.New() 中设置为触发 "secret_scan_blocked" Observer 事件.
// 工具层(FileWrite/FileEdit)不需要直接依赖 Observer,避免循环导入.
//
// 精妙之处(CLEVER): 回调而非直接依赖 EventObserver 接口--
// security 包不导入 engine 包,engine 包在构造时将 observer.Event 注入为回调.
// 这样 security 包保持零引擎依赖,可以被 pkg/memory 等其他包直接使用.
// 替代方案:<security 包导入 engine.EventObserver 接口>
// - 否决原因:形成循环导入(engine 包已经导入 security 包).
OnBlocked func(path string, matches []SecretMatch)
// contains filtered or unexported fields
}
DefaultSecretGuard 是默认的秘密扫描实现.
零值不可用,必须通过 NewDefaultSecretGuard 或 NewSecretGuardWithRules 创建.
func NewDefaultSecretGuard ¶
func NewDefaultSecretGuard() *DefaultSecretGuard
NewDefaultSecretGuard 创建使用内置 45 条规则的默认扫描器. 无豁免路径,对所有路径生效.
func NewSecretGuardWithRules ¶
func NewSecretGuardWithRules(rules []SecretRule) *DefaultSecretGuard
NewSecretGuardWithRules 创建使用自定义规则集的扫描器. 典型用法:在内置规则基础上追加行业特有规则.
guard := security.NewSecretGuardWithRules(
append(security.BuiltinRules(), myInternalRules...)
)
func (*DefaultSecretGuard) Redact ¶
func (g *DefaultSecretGuard) Redact(content string) string
Redact 将内容中匹配的秘密替换为 [REDACTED]. 超过 MaxBytes 的内容直接原样返回(不脱敏)--脱敏失败比暴露原文更可接受.
func (*DefaultSecretGuard) Scan ¶
func (g *DefaultSecretGuard) Scan(path, content string) ([]SecretMatch, error)
Scan 扫描内容中的敏感信息. path 用于豁免检查;content 超过 MaxBytes 时返回 ErrContentTooLarge.
type NoopAuditSink ¶
type NoopAuditSink struct{}
NoopAuditSink 是空实现--不记录任何审计信息. 用于:测试场景,明确不需要审计的嵌入式场景.
精妙之处(CLEVER): 与 NoopSecretGuard 同理,显式 Noop 比 nil 更安全. 调用方传入 security.NoopAuditSink{} 而非 nil,不会有 nil 解引用问题.
func (NoopAuditSink) Close ¶
func (NoopAuditSink) Close() error
func (NoopAuditSink) Write ¶
func (NoopAuditSink) Write(AuditEntry) error
type NoopSecretGuard ¶
type NoopSecretGuard struct{}
NoopSecretGuard 是空实现--不扫描任何内容,直接放行. 用于测试场景或明确关闭扫描的场景(必须显式声明,不能忘记).
精妙之处(CLEVER): 与 NoopObserver 同理--显式 Noop 比 nil 检查更安全. 调用方传入 security.NoopSecretGuard{} 而非 nil,避免 nil 解引用 panic.
func (NoopSecretGuard) Redact ¶
func (NoopSecretGuard) Redact(content string) string
func (NoopSecretGuard) Scan ¶
func (NoopSecretGuard) Scan(_, _ string) ([]SecretMatch, error)
type SecretGuard ¶
type SecretGuard interface {
// Scan 扫描 content 中的敏感信息.
// path 用于豁免规则判断(ExemptPaths 前缀匹配).
// 返回命中的规则列表;返回 ErrContentTooLarge 表示内容超限;
// 内容安全时返回 nil, nil.
Scan(path, content string) ([]SecretMatch, error)
// Redact 将 content 中匹配到的秘密替换为 [REDACTED],返回脱敏后的字符串.
// 用于日志输出,错误消息展示时的保护性处理.
// 不返回 error--脱敏本身不应阻断流程;超限内容直接原样返回(不脱敏).
Redact(content string) string
}
SecretGuard 是秘密扫描的核心接口.
升华改进(ELEVATED): 接口设计同时服务三种场景:
- CLI:默认注入 DefaultSecretGuard,开箱即用
- SDK(Go 嵌入):上层应用可传入 NewSecretGuardWithRules(customRules) 添加行业规则
- HTTP API/SaaS:租户级别注入不同 guard,实现隔离
替代方案:<直接在工具中 hardcode 规则扫描> - 否决原因:无法扩展,无法测试.
Shape: synchronous callback. Engine calls Scan synchronously before tool input / output crosses a trust boundary; consumer implementation returns detected secrets so the caller can redact or deny.
形态: 同步回调. 引擎在工具输入/输出跨越信任边界前同步调 Scan; 消费者实现 返回检测到的 secrets 让调用方脱敏或拒绝.
type SecretMatch ¶
type SecretMatch struct {
// RuleID 是触发的规则 ID,如 "github-pat".
RuleID string
// Label 是人类可读的标签,如 "GitHub PAT".
// 用于错误消息和审计日志(永远不包含实际秘密值).
Label string
}
SecretMatch 是一次扫描命中的结果.
精妙之处(CLEVER): 故意不包含实际匹配内容(matched text). 只返回"发现了什么类型的秘密",不返回"秘密的具体值". 这保证了即使日志系统出现问题,秘密值也不会意外泄露. 早期设计同样遵守此原则,我们继承并明确记录原因.
type SecretRule ¶
type SecretRule struct {
// ID 是规则的唯一标识,对应 gitleaks rule ID(kebab-case).
// 例:"github-pat","aws-access-token"
ID string
// Source 是正则表达式源字符串(Go re2 语法).
// 精妙之处(CLEVER): 存储 source 而非编译后的 *regexp.Regexp,
// 支持按需惰性编译(只在首次扫描时编译),也方便序列化和热更新.
Source string
// Flags 是正则 flag,通常为空(大多数规则区分大小写).
// 仅当规则需要大小写不敏感时设置为 "i".
Flags string
}
SecretRule 描述一条秘密检测规则. 规则 ID 来自 gitleaks(https://github.com/gitleaks/gitleaks,MIT 协议), 只使用高置信度,前缀可辨识的规则. 通用关键词上下文规则(如 api_key=xxx)被排除--误报率过高,不适合阻断写入.
func BuiltinRules ¶
func BuiltinRules() []SecretRule
BuiltinRules 返回内置规则集的副本. 外部可以在此基础上追加自定义规则(仓储,金融等行业特有格式).