fix(detectors/ngrok): broaden valid bearer tokens matching (#5152)

* fix(detectors/ngrok): match API keys that do not start with 2

The ngrok bearer token pattern is 27 alphanumerics, an underscore, and 21
alphanumerics. Requiring a leading 2 missed valid API keys and ak_ resource
IDs were never secrets.

Co-authored-by: Cursor <[email protected]>

* fix(detectors/ngrok): restore digit-leading suffix constraint

The prefix broadening accidentally allowed any alphanumeric suffix start, which would increase false positives.

Co-authored-by: Cursor <[email protected]>

* fix(detectors/ngrok): accept 20-21 char suffixes with any leading char

Real authtokens verified against the ngrok API can have 20-char and
letter-leading suffixes, so the digit-leading 21-char suffix constraint
drops valid secrets.

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
matt
2026-09-14 14:16:40 -04:00
committed by GitHub
co-authored by Cursor
parent 58bf4810c8
commit ce7b2b838c
2 changed files with 36 additions and 4 deletions
+7 -1
View File
@@ -38,7 +38,13 @@ const (
)
var (
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ngrok"}) + `\b(2[a-zA-Z0-9]{26}_\d[a-zA-Z0-9]{20})\b`)
// ngrok API keys and authtokens are {prefix}_{suffix}, both alphanumeric with
// no fixed leading character. The prefix is 27 chars. Suffixes are usually 21
// chars with a leading digit, but real authtokens verified against the ngrok
// API (via ERR_NGROK_206) have been observed with 20-char and letter-leading
// suffixes, so the pattern must accept 20-21 chars starting with any
// alphanumeric character.
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ngrok"}) + `\b([a-zA-Z0-9]{27}_[a-zA-Z0-9]{20,21})\b`)
)
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
+29 -3
View File
@@ -12,9 +12,15 @@ import (
)
var (
validPattern = "2WIRSIHOQyHSVklZnoz2k6bTYdH_0E7z0Ta9QEyR1fZvQ0KU9"
invalidPattern = "2WIRSIHOQyHSVklZnoz2k6bT?dH_0E7z0Ta9QEyR1fZvQ0KU9"
keyword = "ngrok"
validPattern = "2WIRSIHOQyHSVklZnoz2k6bTYdH_0E7z0Ta9QEyR1fZvQ0KU9"
validPatternAlt = "3GuTI5JXfaLtYILkc6N0xopP2V0_89zkDfefiuMv6Hk1z6oYi"
// Authtokens exist in the wild with letter-leading and 20-char suffixes
// (confirmed live against the ngrok API), so both must be detected.
validPatternLetterSuffix = "3GuTI5JXfaLtYILkc6N0xopP2V0_a9zkDfefiuMv6Hk1z6oYi"
validPatternShortSuffix = "4HvUJ6KYgbMuZJMld7O1yqqQ3W1_b8ylEgfgjvNw7Il2z7pZ"
invalidPattern = "2WIRSIHOQyHSVklZnoz2k6bT?dH_0E7z0Ta9QEyR1fZvQ0KU9"
invalidIDPattern = "ak_3GuTI5JXfaLtYILkc6N0xopP2V0"
keyword = "ngrok"
)
func TestNgrok_Pattern(t *testing.T) {
@@ -30,11 +36,26 @@ func TestNgrok_Pattern(t *testing.T) {
input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
want: []string{"2WIRSIHOQyHSVklZnoz2k6bTYdH_0E7z0Ta9QEyR1fZvQ0KU9"},
},
{
name: "valid pattern - api key not starting with 2",
input: fmt.Sprintf("%s token = '%s'", keyword, validPatternAlt),
want: []string{validPatternAlt},
},
{
name: "valid pattern - ignore duplicate",
input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
want: []string{"2WIRSIHOQyHSVklZnoz2k6bTYdH_0E7z0Ta9QEyR1fZvQ0KU9"},
},
{
name: "valid pattern - authtoken suffix not starting with digit",
input: fmt.Sprintf("%s token = '%s'", keyword, validPatternLetterSuffix),
want: []string{validPatternLetterSuffix},
},
{
name: "valid pattern - authtoken with 20 char suffix",
input: fmt.Sprintf("%s token = '%s'", keyword, validPatternShortSuffix),
want: []string{validPatternShortSuffix},
},
{
name: "valid pattern - key out of prefix range",
input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
@@ -45,6 +66,11 @@ func TestNgrok_Pattern(t *testing.T) {
input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
want: []string{},
},
{
name: "invalid pattern - api key resource id not bearer token",
input: fmt.Sprintf("%s api_key = '%s'", keyword, invalidIDPattern),
want: []string{},
},
}
for _, test := range tests {