Documentation
¶
Overview ¶
Package logredact 日志/审计载荷凭据脱敏:连接串/URL/头中的凭据在落日志前模式化打码。 与 fence(Markdown/HTML 注入卫生)正交——本包打码凭据。
规则表是**公开且可组合**的:Rules() 给出内置表副本,RedactWith / NewRedactor 接受调用方自己的扩展规则。下游各家的凭据形态不一样(厂商前缀族、裸 hex、 任意 scheme 的 URL…),把表关在包内就等于逼下游**重写一整份脱敏器**, 或在后面串第二道——两道顺序敏感、掩码风格还不一致。
Index ¶
- Constants
- func HasTruncatedPayloadMarker(s string) bool
- func IsCredentialKey(key string) bool
- func MaskHint(s string) string
- func MaskHintWith(s string, keep int) string
- func NewPayloadRedactor(fields []string, extra []Rule) func(string) string
- func NewRedactor(extra []Rule) func(string) string
- func Redact(s string) string
- func RedactJSON(data []byte) []byte
- func RedactJSONOpts(data []byte, opts ValueOptions) []byte
- func RedactPayloadValue(v any, fields ...string) any
- func RedactPayloads(s string, fields ...string) string
- func RedactPayloadsWith(s string, fields []string, extra []Rule) string
- func RedactSecrets(s string, secrets ...string) string
- func RedactValue(v any) any
- func RedactValueOpts(v any, opts ValueOptions) any
- func RedactWith(s string, extra []Rule) string
- type KeyMatchMode
- type MaskStyle
- type Masker
- type Rule
- type ValueOptions
Constants ¶
const DefaultHintKeep = 4
DefaultHintKeep 保定位线索掩码默认保留位数(首尾各)。
const HintMinLenRatio = 4
HintMinLenRatio 被掩值长度不足 keep*该倍数 时**全量打码**。
阈值的作用是「留了也别等于没留」:9 字符的 secret 留首尾 4 个 = 8 个字符, 换个字符就复原了——那不是定位线索,是泄露。保留的字符数必须远小于被藏起的 字符数,掩码才真的不可逆。
Variables ¶
This section is empty.
Functions ¶
func HasTruncatedPayloadMarker ¶ added in v0.24.0
HasTruncatedPayloadMarker 报告 s 是否含「截断形态载荷」的裁剪标记—— 即日志管道在脱敏**之前**就把载荷字段切断了。
这是 guardx#120 修法里的告警信号出口:截断形态的标记与闭合形态**不同字** ("<N bytes scrubbed, truncated>"),本函数把它收成单一判定点,告警侧 (审计扫描、落盘前检查)用它,别各处手写字符串匹配把信号写散。
注意语义边界:本函数判定的是「文本里有没有截断标记」,前提是文本已经 过本包的载荷裁剪;对未脱敏的原文它恒返回 false——先裁剪、后探测。
func IsCredentialKey ¶ added in v0.19.0
IsCredentialKey 内置键名判定(结构化键名面):键名是否命中内置凭证词表。 供集成方在自持管线里复用(jumpserver-kit 此前自写 isCredentialKey 兜 authorization/bearer/cookie/key 形态,oagentkit/guardx#51——上游暴露谓词 后可只补自己的增量词表)。
func MaskHint ¶ added in v0.6.0
MaskHint 保留首尾各 DefaultHintKeep 字符的不可逆掩码。
输出形如 "sk-a****mnop":长度**固定**(与原值长度无关), 保留位**封顶**(与原值长度无关),中间恒为固定长度的星号。
长度不足时返回全量打码 "****"(理由见 HintMinLenRatio)。 空串返回空串。
func MaskHintWith ¶ added in v0.6.0
MaskHintWith 保留首尾各 keep 字符的不可逆掩码。
keep<=0 取 DefaultHintKeep。被掩值长度不足 keep*HintMinLenRatio 时全量打码。 空串返回空串。
func NewPayloadRedactor ¶ added in v0.17.0
NewPayloadRedactor 返回可复用的「载荷裁剪 + 形状脱敏」函数(字段集与规则表 在构造时**快照**一次)。
与 NewRedactor 同构:高频路径(日志热路径、每条 tool_call 都过一遍)用它, 别每次调用现编正则。返回的函数是并发安全的(只读快照,不改共享状态)。
func NewRedactor ¶ added in v0.6.0
NewRedactor 返回一个可复用的脱敏函数(内置规则 + 扩展规则)。
并集顺序与 RedactWith 同一契约(guardx#75):先内置、后扩展,快照里的顺序 就是应用顺序;扩展规则见已掩文本、依赖内置保留的结构锚点,详见 RedactWith。
规则表在构造时**快照**一次:调用方之后改自己的切片,不影响已构造的脱敏器。 返回的函数是并发安全的(只读规则表,不改共享状态)。
高频路径(日志热路径、每条 tool_call 都过一遍)用这个,别每次重建规则表。
func RedactJSON ¶ added in v0.18.0
RedactJSON 对 JSON 载荷做结构化脱敏,输出恒为合法 JSON:
- 合法 JSON:解码(json.Number 保数字字面量)→ 键感知递归打码 (凭证词键的**整值**打码为 "****",字符串值过文本规则兜内嵌连接串/ JWT/Bearer)→ 重新序列化。键的声明序经 map 归一后按字母序输出—— 打码保的是**结构合法**,不是字节保形。
- 非法 JSON:回落文本规则(与 Redact 同口径)——不是错误,调用方无须 区分两条路径的返回值。
场景:结构化日志行/事件载荷在采集侧要过 JSON 解析器(Loki/ELK/事件面), 文本规则打码后的 `"api_key=****}` 会让整行降级为 raw 文本、字段索引全丢 ——排查方向被带偏到采集管道(oagentkit/guardx#44)。
func RedactJSONOpts ¶ added in v0.19.0
func RedactJSONOpts(data []byte, opts ValueOptions) []byte
RedactJSONOpts 同 RedactJSON,接受 ValueOptions(自定义键名判定、宿主 词元表与匹配口径、可配打码体、值模式扩展规则等,guardx#176;非法 JSON 回落文本规则时 ExtraRules 同样生效)。
func RedactPayloadValue ¶ added in v0.17.0
RedactPayloadValue 结构化形态:递归把 fields 指定的字段裁剪成「只记长度」的标记, 其余字符串按 RedactValue 的口径做形状脱敏(含凭证词键的整值打码——键感知 走 redactWalk 同一份判定)。
与 RedactPayloads 语义一致(同一份判定表),差别只在形态:它吃已解码的值、 吐已解码的值,可用于「按字段名裁剪后再落库」的结构化载荷管道。
裁剪只作用于**字符串形态**的字段值;非字符串形态(对象/数组/数字)的值 原样保留,只递归下探——被裁掉的是「载荷」,不是「字段所在的那个对象」。
func RedactPayloads ¶ added in v0.17.0
RedactPayloads 把 fields 指定的载荷字段裁剪成「只记长度」的标记,再做形状脱敏。
**顺序由本函数保证**:载荷裁剪先于形状规则(见 payloadMarkerFormat 的说明)。 这正是 issue 里说的「「先裁剪再脱敏」应当由函数内部保证,而不是由调用点的 书写顺序保证」——顺序写反不报错,只是报出来的大小是错的。
fields 是**字段名白名单**(如 "content" / "content_base64" / "body"): 只裁剪这些字段的值,其余原样。刻意不给默认表——「哪些字段算载荷」是 调用方的口径,库里替它猜就等于替它决定什么会落盘。
裁剪只作用于**字符串形态**的字段值(载荷就是字符串:一段 base64、一份内容)。 对象/数组形态的值本函数不动它(那是结构化入口 RedactPayloadValue 的射程)。
截断兜底(guardx#120):文本被日志管道截断、字段值的字符串字面量没有 闭合引号时,冒号后的剩余整段替换成**截断形态**标记("<N bytes scrubbed, truncated>")——残片原文不落盘,截断本身是告警信号(HasTruncatedPayloadMarker)。
高频路径(日志热路径、每条 tool_call 都过一遍)用 NewPayloadRedactor, 别每次重建正则。
func RedactPayloadsWith ¶ added in v0.17.0
RedactPayloadsWith = RedactPayloads + 扩展规则。
扩展规则仍然排在**最后**(与 RedactWith 的口径一致:先内建、后扩展); 载荷裁剪仍在最前。三段的顺序是:载荷裁剪 → 内建规则 → 扩展规则。
func RedactSecrets ¶
RedactSecrets 抹除调用方已知的确切秘密(clone URL 内嵌 token、动态签发的临时 凭据等):与模式化 Redact 互补——Redact 靠规则打码未知形态的凭据,本函数抹除 已知值。与 Masker 相反,高敏感秘密不做令牌、不回填(Masker 只收低敏感拓扑标识)。 长秘密优先替换(防短串是长串前缀时留下尾段泄漏),空串忽略。
func RedactValue ¶
RedactValue 递归脱敏任意 JSON 形态值:凭证词键的整值打码(键感知, jsonredact.go),其余 map/slice 逐字段下探,字符串过 Redact,其余原样。 事件载荷入库前的统一出口——tool_call 参数与 tool_result 文本常含连接串、 webhook key、Bearer 头。
⚠️ v0.14.0 起键感知(oagentkit/guardx#44 同源缺陷):此前只有字符串值过 文本规则——而文本规则靠 `password=xxx` 的**键值形态**定位,解码后的值 "abc123" 早已失去键上下文 ⇒ `{"password": "abc123"}` 原样透传(泄漏)。 键感知打码补上这层:键名命中凭证词表(jsonCredentialKeyRe)即整值打码。
⚠️ guardx#63/#76 起类型化容器同一下探:map[string]string、[]string、 []map[string]any、struct 导出字段经反射递归——此前类型开关只认 any 解码 形态,类型化容器静默漏脱(脱敏器最危险的失效形态)。struct 路径的键名 取 json tag 首段(无 tag/`-` 回落字段名,guardx#130)——与 map 形态的键 同一判定口径。边界:未导出字段反射不可读(原样,请传导出形态); json.Number 特赦(数字字面量保形);[]byte 是二进制不是文本(原样)。 输出为副本,入参不动。
func RedactValueOpts ¶ added in v0.19.0
func RedactValueOpts(v any, opts ValueOptions) any
RedactValueOpts 同 RedactValue,接受 ValueOptions(自定义键名判定等)。
func RedactWith ¶ added in v0.6.0
RedactWith 用内置规则 + 扩展规则脱敏。
**并集顺序契约**(guardx#75:两个消费仓各自用注释钉死的顺序纪律,在此升格 为可依赖的契约,不是实现细节):
- 顺序固定为「先内置、后扩展」,每组内部按声明顺序。需要「扩展先行」的 调用方(形态比内置更精确、要抢在粗规则前保线索)用函数组合实现: 先跑自己的规则,再过 RedactWith(s, nil)——此时扩展先见原文,与内置的 幂等性由调用方自行保证。
- 扩展规则看到的是内置规则处理**之后**的文本:内置打码保结构(scheme、 用户名、键名留着),扩展规则仍能锚定这些结构;但别指望拿到原始凭据—— 内置已命中的部分早成 ****。
- 二次打码幂等:内置掩码是固定字面 ****,对已掩文本重复 Redact 不再改动; 扩展规则也应满足同款幂等(对 **** 无操作的规则天然满足)。
- 定位线索保留:MaskStyleHint 档的线索只作用于捕获组(Group>0),组捕获 失败回退全量打码——既不丢「认得出哪条凭据」的能力,也不留凭据尾巴。
为什么是这个顺序:内置规则的打码**保留结构**("$1://$2:****@" —— scheme 与 用户名留着,只有凭据体被替换),它对同一段文本的定位比扩展规则精确。 先跑内置,扩展规则面对的就是「已经打过码」的文本,既不会把内置已处理的凭据 再脱一次(幂等),也不会让扩展的粗规则先吃掉内置能精确定位的那部分。
扩展规则里 Re 为 nil 的会被跳过(不 panic)——扩展表是调用方给的, 一条写错的规则不该让整条脱敏链路崩掉。
Types ¶
type KeyMatchMode ¶ added in v0.26.0
type KeyMatchMode int
KeyMatchMode ValueOptions.KeyTokens 的匹配口径(oagentkit/guardx#176): 宿主词元表以哪种粒度参与键名判定。零值 = KeyMatchSegment(与内置词表同一 分段口径,默认行为不变)。
三档来源(#176 (b):bianque internal/redact 的两套键名谓词 + 内置表的既有 口径):ExactKey(只打恰等于敏感词的键,保计量字段)、LooseKey(子串命中, 审计面迁移前正则逐字对齐)、内置分段(凭证词作为 [-_] 分隔的段,#132 后 可在键的任意段)。三者互不替代,按 ValueOptions.KeyMode 单选。
const ( // KeyMatchSegment 分段口径(零值默认):词元按内置凭证词的同款段语义 // 命中——词元可出现在键的任意位置(前缀直连或 [-_] 分隔皆可,guardx#132 // 口径),其后只允许 [-_] 分隔的后续段到键尾。词元按字面匹配,不带内置 // 表的复数/缩写展开("token" 不自动吃 "tokens",复数形态请自行列入词元 // 表);也不内置 #52 的 token 计量收窄——#52 是内置词表的计量面契约, // 宿主词元表是显式声明,要保计量字段请走 KeyMatchExact。 KeyMatchSegment KeyMatchMode = iota // KeyMatchExact 精确口径:键名归一(小写 + 去首尾空白)后**恰等于**词元 // 才命中——bianque 事件面的 ExactKey 形态(保计量字段:内置分段表会 // over-mask 的 sort_key/cache_key 等中性键在此口径下不打码)。 // // ⚠️ 这是**收窄模式**(guardx#176 (e) 的安全张力):本口径下内置词表 // **整体退出**,生效的凭证键集合 = 恰等于 KeyTokens 词元的键(∪ // IsCredentialKey 回调)。「键表内的键,值必被打码」的承诺边界由内置表 // 守着——收窄即显式放弃这层兜底:词元表漏了 password/token 这类通用 // 凭证词,它们将**原文落盘**。故本口径只应用于载荷键名完全受宿主控制的 // 场景(事件面的自持计量清单),且**必须显式 opt-in**(零值恒为 // KeyMatchSegment,默认行为不变)。KeyTokens 为空且未配 IsCredentialKey // 时收窄到空集 = 什么都不打码——本包不拦截这个配置(显式即责任), // 调用方要清楚这是字面语义。 KeyMatchExact // KeyMatchSubstring 子串口径:键名归一(小写)后**包含**词元即命中—— // bianque 审计面 LooseKey 的迁移口径(迁移前正则逐字对齐)。比分段宽: // "mypassword1"、"passwordx" 这类分段失配的键同样命中(over-mask 方向, // 与凭证词表吞 bypass 的取舍同款)。并集模式下内置保护不松动。 // 迁移前正则命中面 vs 内置分段命中面的逐键实证差异对比见 // issue176_dualmode_test.go(guardx#176 (e) 的证据义务)。 KeyMatchSubstring )
type MaskStyle ¶ added in v0.6.0
type MaskStyle int
MaskStyle 命中后的替换方式。
const ( // MaskStyleLiteral 按 Repl 字面替换(支持 $1 引用)。内置规则用它**保留结构**: // scheme、用户名、键名留着,只有凭据体变成 ****——处置后的文本仍能看出 // 「是哪一条凭据」。这是默认档。 MaskStyleLiteral MaskStyle = iota // MaskStyleHint 保定位线索的不可逆掩码:保留首尾各 Keep 个字符。 // 用于「全量打码会让人认不出是哪一条凭据」的场景。 // // 与 MaskStyleLiteral 是**不同档**,不是替代关系:前者给的是「认不出」, // 后者给的是「认得出但不还原」。 MaskStyleHint )
type Masker ¶
type Masker struct {
// contains filtered or unexported fields
}
Masker 低敏感掩码+令牌回填器(K8sGPT anonymize 形态,bianque spec 2026-09-14-aiops-uplift-design §3):把目标机拓扑标识(IPv4/IPv6 + 调用方 注册的精确串)在进 LLM 前替换为 «Tn» 令牌,在最终展示面 Restore 回填原名 ——LLM 解释里引用的「主机 «T1»」在报告中还原为真名,可读性与隐私兼得。
分级纪律:高敏感(密码/密钥/连接串/Bearer)不走本机制——那是 Redact 的领域, 删除后永不回填;Masker 只收低敏感拓扑标识,且回填只发生在展示面、不回灌二次 LLM 输入(防掩码词表被 prompt 注入探测绕过)。令牌用全角书名号+序号,难以仿造; Restore 对未知令牌原样保留(不猜、不误回填)。
func (*Masker) AddSecret ¶
AddSecret 注册精确串(会话主机名、实例名等调用方已知标识),返回其令牌。空串忽略。
显式注册的串**全串盲替换**(无边界判定——调用方声明了「这个串就是凭据/ 标识本体」,出现在任何上下文里都该被掩;长短串冲突由注册后的长度优先序 化解,与 logredact.RedactSecrets 同款取舍)。与动态注册(IP 模式命中) 的差别见 Masker.secrets:显式串参与后续精确替换,动态 IP 不参与(#122)。
func (*Masker) Mask ¶
Mask 掩码文本:先精确替换显式注册串(AddSecret),再 IPv6 / IPv4 模式 (动态注册,同址恒同令牌——且该不变量跨调用成立:早前掩过的 IP 再次出现 仍得同令牌,无论其后是否出现过以它为子串的更长 IP,guardx#122)。 结构化数据的双视图入口见 MaskValue(逐字符串值 walk,无序列化往返)。
func (*Masker) MaskValue ¶ added in v0.19.0
MaskValue 递归掩码任意 JSON 形态值(结构化双视图,oagentkit/guardx#54): 键不动、**逐字符串值**走 Mask——替代「json.Marshal → Mask → json.Unmarshal」 的序列化往返。往返形态的正确性依赖两个未经类型化保证的假设(令牌只在 字符串值里 / 字符串替换不破坏 JSON 结构),而同包 Redact 已实证结构破坏 面(guardx#44)——逆向 Restore 的同款结构假设不再被要求。map/slice 下探, 标量原样;输出的 map 为副本(不污染入参)。与 RedactValueOpts 同构的 walk 形态,语义是 Mask(低敏感拓扑标识)不是 Redact(高敏感不回填)。
类型化容器(map[string]string / []string / []map[string]any / struct 导出 字段 / 指针,guardx#101)同样下探——与 #63/#76 给 RedactValue 的 redactReflect 同源:类型开关只认 any 解码形态时,Go 侧类型化容器落 default 原样返回,是「零令牌零报错的静默漏掩」,比它替代的序列化往返 更糟。边界与 redactReflect 一致:json.Number 特赦(数字字面量不是文本)、 []byte 豁免(二进制不是文本)、未导出字段不可读原样保留、输出为副本、 深度上限防自引用环。
func (*Masker) UnmaskValue ¶ added in v0.19.0
UnmaskValue 递归回填任意 JSON 形态值中的令牌(MaskValue 的逆视图):逐 字符串值 Restore,键不动、结构不动——展示面拿到的是已反序列化的结构, 不再有「字符串替换破坏 JSON 文本」的假设面。
type Rule ¶ added in v0.6.0
type Rule struct {
// Name 规则名(诊断用:扩展规则出错时要能指出是哪一条)。
Name string
// Re 命中模式。nil 的规则会被跳过(不 panic)。
Re *regexp.Regexp
// Repl 字面替换串,支持 $1 引用。Style=MaskStyleHint 时被忽略。
Repl string
// Style 替换方式,默认 MaskStyleLiteral。
Style MaskStyle
// Keep Style=MaskStyleHint 时保留的首尾字符数(<=0 取 DefaultHintKeep)。
Keep int
// Group Style=MaskStyleHint 时,掩码作用在**第几个捕获组**(1 起);0 = 整段匹配。
//
// 默认 0(整段)有个陷阱:规则的匹配范围里若含上下文(如 "password=xxx"),
// 整段掩码保留的尾部 4 个字符**就是凭据的一部分**——那不是定位线索,是泄露。
// 所以凭据类规则应当用捕获组圈出凭据本体,再把 Group 指过去。
//
// 组捕获失败(Group 越界、可选组未参与匹配)时回退**全量打码**而非整段
// hint——整段 hint 的尾 4 字符落在凭据上,正是本注释警告的陷阱本身
// (guardx#69):宁可丢线索,不留尾巴。
Group int
// Guard 可选的命中守卫(guardx#187):收到本次命中的全文,返回 false 时
// 该次命中**原样保留**(不替换)。RE2 无环视(先行/后行断言都没有),
// 「命中了但按上下文豁免」的规则只能经替换函数表达——本字段就是它的
// 公开形态,两种 Style 下都生效。nil = 无守卫。内置 bare-userinfo 规则
// 用它豁免 mailto: 形态与 scheme:// 干扰(见该规则注释);扩展规则同样
// 可用(守卫里自行解析命中文本,组结构归宿主)。
Guard func(match string) bool
}
Rule 一条脱敏规则。
命中后按 Style 替换:
MaskStyleLiteral —— 整段替换为 Repl(字面,支持 $1/$2 引用); MaskStyleHint —— 整段替换为保留首尾各 Keep 字符的不可逆掩码(Repl 被忽略)。
规则表按声明顺序应用;**同一条规则内的多个匹配**由 regexp 一次性替换。
type ValueOptions ¶ added in v0.19.0
type ValueOptions struct {
// IsCredentialKey 追加的键名判定:返回 true 的键整值打码。并集模式
// (KeyMatchSegment/KeyMatchSubstring)下**叠加**于「内置词表 ∪
// KeyTokens」之上;KeyMatchExact(收窄模式)下叠加于宿主词元集之上。
// 返回 false 不豁免内置命中——要豁免请走 KeyMatchExact(显式收窄),
// 内置词表的「值必被打码」承诺不因回调而静默松动。nil = 不追加。
// (oagentkit/guardx#51:集成方喂自己的 isCredentialKey,替掉为兜自家
// 键形态而维护的整套绕行脱敏器。)struct 路径传的是 jsonFieldName 取的
// 键名——json tag 首段优先、无 tag/`-` 回落字段名(guardx#130),与
// map 形态见到的键同一口径。
IsCredentialKey func(key string) bool
// Mask 凭证词键整值打码体(oagentkit/guardx#176):键名判定命中后整值
// 替换为该字面。空串回落内置 "****"(maskLiteral)——空回落杜绝
// 「忘填 = 空掩码」的静默失效。迁移动机:审计面打码体约定为 "***",
// 此前 maskLiteral 硬编码 const 无法表达。⚠️ 只作用于**键名面**的整值
// 打码;字符串叶子内的内嵌凭据仍由文本规则打码(掩码字面由各规则 Repl
// 决定,需要同风格时用 ExtraRules 覆写)。
Mask string
// KeyTokens 宿主词元表(oagentkit/guardx#176):与内置凭证词表参与同一
// 键名判定,匹配口径按 KeyMode。词元是宿主领域数据(本包不预设、不
// 展开),双侧归一(小写、去首尾空白)后比对。词表建议构造一次复用:
// 分段口径的编译缓存以归一词元为键,逐调用重建词表会绕过缓存收益。
KeyTokens []string
// KeyMode KeyTokens 的匹配口径。零值 KeyMatchSegment(默认行为不变);
// KeyMatchExact 为收窄模式(内置表退出),语义与风险见其注释。
KeyMode KeyMatchMode
// ExtraRules 追加到字符串叶子文本规则的扩展表(guardx#76 的**值模式**
// 检测缝):结构化路径的每个字符串叶子按「先内置、后扩展」过
// RedactWith——手机号/身份证这类**值本身即敏感、无键名可依**的形态由
// 调用方注入正则规则,不必再自研结构递归器(agent-harness 整包 pii 的
// 递归 ApplyValue 由此收编,只留值模式正则)。刻意不进内置表:值模式
// 是地区/产品口径(大陆手机号正则会误掩 11 位订单号/毫秒时间戳),
// 内置表守的是通用凭证语义。
ExtraRules []Rule
}
ValueOptions RedactValue / RedactJSON 结构化路径的行为选项(零值 = 内置 词表语义)。