From c069a1646ed003c3f545989ad5c718eb592d5e39 Mon Sep 17 00:00:00 2001 From: Tom Harold Date: Mon, 18 May 2026 16:30:54 -0400 Subject: [PATCH 1/2] fix: use Basic auth for git clone/push/fetch GitHub's git server rejects Authorization: Bearer for HTTPS git operations; Basic auth with x-access-token as the username is the supported scheme for GitHub App installation tokens. Co-Authored-By: Claude Sonnet 4.6 --- actions/allocate-build-counter/action.ps1 | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/actions/allocate-build-counter/action.ps1 b/actions/allocate-build-counter/action.ps1 index a745f3e..87c816f 100644 --- a/actions/allocate-build-counter/action.ps1 +++ b/actions/allocate-build-counter/action.ps1 @@ -191,14 +191,20 @@ function Get-ResolvedRepository { return @{ Value = 'local-repo'; Source = 'fallback default' } } +function Get-GitAuthHeader { + param([string] $Token) + $b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("x-access-token:$Token")) + return "Authorization: Basic $b64" +} + function Format-GitArgsForDisplay { param([string[]] $Arguments) # Redact embedded tokens and credentials before logging. # Pattern 1: scheme://userinfo:token@host -> scheme://userinfo:***@host - # Pattern 2: Authorization: Bearer/token ... -> Authorization: Bearer/token *** + # Pattern 2: Authorization: Bearer/token/Basic ... -> Authorization: *** return $Arguments | ForEach-Object { $_ -replace '(://[^:/\s@]+):[^@\s]+@', '$1:***@' ` - -replace '(Authorization: (?:Bearer|token)\s+)\S+', '$1***' + -replace '(Authorization: (?:Bearer|token|Basic)\s+)\S+', '$1***' } } @@ -262,7 +268,7 @@ function Initialize-CounterRepository { Remove-Item -Path $clonePath -Recurse -Force -ErrorAction SilentlyContinue # Clone with token via header to keep it out of git config - $authHeader = "Authorization: Bearer $Token" + $authHeader = Get-GitAuthHeader -Token $Token Invoke-GitCommand @('-c', "http.extraheader=$authHeader", 'clone', '--filter=blob:none', '--no-checkout', '--depth=1', $resolvedUrl, $clonePath) # Ensure remote URL has no embedded credentials @@ -281,7 +287,7 @@ function Get-NextBuildNumber { ) Write-Verbose "Fetching tags for prefix: $TagPrefix" - $fetchArgs = if ($Token) { @('-c', "http.extraheader=Authorization: Bearer $Token") + @('fetch', '--tags', '--force') } else { @('fetch', '--tags', '--force') } + $fetchArgs = if ($Token) { @('-c', "http.extraheader=$(Get-GitAuthHeader -Token $Token)") + @('fetch', '--tags', '--force') } else { @('fetch', '--tags', '--force') } Invoke-GitCommand $fetchArgs -RepoPath $RepoPath -IgnoreErrors $allTags = Invoke-GitCommand @('tag', '-l', "${TagPrefix}*") -RepoPath $RepoPath -IgnoreErrors -CaptureOutput | Where-Object { $_ } @@ -320,7 +326,7 @@ function Push-BuildNumberTag { Invoke-GitCommand @('tag', $NewTag) -RepoPath $RepoPath Write-Verbose "Pushing tag to origin" - $pushArgs = if ($Token) { @('-c', "http.extraheader=Authorization: Bearer $Token") + @('push', 'origin', "refs/tags/${NewTag}") } else { @('push', 'origin', "refs/tags/${NewTag}") } + $pushArgs = if ($Token) { @('-c', "http.extraheader=$(Get-GitAuthHeader -Token $Token)") + @('push', 'origin', "refs/tags/${NewTag}") } else { @('push', 'origin', "refs/tags/${NewTag}") } Invoke-GitCommand $pushArgs -RepoPath $RepoPath -IgnoreErrors if ($LASTEXITCODE -eq 0) { return $true @@ -350,7 +356,7 @@ function Remove-OldBuildNumberTags { if ($tagsToDelete) { Write-Verbose "Cleaning up $($tagsToDelete.Count) old tag(s)" foreach ($oldTag in $tagsToDelete) { - $deleteArgs = if ($Token) { @('-c', "http.extraheader=Authorization: Bearer $Token") + @('push', 'origin', '--delete', $oldTag) } else { @('push', 'origin', '--delete', $oldTag) } + $deleteArgs = if ($Token) { @('-c', "http.extraheader=$(Get-GitAuthHeader -Token $Token)") + @('push', 'origin', '--delete', $oldTag) } else { @('push', 'origin', '--delete', $oldTag) } Invoke-GitCommand $deleteArgs -RepoPath $RepoPath -IgnoreErrors if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to delete old tag $oldTag (git exit code $LASTEXITCODE) - tag will be retried on next allocation" From e3ac9ca8a6555dffbe88d79c17dcb5443f3eeb74 Mon Sep 17 00:00:00 2001 From: Tom Harold Date: Mon, 18 May 2026 16:30:58 -0400 Subject: [PATCH 2/2] test: cover Get-GitAuthHeader and Basic redaction Co-Authored-By: Claude Sonnet 4.6 --- .../tests/utilities.tests.ps1 | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/actions/allocate-build-counter/tests/utilities.tests.ps1 b/actions/allocate-build-counter/tests/utilities.tests.ps1 index 73d081a..f146fab 100644 --- a/actions/allocate-build-counter/tests/utilities.tests.ps1 +++ b/actions/allocate-build-counter/tests/utilities.tests.ps1 @@ -24,6 +24,20 @@ Describe 'Set-GitHubOutput' { } } +Describe 'Get-GitAuthHeader' { + It 'returns Authorization: Basic with base64(x-access-token:TOKEN)' { + $token = 'ghs_placeholder-test-fixture-not-a-credential' + $header = Get-GitAuthHeader -Token $token + $expected = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("x-access-token:$token")) + $header | Should -Be "Authorization: Basic $expected" + } + + It 'starts with Authorization: Basic' { + $header = Get-GitAuthHeader -Token 'any-token' + $header | Should -Match '^Authorization: Basic ' + } +} + Describe 'Format-GitArgsForDisplay' { It 'redacts token in basic-auth URL' { # Use a fixture password that is obviously not a credential to avoid @@ -34,6 +48,14 @@ Describe 'Format-GitArgsForDisplay' { $redacted -join ' ' | Should -Not -Match $fixturePassword } + It 'redacts Basic auth token in http.extraheader config arg' { + $b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('x-access-token:ghs_placeholder-test-fixture-not-a-credential')) + $args1 = @('-c', "http.extraheader=Authorization: Basic $b64", 'clone', 'https://github.com/org/repo.git', '/tmp/x') + $redacted = Format-GitArgsForDisplay -Arguments $args1 + $redacted -join ' ' | Should -Match 'Authorization: Basic \*\*\*' + $redacted -join ' ' | Should -Not -Match $b64 + } + It 'leaves token-free URLs untouched' { $args1 = @('fetch', '--tags', '--force') $redacted = Format-GitArgsForDisplay -Arguments $args1