stream.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // Package pii 提供字段级 PII 脱敏(规则拷贝自旧 MCP 的 helper/pii_*.go,两者独立维护)。
  2. package pii
  3. import (
  4. "bytes"
  5. "encoding/json"
  6. "fmt"
  7. "strconv"
  8. )
  9. // MaskJSONBytes 对已序列化的 JSON 字节流做流式脱敏,返回脱敏后的 JSON 字节。
  10. //
  11. // 实现要点(优化 B):
  12. // - 使用 json.Decoder 的 token 流式解析,不构建完整的 interface{} 对象树,
  13. // 内存占用为常数级(栈深度 = JSON 嵌套深度)。
  14. // - 单次遍历:读到 object 的 key 命中敏感规则时,对其对应的 value 做脱敏;
  15. // 其余 token 原样透传。
  16. // - 若敏感 key 的 value 是对象或数组,则整个子树被完全脱敏(fail-safe)。
  17. // - 解析失败时返回 error(fail-closed,由上层拒绝输出,不泄露原始数据)。
  18. func MaskJSONBytes(in []byte) ([]byte, error) {
  19. dec := json.NewDecoder(bytes.NewReader(in))
  20. dec.UseNumber()
  21. var out bytes.Buffer
  22. w := &jsonWriter{buf: &out}
  23. if err := maskValue(dec, w, nil); err != nil {
  24. return nil, fmt.Errorf("PII 脱敏失败: %w", err)
  25. }
  26. // 确认没有多余内容(例如尾部垃圾),保证输入是单个合法 JSON 值。
  27. if dec.More() {
  28. return nil, fmt.Errorf("PII 脱敏失败: JSON 存在多余的尾部内容")
  29. }
  30. return out.Bytes(), nil
  31. }
  32. // maskValue 读取下一个 JSON 值并写入 w。
  33. // forceMask 非 nil 时,表示当前值处于某个敏感 key 之下,整个值(含子树)需完全脱敏。
  34. func maskValue(dec *json.Decoder, w *jsonWriter, forceMask *MaskKind) error {
  35. tok, err := dec.Token()
  36. if err != nil {
  37. return err
  38. }
  39. switch t := tok.(type) {
  40. case json.Delim:
  41. switch t {
  42. case '{':
  43. return maskObject(dec, w, forceMask)
  44. case '[':
  45. return maskArray(dec, w, forceMask)
  46. default:
  47. return fmt.Errorf("非预期的分隔符: %v", t)
  48. }
  49. default:
  50. // 标量值(string / json.Number / bool / nil)
  51. return writeScalar(w, tok, forceMask)
  52. }
  53. }
  54. // maskObject 处理一个 JSON 对象。forceMask 非 nil 时整个对象子树被脱敏。
  55. func maskObject(dec *json.Decoder, w *jsonWriter, forceMask *MaskKind) error {
  56. w.openObject()
  57. for dec.More() {
  58. // 读 key
  59. keyTok, err := dec.Token()
  60. if err != nil {
  61. return err
  62. }
  63. key, ok := keyTok.(string)
  64. if !ok {
  65. return fmt.Errorf("对象 key 非字符串: %T", keyTok)
  66. }
  67. w.writeKey(key)
  68. // 决定该字段 value 的脱敏方式:
  69. // - 若已处于强制脱敏子树中,继续沿用父级 forceMask。
  70. // - 否则查规则表。
  71. var childForce *MaskKind
  72. if forceMask != nil {
  73. childForce = forceMask
  74. } else if kind, hit := maskKindFor(key); hit {
  75. k := kind
  76. childForce = &k
  77. }
  78. if err := maskValue(dec, w, childForce); err != nil {
  79. return err
  80. }
  81. }
  82. // 消费结束的 '}'
  83. if _, err := dec.Token(); err != nil {
  84. return err
  85. }
  86. w.closeObject()
  87. return nil
  88. }
  89. // maskArray 处理一个 JSON 数组。forceMask 非 nil 时数组内每个元素都被脱敏。
  90. func maskArray(dec *json.Decoder, w *jsonWriter, forceMask *MaskKind) error {
  91. w.openArray()
  92. for dec.More() {
  93. if err := maskValue(dec, w, forceMask); err != nil {
  94. return err
  95. }
  96. }
  97. // 消费结束的 ']'
  98. if _, err := dec.Token(); err != nil {
  99. return err
  100. }
  101. w.closeArray()
  102. return nil
  103. }
  104. // writeScalar 写入一个标量值,必要时脱敏。
  105. func writeScalar(w *jsonWriter, tok json.Token, forceMask *MaskKind) error {
  106. if forceMask != nil {
  107. // 处于敏感字段之下:
  108. // - 字符串按具体 MaskKind 处理
  109. // - 其余标量(数字/bool,例如经纬度以 number 存储)统一替换为掩码字符串
  110. if s, ok := tok.(string); ok {
  111. return w.writeValueRaw(quote(applyMaskString(*forceMask, s)))
  112. }
  113. if tok == nil {
  114. // null 保持 null(无信息泄露)
  115. return w.writeValueRaw("null")
  116. }
  117. return w.writeValueRaw(quote(maskFull()))
  118. }
  119. // 非敏感字段:原样写出
  120. switch v := tok.(type) {
  121. case string:
  122. return w.writeValueRaw(quote(v))
  123. case json.Number:
  124. return w.writeValueRaw(v.String())
  125. case bool:
  126. return w.writeValueRaw(strconv.FormatBool(v))
  127. case nil:
  128. return w.writeValueRaw("null")
  129. default:
  130. return fmt.Errorf("非预期的标量类型: %T", tok)
  131. }
  132. }
  133. // quote 将字符串按 JSON 规则转义并加引号。
  134. func quote(s string) string {
  135. b, _ := json.Marshal(s)
  136. return string(b)
  137. }
  138. // jsonWriter 负责按结构写出 JSON,管理逗号与 key/value 状态。
  139. type jsonWriter struct {
  140. buf *bytes.Buffer
  141. stack []*frame
  142. }
  143. type frame struct {
  144. isObject bool
  145. needComma bool // 是否需要在下一个元素/键前加逗号
  146. expectKey bool // 对象中:下一个 writeKey 之后期待 value
  147. pendingKey bool // 已写入 key,等待 value(value 前不加逗号)
  148. }
  149. func (w *jsonWriter) top() *frame {
  150. if len(w.stack) == 0 {
  151. return nil
  152. }
  153. return w.stack[len(w.stack)-1]
  154. }
  155. // beforeValue 在写入一个值(标量或容器起始)之前处理逗号。
  156. func (w *jsonWriter) beforeValue() {
  157. f := w.top()
  158. if f == nil {
  159. return
  160. }
  161. if f.isObject {
  162. // 对象中值紧跟 key,key 已处理逗号,这里不加
  163. if f.pendingKey {
  164. f.pendingKey = false
  165. return
  166. }
  167. }
  168. if f.needComma {
  169. w.buf.WriteByte(',')
  170. }
  171. f.needComma = true
  172. }
  173. func (w *jsonWriter) openObject() {
  174. w.beforeValue()
  175. w.buf.WriteByte('{')
  176. w.stack = append(w.stack, &frame{isObject: true})
  177. }
  178. func (w *jsonWriter) closeObject() {
  179. w.buf.WriteByte('}')
  180. w.stack = w.stack[:len(w.stack)-1]
  181. }
  182. func (w *jsonWriter) openArray() {
  183. w.beforeValue()
  184. w.buf.WriteByte('[')
  185. w.stack = append(w.stack, &frame{isObject: false})
  186. }
  187. func (w *jsonWriter) closeArray() {
  188. w.buf.WriteByte(']')
  189. w.stack = w.stack[:len(w.stack)-1]
  190. }
  191. func (w *jsonWriter) writeKey(key string) {
  192. f := w.top()
  193. if f != nil && f.needComma {
  194. w.buf.WriteByte(',')
  195. }
  196. w.buf.WriteString(quote(key))
  197. w.buf.WriteByte(':')
  198. if f != nil {
  199. f.needComma = true
  200. f.pendingKey = true
  201. }
  202. }
  203. func (w *jsonWriter) writeValueRaw(raw string) error {
  204. w.beforeValue()
  205. _, err := w.buf.WriteString(raw)
  206. return err
  207. }
  208. // Marshal 将任意数据序列化为紧凑 JSON 并完成字段级脱敏。
  209. // 脱敏失败时返回错误(fail-closed),调用方不得输出原始数据。
  210. func Marshal(v any) ([]byte, error) {
  211. raw, err := json.Marshal(v)
  212. if err != nil {
  213. return nil, fmt.Errorf("JSON 序列化失败: %w", err)
  214. }
  215. return MaskJSONBytes(raw)
  216. }
  217. // MaskField 按字段名对单个字符串值脱敏(表格/CSV 单元格使用);字段不在规则表中时原样返回。
  218. func MaskField(key, value string) string {
  219. kind, ok := maskKindFor(key)
  220. if !ok || value == "" {
  221. return value
  222. }
  223. return applyMaskString(kind, value)
  224. }