1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6 "encoding/base64"
7 "strings"
8 "testing"
9)
10
11// Cases ported from actions/runner src/Test/L0/HostContextL0.cs
12// (DefaultSecretMaskers, SecretSectionMasking), MIT, (c) 2019 GitHub; see
13// THIRD_PARTY.md.
14func TestDefaultSecretMaskers(t *testing.T) {
15 m := newMasker()
16 for _, v := range []string{"Password123!", "Pass\"word\"123!", "Pass word 123!", "Pass<word>123!", "Pass'word'123!", "\"Password123!!\"", "\"short\""} {
17 m.add(v)
18 }
19 b64 := func(s string) string { return base64.StdEncoding.EncodeToString([]byte(s)) }
20 for _, tc := range []struct{ in, want string }{
21 {"123Password123!123", "123***123"},
22 {"password123", "password123"},
23 {`123Pass\"word\"123!123`, "123***123"},
24 {"123Pass%20word%20123%21123", "123***123"},
25 {"123Pass<word>123!123", "123***123"},
26 {"123Pass''word''123!123", "123***123"},
27 {b64(":Password123!"), "OlBh***"},
28 {b64("a:Password123!"), "YTpQ***"},
29 {b64("ab:Password123!"), "YWI6***"},
30 {b64("abc:Password123!"), "YWJjOlBh***"},
31 {b64("abcd:Password123!"), "YWJjZDpQ***"},
32 {b64("abcde:Password123!"), "YWJjZGU6***"},
33 {"123Password123!!123", "123***123"},
34 {"123short123", "123short123"},
35 {"123\"short\"123", "123***123"},
36 } {
37 if got := m.mask(tc.in); got != tc.want {
38 t.Errorf("mask(%q) = %q, want %q", tc.in, got, tc.want)
39 }
40 }
41}
42
43func TestSecretSectionMasking(t *testing.T) {
44 for _, tc := range []struct{ secret, raw, want string }{
45 {"secret&secret&secret", "secret&secret&\x1b[96msecret\x1b[0m", "***\x1b[96m***\x1b[0m"},
46 {"secret&secret+secret", "secret&\x1b[96msecret+secret\x1b[0m", "***\x1b[96m***\x1b[0m"},
47 {"secret+secret&secret", "secret+secret&\x1b[96msecret\x1b[0m", "***\x1b[96m***\x1b[0m"},
48 {"secret&secret&+secretsecret", "secret&secret&+\x1b[96ms\x1b[0mecretsecret", "***\x1b[96ms\x1b[0m***"},
49 {"secret&+secret&secret", "secret&+\x1b[96ms\x1b[0mecret&secret", "***\x1b[96ms\x1b[0m***"},
50 {"secret&+secret&+secret", "secret&+\x1b[96ms\x1b[0mecret&+secret", "***\x1b[96ms\x1b[0m***"},
51 {"secret&+secret&secret&+secret", "secret&+\x1b[96ms\x1b[0mecret&secret&+secret", "***\x1b[96ms\x1b[0m***"},
52 {"secret&secret&+", "secret&secret&+\x1b[96m\x1b[0m", "***\x1b[96m\x1b[0m"},
53 } {
54 m := newMasker(tc.secret)
55 if got := m.mask(tc.raw); got != tc.want {
56 t.Errorf("secret %q: mask(%q) = %q, want %q", tc.secret, tc.raw, got, tc.want)
57 }
58 }
59}
60
61// Encoders of ValueEncoders.cs, one by one.
62func TestValueEncoders(t *testing.T) {
63 for _, tc := range []struct{ name, got, want string }{
64 {"cmdline", strings.ReplaceAll(`a"b`, `"`, `\"`), `a\"b`},
65 {"json", jsonStringEscape("a\"b\\c\n\t\x01/é\u2028"), `a\"b\\c\n\t\u0001/é\u2028`},
66 {"uri", uriDataEscape("a b/c~d-é"), "a%20b%2Fc~d-%C3%A9"},
67 {"xml", xmlDataEscape(`<a href="x">'&'</a>`), "<a href="x">'&'</a>"},
68 {"trim 9 chars", trimDoubleQuotes(`"12345678"`), "12345678"},
69 {"trim 8 chars", trimDoubleQuotes(`"123456"`), ""},
70 {"trim no quote", trimDoubleQuotes(`12345678901`), ""},
71 {"pre", powerShellPreAmpersandEscape("secret&secret&secret"), "secret&secret&"},
72 {"pre plus", powerShellPreAmpersandEscape("secret&+secret&secret"), "secret&+"},
73 {"pre short", powerShellPreAmpersandEscape("ab&c"), ""},
74 {"pre no ampersand", powerShellPreAmpersandEscape("secretsecret"), ""},
75 {"post", powerShellPostAmpersandEscape("secret&secret&secret"), "secret"},
76 {"post plus", powerShellPostAmpersandEscape("secret&+Xsecret&secret"), "secret&secret"},
77 {"post plus, nothing after", powerShellPostAmpersandEscape("secret&+X"), ""},
78 {"post short", powerShellPostAmpersandEscape("secret&abc"), ""},
79 } {
80 if tc.got != tc.want {
81 t.Errorf("%s: %q, want %q", tc.name, tc.got, tc.want)
82 }
83 }
84}
85
86// Adding a value after the first masks makes later text masked, empty values
87// are ignored, overlapping matches merge into one ***.
88func TestMaskerAddAndMerge(t *testing.T) {
89 m := newMasker()
90 m.add("")
91 if m.mask("abc") != "abc" {
92 t.Fatal("empty value masks")
93 }
94 m.add("abcd")
95 m.add("cdef")
96 if got := m.mask("xabcdefy abcd"); got != "x***y ***" {
97 t.Errorf("%q", got)
98 }
99 if got := m.mask(""); got != "" {
100 t.Error(got)
101 }
102 // the same value again changes nothing
103 m.add("abcd")
104 if got := m.mask("abcd"); got != "***" {
105 t.Error(got)
106 }
107}
108
109// SecretMasker.AddValue skips a value only when it was added before as an
110// original, not when it is already one of the forms of another value: the
111// encodings of "one%20two" are masked after "one two" was added, although
112// "one%20two" itself is the URI form of "one two" (GO-001).
113func TestMaskerAddsEncodingsOfAValueThatIsAnEncodingOfAnother(t *testing.T) {
114 m := newMasker()
115 m.add("one two")
116 m.add("one%20two")
117 for _, text := range []string{"one two", "one%20two", "one%2520two"} {
118 if got := m.mask(text); got != "***" {
119 t.Errorf("mask(%q) = %q", text, got)
120 }
121 }
122 // the same original twice is still a no-op
123 m.add("one%20two")
124 if n := len(m.list); n == 0 {
125 t.Fatal("empty")
126 }
127 before := len(m.list)
128 m.add("one%20two")
129 if len(m.list) != before {
130 t.Error("an original added twice grew the list")
131 }
132}