security

package
v0.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2026 License: None detected not legal advice Imports: 0 Imported by: 0

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(引擎层)同时引用,不引入循环依赖.

核心能力:

  1. SecretGuard - 写入前扫描内容,检测 API key,密码等敏感信息
  2. AuditSink - 操作审计落地接口(本地文件,远端等实现由外部注入)

升华改进(ELEVATED): 早期设计只保护 TeamMem 同步路径, 设计出发点是"防止秘密被同步给团队成员". 我们把安全边界翻转:默认全路径扫描,调用方显式豁免(而不是显式开启). 这符合"安全默认,显式降级"原则,与 Go httptest.NewServer 等标准库哲学一致. 替代方案:<路径白名单--只扫描指定路径> - 否决原因:遗漏路径比豁免路径危险得多.

升华改进(ELEVATED): 早期设计用 ANT_KEY_PFX = ['sk','ant','api'].join('-') 在运行时 拼接 Anthropic key 前缀,目的是防止 JS bundle 被 gitleaks 扫描时误报. Go 编译产物无此问题(二进制不含源码),直接写明前缀即可,反而更易理解和审计. 替代方案:<保留运行时拼接> - 否决原因:在 Go 库中无意义,徒增阅读成本.

Index

Constants

View Source
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

View Source
var ErrContentTooLarge = fmt.Errorf("security: content exceeds max scan size (%d bytes)", MaxScanBytes)

ErrContentTooLarge 在内容超过 MaxScanBytes 时由 Scan 返回. 调用方可以选择:

  1. 拒绝写入(保守)
  2. 放行并写入审计警告(宽松)

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) Close

func (c *CompositeAuditSink) Close() error

Close 关闭所有子 Sink.

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

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 返回内置规则集的副本. 外部可以在此基础上追加自定义规则(仓储,金融等行业特有格式).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL