From 615a7130b5e3a392c3d0be096d095ba365253205 Mon Sep 17 00:00:00 2001 From: Hirsch Singhal Date: Mon, 25 May 2026 09:38:54 -0700 Subject: [PATCH 1/7] Fix ghs_ token patterns to support new token format Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/entry-points.js | 22 +++++++++++++++------- src/artifact-scanner.test.ts | 21 +++++++++++++++------ src/artifact-scanner.ts | 25 ++++++++++++++++++------- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 78a5f058af..53cf6ec342 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -158049,7 +158049,7 @@ var GITHUB_TOKEN_PATTERNS = [ }, { type: "Server-to-Server Token" /* ServerToServer */, - pattern: /\bghs_[a-zA-Z0-9]{36}\b/g + pattern: /ghs_[A-Za-z0-9._]{36,}/g }, { type: "Refresh Token" /* Refresh */, @@ -158057,7 +158057,7 @@ var GITHUB_TOKEN_PATTERNS = [ }, { type: "App Installation Access Token" /* AppInstallationAccess */, - pattern: /\bghs_[a-zA-Z0-9]{255}\b/g + pattern: /ghs_[A-Za-z0-9._]{36,}/g } ]; function isAuthToken(value, patterns = GITHUB_TOKEN_PATTERNS) { @@ -158070,15 +158070,23 @@ function isAuthToken(value, patterns = GITHUB_TOKEN_PATTERNS) { } function scanFileForTokens(filePath, relativePath, logger) { const findings = []; + const seenMatches = /* @__PURE__ */ new Set(); try { const content = fs23.readFileSync(filePath, "utf8"); for (const { type: type2, pattern } of GITHUB_TOKEN_PATTERNS) { - const matches = content.match(pattern); - if (matches) { - for (let i = 0; i < matches.length; i++) { - findings.push({ tokenType: type2, filePath: relativePath }); + const regex = new RegExp(pattern.source, pattern.flags); + let matchCount = 0; + for (const match of content.matchAll(regex)) { + const index = match.index; + if (index === void 0 || seenMatches.has(index)) { + continue; } - logger.debug(`Found ${matches.length} ${type2}(s) in ${relativePath}`); + seenMatches.add(index); + findings.push({ tokenType: type2, filePath: relativePath }); + matchCount++; + } + if (matchCount > 0) { + logger.debug(`Found ${matchCount} ${type2}(s) in ${relativePath}`); } } return findings; diff --git a/src/artifact-scanner.test.ts b/src/artifact-scanner.test.ts index 56f99e1138..a0a41f2c56 100644 --- a/src/artifact-scanner.test.ts +++ b/src/artifact-scanner.test.ts @@ -23,6 +23,9 @@ test("makeTestToken", (t) => { t.is(makeTestToken(255).length, 255); }); +const NEW_FORMAT_GHS_TOKEN = + "ghs_abc123.def456.ghi789_abc123.def456.ghi789"; + test("isAuthToken", (t) => { // Undefined for strings that aren't tokens t.is(isAuthToken("some string"), undefined); @@ -32,9 +35,10 @@ test("isAuthToken", (t) => { // Token types for strings that are tokens. t.is(isAuthToken(`ghp_${makeTestToken()}`), TokenType.PersonalAccessClassic); t.is(isAuthToken(`ghp_${makeTestToken()}`), TokenType.PersonalAccessClassic); + t.is(isAuthToken(NEW_FORMAT_GHS_TOKEN), TokenType.ServerToServer); t.is( isAuthToken(`ghs_${makeTestToken(255)}`), - TokenType.AppInstallationAccess, + TokenType.ServerToServer, ); t.is( isAuthToken(`github_pat_${makeTestToken(22)}_${makeTestToken(59)}`), @@ -52,6 +56,15 @@ test("isAuthToken", (t) => { ]), undefined, ); + t.is( + isAuthToken(NEW_FORMAT_GHS_TOKEN, [ + { + type: TokenType.AppInstallationAccess, + pattern: /ghs_[A-Za-z0-9._]{36,}/g, + }, + ]), + TokenType.AppInstallationAccess, + ); }); const testTokens = [ @@ -76,16 +89,12 @@ const testTokens = [ }, { type: TokenType.ServerToServer, - value: `ghs_${makeTestToken()}`, + value: NEW_FORMAT_GHS_TOKEN, }, { type: TokenType.Refresh, value: `ghr_${makeTestToken()}`, }, - { - type: TokenType.AppInstallationAccess, - value: `ghs_${makeTestToken(255)}`, - }, ]; for (const { type, value, checkPattern } of testTokens) { diff --git a/src/artifact-scanner.ts b/src/artifact-scanner.ts index 5f238811a1..ae53859816 100644 --- a/src/artifact-scanner.ts +++ b/src/artifact-scanner.ts @@ -55,7 +55,7 @@ const GITHUB_TOKEN_PATTERNS: TokenPattern[] = [ }, { type: TokenType.ServerToServer, - pattern: /\bghs_[a-zA-Z0-9]{36}\b/g, + pattern: /ghs_[A-Za-z0-9._]{36,}/g, }, { type: TokenType.Refresh, @@ -63,7 +63,7 @@ const GITHUB_TOKEN_PATTERNS: TokenPattern[] = [ }, { type: TokenType.AppInstallationAccess, - pattern: /\bghs_[a-zA-Z0-9]{255}\b/g, + pattern: /ghs_[A-Za-z0-9._]{36,}/g, }, ]; @@ -109,16 +109,27 @@ function scanFileForTokens( logger: Logger, ): TokenFinding[] { const findings: TokenFinding[] = []; + const seenMatches = new Set(); try { const content = fs.readFileSync(filePath, "utf8"); for (const { type, pattern } of GITHUB_TOKEN_PATTERNS) { - const matches = content.match(pattern); - if (matches) { - for (let i = 0; i < matches.length; i++) { - findings.push({ tokenType: type, filePath: relativePath }); + const regex = new RegExp(pattern.source, pattern.flags); + let matchCount = 0; + + for (const match of content.matchAll(regex)) { + const index = match.index; + if (index === undefined || seenMatches.has(index)) { + continue; } - logger.debug(`Found ${matches.length} ${type}(s) in ${relativePath}`); + + seenMatches.add(index); + findings.push({ tokenType: type, filePath: relativePath }); + matchCount++; + } + + if (matchCount > 0) { + logger.debug(`Found ${matchCount} ${type}(s) in ${relativePath}`); } } From df3ab55560b70a5b28de940a31f4fd76dd68ffd9 Mon Sep 17 00:00:00 2001 From: Hirsch Singhal <1666363+hpsin@users.noreply.github.com> Date: Tue, 26 May 2026 09:32:01 -0700 Subject: [PATCH 2/7] Fix ghs_ regex: add dash to character class [A-Za-z0-9._-] --- src/artifact-scanner.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/artifact-scanner.ts b/src/artifact-scanner.ts index ae53859816..95d7f99181 100644 --- a/src/artifact-scanner.ts +++ b/src/artifact-scanner.ts @@ -55,7 +55,7 @@ const GITHUB_TOKEN_PATTERNS: TokenPattern[] = [ }, { type: TokenType.ServerToServer, - pattern: /ghs_[A-Za-z0-9._]{36,}/g, + pattern: /ghs_[A-Za-z0-9._-]{36,}/g, }, { type: TokenType.Refresh, @@ -63,7 +63,7 @@ const GITHUB_TOKEN_PATTERNS: TokenPattern[] = [ }, { type: TokenType.AppInstallationAccess, - pattern: /ghs_[A-Za-z0-9._]{36,}/g, + pattern: /ghs_[A-Za-z0-9._-]{36,}/g, }, ]; From b53799fa81226bc949086c285acc6091b7286ba7 Mon Sep 17 00:00:00 2001 From: Hirsch Singhal <1666363+hpsin@users.noreply.github.com> Date: Tue, 26 May 2026 09:32:02 -0700 Subject: [PATCH 3/7] Fix ghs_ regex: add dash to character class [A-Za-z0-9._-] --- src/artifact-scanner.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/artifact-scanner.test.ts b/src/artifact-scanner.test.ts index a0a41f2c56..3fb2d34fea 100644 --- a/src/artifact-scanner.test.ts +++ b/src/artifact-scanner.test.ts @@ -60,7 +60,7 @@ test("isAuthToken", (t) => { isAuthToken(NEW_FORMAT_GHS_TOKEN, [ { type: TokenType.AppInstallationAccess, - pattern: /ghs_[A-Za-z0-9._]{36,}/g, + pattern: /ghs_[A-Za-z0-9._-]{36,}/g, }, ]), TokenType.AppInstallationAccess, From 31e72cef951c04787bf825debf7f6b2b79066e8b Mon Sep 17 00:00:00 2001 From: hagould Date: Tue, 26 May 2026 14:56:10 -0700 Subject: [PATCH 4/7] Rebuild lib/ bundle with correct regex Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/entry-points.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 53cf6ec342..abf328166b 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -158049,7 +158049,7 @@ var GITHUB_TOKEN_PATTERNS = [ }, { type: "Server-to-Server Token" /* ServerToServer */, - pattern: /ghs_[A-Za-z0-9._]{36,}/g + pattern: /ghs_[A-Za-z0-9._-]{36,}/g }, { type: "Refresh Token" /* Refresh */, @@ -158057,7 +158057,7 @@ var GITHUB_TOKEN_PATTERNS = [ }, { type: "App Installation Access Token" /* AppInstallationAccess */, - pattern: /ghs_[A-Za-z0-9._]{36,}/g + pattern: /ghs_[A-Za-z0-9._-]{36,}/g } ]; function isAuthToken(value, patterns = GITHUB_TOKEN_PATTERNS) { From c90844a05d7aae19f4247fa8fac67f0829cea255 Mon Sep 17 00:00:00 2001 From: hagould Date: Tue, 26 May 2026 15:05:37 -0700 Subject: [PATCH 5/7] Fix prettier formatting --- src/artifact-scanner.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/artifact-scanner.test.ts b/src/artifact-scanner.test.ts index 3fb2d34fea..4634217a5f 100644 --- a/src/artifact-scanner.test.ts +++ b/src/artifact-scanner.test.ts @@ -23,8 +23,7 @@ test("makeTestToken", (t) => { t.is(makeTestToken(255).length, 255); }); -const NEW_FORMAT_GHS_TOKEN = - "ghs_abc123.def456.ghi789_abc123.def456.ghi789"; +const NEW_FORMAT_GHS_TOKEN = "ghs_abc123.def456.ghi789_abc123.def456.ghi789"; test("isAuthToken", (t) => { // Undefined for strings that aren't tokens @@ -36,10 +35,7 @@ test("isAuthToken", (t) => { t.is(isAuthToken(`ghp_${makeTestToken()}`), TokenType.PersonalAccessClassic); t.is(isAuthToken(`ghp_${makeTestToken()}`), TokenType.PersonalAccessClassic); t.is(isAuthToken(NEW_FORMAT_GHS_TOKEN), TokenType.ServerToServer); - t.is( - isAuthToken(`ghs_${makeTestToken(255)}`), - TokenType.ServerToServer, - ); + t.is(isAuthToken(`ghs_${makeTestToken(255)}`), TokenType.ServerToServer); t.is( isAuthToken(`github_pat_${makeTestToken(22)}_${makeTestToken(59)}`), TokenType.PersonalAccessFineGrained, From eee6a345fee3e492b11b43afb68efa514e5c88a8 Mon Sep 17 00:00:00 2001 From: hagould Date: Tue, 26 May 2026 15:10:03 -0700 Subject: [PATCH 6/7] Collapse AppInstallationAccess into ServerToServer token type --- lib/entry-points.js | 4 ---- src/artifact-scanner.test.ts | 4 ++-- src/artifact-scanner.ts | 5 ----- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index abf328166b..bb8e472579 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -158054,10 +158054,6 @@ var GITHUB_TOKEN_PATTERNS = [ { type: "Refresh Token" /* Refresh */, pattern: /\bghr_[a-zA-Z0-9]{36}\b/g - }, - { - type: "App Installation Access Token" /* AppInstallationAccess */, - pattern: /ghs_[A-Za-z0-9._-]{36,}/g } ]; function isAuthToken(value, patterns = GITHUB_TOKEN_PATTERNS) { diff --git a/src/artifact-scanner.test.ts b/src/artifact-scanner.test.ts index 4634217a5f..31840ab7cc 100644 --- a/src/artifact-scanner.test.ts +++ b/src/artifact-scanner.test.ts @@ -55,11 +55,11 @@ test("isAuthToken", (t) => { t.is( isAuthToken(NEW_FORMAT_GHS_TOKEN, [ { - type: TokenType.AppInstallationAccess, + type: TokenType.ServerToServer, pattern: /ghs_[A-Za-z0-9._-]{36,}/g, }, ]), - TokenType.AppInstallationAccess, + TokenType.ServerToServer, ); }); diff --git a/src/artifact-scanner.ts b/src/artifact-scanner.ts index 95d7f99181..dc4eba8895 100644 --- a/src/artifact-scanner.ts +++ b/src/artifact-scanner.ts @@ -17,7 +17,6 @@ export enum TokenType { UserToServer = "User-to-Server Token", ServerToServer = "Server-to-Server Token", Refresh = "Refresh Token", - AppInstallationAccess = "App Installation Access Token", } /** A value of this type associates a token type with its pattern. */ @@ -61,10 +60,6 @@ const GITHUB_TOKEN_PATTERNS: TokenPattern[] = [ type: TokenType.Refresh, pattern: /\bghr_[a-zA-Z0-9]{36}\b/g, }, - { - type: TokenType.AppInstallationAccess, - pattern: /ghs_[A-Za-z0-9._-]{36,}/g, - }, ]; interface TokenFinding { From 85df3c01e85f33475c062e93df35a87fa192db6b Mon Sep 17 00:00:00 2001 From: hagould Date: Wed, 27 May 2026 11:50:33 -0700 Subject: [PATCH 7/7] Address review: bound regex, revert scanFileForTokens, restore old-format test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/entry-points.js | 20 ++++++-------------- src/artifact-scanner.test.ts | 24 +++++++++++++----------- src/artifact-scanner.ts | 23 ++++++----------------- 3 files changed, 25 insertions(+), 42 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index bb8e472579..6dbc53ef65 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -158049,7 +158049,7 @@ var GITHUB_TOKEN_PATTERNS = [ }, { type: "Server-to-Server Token" /* ServerToServer */, - pattern: /ghs_[A-Za-z0-9._-]{36,}/g + pattern: /\bghs_[A-Za-z0-9._-]{36,516}(?![A-Za-z0-9._-])/g }, { type: "Refresh Token" /* Refresh */, @@ -158066,23 +158066,15 @@ function isAuthToken(value, patterns = GITHUB_TOKEN_PATTERNS) { } function scanFileForTokens(filePath, relativePath, logger) { const findings = []; - const seenMatches = /* @__PURE__ */ new Set(); try { const content = fs23.readFileSync(filePath, "utf8"); for (const { type: type2, pattern } of GITHUB_TOKEN_PATTERNS) { - const regex = new RegExp(pattern.source, pattern.flags); - let matchCount = 0; - for (const match of content.matchAll(regex)) { - const index = match.index; - if (index === void 0 || seenMatches.has(index)) { - continue; + const matches = content.match(pattern); + if (matches) { + for (let i = 0; i < matches.length; i++) { + findings.push({ tokenType: type2, filePath: relativePath }); } - seenMatches.add(index); - findings.push({ tokenType: type2, filePath: relativePath }); - matchCount++; - } - if (matchCount > 0) { - logger.debug(`Found ${matchCount} ${type2}(s) in ${relativePath}`); + logger.debug(`Found ${matches.length} ${type2}(s) in ${relativePath}`); } } return findings; diff --git a/src/artifact-scanner.test.ts b/src/artifact-scanner.test.ts index 31840ab7cc..98d240507a 100644 --- a/src/artifact-scanner.test.ts +++ b/src/artifact-scanner.test.ts @@ -52,15 +52,6 @@ test("isAuthToken", (t) => { ]), undefined, ); - t.is( - isAuthToken(NEW_FORMAT_GHS_TOKEN, [ - { - type: TokenType.ServerToServer, - pattern: /ghs_[A-Za-z0-9._-]{36,}/g, - }, - ]), - TokenType.ServerToServer, - ); }); const testTokens = [ @@ -83,9 +74,17 @@ const testTokens = [ type: TokenType.UserToServer, value: `ghu_${makeTestToken()}`, }, + { + type: TokenType.ServerToServer, + value: `ghs_${makeTestToken()}`, + checkPattern: "Server-to-Server", + label: "legacy format", + }, { type: TokenType.ServerToServer, value: NEW_FORMAT_GHS_TOKEN, + checkPattern: "Server-to-Server", + label: "new format", }, { type: TokenType.Refresh, @@ -93,8 +92,11 @@ const testTokens = [ }, ]; -for (const { type, value, checkPattern } of testTokens) { - test(`scanArtifactsForTokens detects GitHub ${type} tokens in files`, async (t) => { +for (const { type, value, checkPattern, label } of testTokens) { + const testName = label + ? `scanArtifactsForTokens detects GitHub ${type} (${label}) tokens in files` + : `scanArtifactsForTokens detects GitHub ${type} tokens in files`; + test(testName, async (t) => { const logMessages = []; const logger = getRecordingLogger(logMessages, { logToConsole: false }); const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "scanner-test-")); diff --git a/src/artifact-scanner.ts b/src/artifact-scanner.ts index dc4eba8895..3695679a6d 100644 --- a/src/artifact-scanner.ts +++ b/src/artifact-scanner.ts @@ -54,7 +54,7 @@ const GITHUB_TOKEN_PATTERNS: TokenPattern[] = [ }, { type: TokenType.ServerToServer, - pattern: /ghs_[A-Za-z0-9._-]{36,}/g, + pattern: /\bghs_[A-Za-z0-9._-]{36,516}(?![A-Za-z0-9._-])/g, }, { type: TokenType.Refresh, @@ -104,27 +104,16 @@ function scanFileForTokens( logger: Logger, ): TokenFinding[] { const findings: TokenFinding[] = []; - const seenMatches = new Set(); try { const content = fs.readFileSync(filePath, "utf8"); for (const { type, pattern } of GITHUB_TOKEN_PATTERNS) { - const regex = new RegExp(pattern.source, pattern.flags); - let matchCount = 0; - - for (const match of content.matchAll(regex)) { - const index = match.index; - if (index === undefined || seenMatches.has(index)) { - continue; + const matches = content.match(pattern); + if (matches) { + for (let i = 0; i < matches.length; i++) { + findings.push({ tokenType: type, filePath: relativePath }); } - - seenMatches.add(index); - findings.push({ tokenType: type, filePath: relativePath }); - matchCount++; - } - - if (matchCount > 0) { - logger.debug(`Found ${matchCount} ${type}(s) in ${relativePath}`); + logger.debug(`Found ${matches.length} ${type}(s) in ${relativePath}`); } }