Skip to content

Commit fa3c22e

Browse files
committed
Add redaction support for all token types
1 parent 7e305b2 commit fa3c22e

File tree

2 files changed

+51
-42
lines changed

2 files changed

+51
-42
lines changed

testscript/testscript.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -883,19 +883,23 @@ func (ts *TestScript) condition(cond string) (bool, error) {
883883

884884
// Helpers for command implementations.
885885

886-
// redactTokens looks at the entire string looking for strings that start with
887-
// gho_ or ghp_, then it finds everything after _ until a whitespace separator,
888-
// and replaces those characters with asterisks (e.g. gho_abc123 -> gho_******).
886+
// redactTokens looks at the entire string looking for strings that start with a known token prefix,
887+
// keeps the prefix that distinguishes the token type and masks the rest of the token with asterisks.
888+
// Note that it does not attempt to distinguish the end of a token from any suffixed characters so
889+
// gho_mytokenNOTATOKEN will mask the entirity of the string.
889890
func redactTokens(s string) string {
890891
// Regular expression to match "ghp_" or "gho_" followed by any sequence of non-whitespace characters
891-
re := regexp.MustCompile(`(gh[op]_)\S+`)
892+
re := regexp.MustCompile(`(gh[pousr]_|github_pat_)\S+`)
892893

893894
// Replace matched strings with the prefix followed by asterisks
894895
return re.ReplaceAllStringFunc(s, func(match string) string {
895-
// Keep the "ghp_" or "gho_" prefix and replace the rest with asterisks
896-
prefix := match[:4] // "ghp_" or "gho_"
897-
length := len(match[4:]) // Length of the remaining characters to be redacted
898-
return prefix + strings.Repeat("*", length) // Replace the rest with asterisks
896+
// If the match is gh then we want to keep the first 4 characters and redact everything else,
897+
// otherwise, it must be a github_pat_ and then we keep the first 11 characters and redact everything else.
898+
prefixLength := 4
899+
if strings.HasPrefix(match, "github_pat_") {
900+
prefixLength = 11
901+
}
902+
return match[:prefixLength] + strings.Repeat("*", len(match)-prefixLength)
899903
})
900904
}
901905

testscript/testscript_test.go

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -411,59 +411,64 @@ func TestBadDir(t *testing.T) {
411411
}
412412

413413
func TestRedactTokens(t *testing.T) {
414+
// https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats
415+
tokensToRedacted := map[string]string{
416+
"ghp_1234567890": "ghp_**********",
417+
"github_pat_1234567890": "github_pat_**********",
418+
"gho_1234567890": "gho_**********",
419+
"ghu_1234567890": "ghu_**********",
420+
"ghs_1234567890": "ghs_**********",
421+
"ghr_1234567890": "ghr_**********",
422+
}
423+
414424
tests := []struct {
415425
name string
416426
input string
417427
want string
418428
}{
419429
{
420-
name: "no token",
421-
input: "no token",
422-
want: "no token",
423-
},
424-
{
425-
name: "oauth token at start",
426-
input: "gho_1234567890",
427-
want: "gho_**********",
428-
},
429-
{
430-
name: "oauth token at end",
431-
input: "this is a gho_1234567890",
432-
want: "this is a gho_**********",
430+
name: "token at start",
431+
input: "%s",
432+
want: "%s",
433433
},
434434
{
435-
name: "oauth token in middle",
436-
input: "this is a gho_1234567890 and this is redacted",
437-
want: "this is a gho_********** and this is redacted",
435+
name: "token at end",
436+
input: "this is a %s",
437+
want: "this is a %s",
438438
},
439439
{
440-
name: "oauth token at start with newline",
441-
input: "gho_1234567890\n",
442-
want: "gho_**********\n",
440+
name: "token in middle",
441+
input: "this is a %s and this is redacted",
442+
want: "this is a %s and this is redacted",
443443
},
444444
{
445-
name: "oauth token with no preceding word boundary",
446-
input: "foogho_1234567890",
447-
want: "foogho_**********",
445+
name: "token at start with newline",
446+
input: "%s\n",
447+
want: "%s\n",
448448
},
449449
{
450-
name: "oauth token no word boundary (extends redaction into next word)",
451-
input: "gho_1234567890x",
452-
want: "gho_***********",
450+
name: "token with no preceding word boundary",
451+
input: "foo%s",
452+
want: "foo%s",
453453
},
454454
{
455-
name: "pat token",
456-
input: "ghp_1234567890",
457-
want: "ghp_**********",
455+
name: "token no word boundary (extends redaction into next word)",
456+
input: "%sx",
457+
want: "%s*",
458458
},
459459
}
460460

461-
for _, test := range tests {
462-
t.Run(test.name, func(t *testing.T) {
463-
if got := redactTokens(test.input); got != test.want {
464-
t.Fatalf("redactTokens(%q) == %q, want %q", test.input, got, test.want)
465-
}
466-
})
461+
for token, redacted := range tokensToRedacted {
462+
for _, test := range tests {
463+
t.Run(fmt.Sprintf("%s: %s", token, test.name), func(t *testing.T) {
464+
unredacted := fmt.Sprintf(test.input, token)
465+
expected := fmt.Sprintf(test.want, redacted)
466+
467+
if got := redactTokens(unredacted); got != expected {
468+
t.Fatalf("redactTokens(%q) == %q, want %q", unredacted, got, expected)
469+
}
470+
})
471+
}
467472
}
468473
}
469474

0 commit comments

Comments
 (0)