Skip to content

Commit e68f3d2

Browse files
nikpivkinChrisJr404DmitriyLewen
authored
feat(secret): support new stateless format for GitHub App installation tokens (aquasecurity#10826)
Co-authored-by: ChrisJr404 <11917633+ChrisJr404@users.noreply.github.com> Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com>
1 parent 859a933 commit e68f3d2

3 files changed

Lines changed: 53 additions & 2 deletions

File tree

pkg/fanal/secret/builtin-rules.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,17 @@ var builtinRules = []Rule{
140140
Keywords: []string{"gho_"},
141141
},
142142
{
143+
// `ghu_` user-to-server tokens keep the legacy fixed format: exactly
144+
// 36 alphanumeric chars. Since 2026-04-27 `ghs_` installation
145+
// tokens use a new stateless format `ghs_<APPID>_<JWT>` (~520 chars,
146+
// base64url + dot-separated), so they contain `.`, `-` and `_`.
147+
// GitHub recommends `ghs_[A-Za-z0-9.\-_]{36,}`. See
148+
// https://github.com/aquasecurity/trivy/issues/10591.
143149
ID: "github-app-token",
144150
Category: CategoryGitHub,
145151
Title: "GitHub App Token",
146152
Severity: "CRITICAL",
147-
Regex: MustCompileWithoutWordPrefix(`?P<secret>(ghu|ghs)_[0-9a-zA-Z]{36}`),
153+
Regex: MustCompileWithoutWordPrefix(`?P<secret>(?:ghu_[0-9a-zA-Z]{36}|ghs_[0-9a-zA-Z._-]{36,})`),
148154
SecretGroupName: "secret",
149155
Keywords: []string{"ghu_", "ghs_"},
150156
},
@@ -608,7 +614,20 @@ var builtinRules = []Rule{
608614
Category: CategoryJWT,
609615
Title: "JWT token",
610616
Severity: "MEDIUM",
611-
Regex: MustCompile(`ey[a-zA-Z0-9]{17,}\.ey[a-zA-Z0-9\/\\_-]{17,}\.(?:[a-zA-Z0-9\/\\_-]{10,}={0,2})?`),
617+
// The optional `ghs_<APPID>_` prefix is part of the full match (which the
618+
// allow-rule below inspects) but not of the `secret` group (which is
619+
// reported). This drops the JWT embedded in a stateless GitHub App token
620+
// so it is reported only once, by the `github-app-token` rule, while
621+
// standalone JWTs keep matching and are reported exactly as before.
622+
Regex: MustCompile(`(?:ghs_[0-9]+_)?(?P<secret>ey[a-zA-Z0-9]{17,}\.ey[a-zA-Z0-9\/\\_-]{17,}\.(?:[a-zA-Z0-9\/\\_-]{10,}={0,2})?)`),
623+
SecretGroupName: "secret",
624+
AllowRules: AllowRules{
625+
{
626+
ID: "stateless-ghs-jwt",
627+
Description: "Avoid double-reporting the JWT embedded in a stateless ghs_ GitHub App token",
628+
Regex: MustCompile(`^ghs_`),
629+
},
630+
},
612631
Keywords: []string{".eyJ"},
613632
},
614633
{

pkg/fanal/secret/scanner_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,28 @@ func TestSecretScanner(t *testing.T) {
326326
},
327327
Offset: 13,
328328
}
329+
wantFindingGitHubAppToken := types.SecretFinding{
330+
RuleID: "github-app-token",
331+
Category: secret.CategoryGitHub,
332+
Title: "GitHub App Token",
333+
Severity: "CRITICAL",
334+
StartLine: 1,
335+
EndLine: 1,
336+
Match: "GITHUB_TOKEN=**********************************************************************************************",
337+
Code: types.Code{
338+
Lines: []types.Line{
339+
{
340+
Number: 1,
341+
Content: "GITHUB_TOKEN=**********************************************************************************************",
342+
Highlighted: "GITHUB_TOKEN=**********************************************************************************************",
343+
IsCause: true,
344+
FirstCause: true,
345+
LastCause: true,
346+
},
347+
},
348+
},
349+
Offset: 13,
350+
}
329351
wantFindingMyAwsAccessKey := types.SecretFinding{
330352
RuleID: "aws-secret-access-key",
331353
Category: secret.CategoryAWS,
@@ -1689,6 +1711,15 @@ func TestSecretScanner(t *testing.T) {
16891711
Findings: []types.SecretFinding{wantFindingGitHubPAT},
16901712
},
16911713
},
1714+
{
1715+
name: "should find GitHub App installation token (stateless format)",
1716+
configPath: filepath.Join("testdata", "skip-test.yaml"),
1717+
inputFilePath: "testdata/github-app-token.txt",
1718+
want: types.Secret{
1719+
FilePath: "testdata/github-app-token.txt",
1720+
Findings: []types.SecretFinding{wantFindingGitHubAppToken},
1721+
},
1722+
},
16921723
{
16931724
name: "should enable github-pat builtin rule, but disable aws-access-key-id rule",
16941725
configPath: filepath.Join("testdata", "config-enable-ghp.yaml"),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GITHUB_TOKEN=ghs_15368_eyJtestHEADERdummynotreal00.eyJtestPAYLOADdummynotreal00.testSIGNATUREdummynotreal00

0 commit comments

Comments
 (0)