Class: Expect::Redactor

Inherits:
Object
  • Object
show all
Defined in:
lib/expect/redactor.rb

Overview

单个日志字节流的过滤器。保留最长秘密长度减一的尾部,跨 write/read 分片仍可识别。 掩码与原字节一起留存;重叠命中的区间取并集,已经输出的掩码不重复生成。 不依赖会话或 IO;每个日志目标或诊断方向使用独立实例。

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(patterns, replacement: "[FILTERED]") ⇒ Redactor

pending 保存尚不能安全输出的原字节,hidden 的对应字节用 0/1 表示是否需要遮盖。



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/expect/redactor.rb', line 15

def initialize(patterns, replacement: "[FILTERED]")
  unless replacement.is_a?(String) && !replacement.empty?
    raise ArgumentError, "replacement must be a nonempty String"
  end

  @replacement = replacement.b.freeze
  self.patterns = patterns
  @pending = "".b
  @hidden = "".b
  @masking = false
end

Class Method Details

.redact(data, patterns, replacement: "[FILTERED]") ⇒ Object

完整诊断文本只匹配完整秘密;流边界的疑似秘密前缀由 finish 的默认策略保护。



9
10
11
12
# File 'lib/expect/redactor.rb', line 9

def self.redact(data, patterns, replacement: "[FILTERED]")
  filter = new(patterns, replacement: replacement)
  filter.append(data) + filter.finish(partial: false)
end

Instance Method Details

#append(data) ⇒ Object

追加一个原始字节块,返回已经可以确定的安全前缀;新秘密可能跨越此前保留的尾部。

Raises:

  • (ArgumentError)


38
39
40
41
42
43
44
45
# File 'lib/expect/redactor.rb', line 38

def append(data)
  raise ArgumentError, "data must be a String" unless data.is_a?(String)

  @pending << data.b
  @hidden << ("\0" * data.bytesize)
  mark_secrets
  release([@pending.bytesize - @lookbehind, 0].max)
end

#finish(partial: true) ⇒ Object

EOF、日志目标替换及关闭是流边界;尾部疑似秘密前缀也遮盖,不能因 flush 泄露片段。

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
55
56
# File 'lib/expect/redactor.rb', line 48

def finish(partial: true)
  raise ArgumentError, "partial must be true or false" unless [true, false].include?(partial)

  mark_secrets
  mark_partial_secrets if partial
  output = release(@pending.bytesize)
  @masking = false
  output
end

#inspect ⇒ Object

过滤器公开后仍不在诊断摘要中展开注册秘密或尚未交付的原始字节。



59
# File 'lib/expect/redactor.rb', line 59

def inspect = "#<#{self.class}>"

#patterns=(patterns) ⇒ Object

更新后续匹配规则并保留已有尾部与掩码;不能追溯修改已经交付给日志目标的内容。



28
29
30
31
32
33
34
35
# File 'lib/expect/redactor.rb', line 28

def patterns=(patterns)
  unless patterns.is_a?(Array) && patterns.all? { |pattern| pattern.is_a?(String) && !pattern.empty? }
    raise ArgumentError, "patterns must be an Array of nonempty Strings"
  end

  @patterns = patterns.map { |pattern| pattern.b.freeze }.uniq.freeze
  @lookbehind = [(@patterns.map(&:bytesize).max || 0) - 1, 0].max
end