-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsecrets_fuzz_test.go
More file actions
91 lines (83 loc) · 3.39 KB
/
Copy pathsecrets_fuzz_test.go
File metadata and controls
91 lines (83 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package tok_test
import (
"strings"
"testing"
"github.com/GrayCodeAI/tok"
)
// FuzzRedactSecrets exercises the secrets redactor with arbitrary input.
//
// It asserts the security-relevant invariants that hold for the current
// redactor implementation:
//
// 1. No panic — the redactor (and the underlying detector) must tolerate
// any byte sequence, including invalid UTF-8, unbalanced delimiters,
// and pathological key-like fragments.
// 2. Redaction reduces exposure — when a secret is detected, the redacted
// output is not byte-identical to the input, carries a [REDACTED:...]
// marker, and contains strictly fewer verbatim occurrences of each
// detected value than the input did. (A plain check for total absence
// would false-positive when the same byte sequence also appears as
// non-secret text elsewhere in the input.)
// 3. Idempotence on a clean fixed point — redacting an input that contains
// no detectable secret returns that input unchanged.
// 4. Position validity — reported match offsets stay within bounds.
//
// Note: full string-level idempotence (Redact(Redact(x)) == Redact(x)) does
// NOT hold for this implementation, because a substituted marker such as
// "password = [REDACTED:Generic Password]" can itself re-match a generic
// "password = <value>" pattern on a second pass. That is a property of the
// production redactor, not of this test; the invariants above are the ones
// that are both meaningful and actually guaranteed.
func FuzzRedactSecrets(f *testing.F) {
seeds := []string{
"",
"hello world",
"AKIAIOSFODNN7EXAMPLE",
"github token ghp_0123456789abcdefghijklmnopqrstuvwxyz",
"password = hunter2hunter2",
"sk-ant-0123456789abcdefghijklmno secret",
"-----BEGIN RSA PRIVATE KEY-----\nMIIabc\n-----END RSA PRIVATE KEY-----",
"api_key: 'abcdefghijklmnopqrstuvwxyz'",
"bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.sig",
"\x00\xff\xfe invalid utf8 \xc3\x28",
"[REDACTED:OpenAI API Key]",
}
for _, s := range seeds {
f.Add(s)
}
det := tok.NewSecretDetector()
f.Fuzz(func(t *testing.T, input string) {
// Invariant 1: never panics.
matches := det.DetectSecrets(input)
out := det.RedactSecrets(input)
// Invariant 4: reported positions stay within bounds.
for _, m := range matches {
if m.StartPos < 0 || m.EndPos > len(input) || m.StartPos > m.EndPos {
t.Fatalf("match position out of range: %+v (len=%d) input=%q", m, len(input), input)
}
}
// Invariant 2: redaction reduces exposure.
if len(matches) > 0 {
if out == input {
t.Fatalf("redactor returned input unchanged despite %d match(es)\ninput: %q", len(matches), input)
}
if !strings.Contains(out, "[REDACTED:") {
t.Fatalf("redacted output carries no [REDACTED:] marker\ninput: %q\nout: %q", input, out)
}
for _, m := range matches {
if len(m.Value) < 4 {
continue
}
if after, before := strings.Count(out, m.Value), strings.Count(input, m.Value); after >= before {
t.Fatalf("detected secret value not reduced by redaction (before=%d after=%d)\ntype: %s\nvalue: %q\ninput: %q\nout: %q",
before, after, m.Type, m.Value, input, out)
}
}
}
// Invariant 3: idempotent on a clean fixed point — redacting an input
// with no detectable secret must leave it unchanged.
if len(matches) == 0 && out != input {
t.Fatalf("redactor altered secret-free input\ninput: %q\nout: %q", input, out)
}
})
}