From ca2f66fc96eb384f95f3334e65e26d794cf0f1cd Mon Sep 17 00:00:00 2001 From: Peter Evans <18365890+peter-evans@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:43:33 +0000 Subject: [PATCH] fix: only remove credentials created by this action instance Previously, removeIncludeIfCredentials() deleted all includeIf.gitdir entries matching git-credentials-*.config, regardless of which action created them. This broke subsequent workflow steps that relied on credentials persisted by other actions (e.g., actions/checkout@v6). Now the cleanup only removes the specific credentials file and config entries created by this action instance, leaving other actions' credentials intact. --- __test__/git-config-helper.int.test.ts | 22 +++++++++--- dist/index.js | 44 ++++++++++-------------- src/git-config-helper.ts | 47 +++++++++++--------------- 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/__test__/git-config-helper.int.test.ts b/__test__/git-config-helper.int.test.ts index 8a53c19..85996cc 100644 --- a/__test__/git-config-helper.int.test.ts +++ b/__test__/git-config-helper.int.test.ts @@ -69,6 +69,18 @@ describe('git-config-helper integration tests', () => { const includeIfKeys = await git.tryGetConfigKeys('^includeIf\\.gitdir:') expect(includeIfKeys.length).toBeGreaterThan(0) + // Count credential includes pointing to this action's credentials file + let credentialIncludesForThisAction = 0 + for (const key of includeIfKeys) { + const values = await git.tryGetConfigValues(key) + for (const value of values) { + if (value === credentialsPath) { + credentialIncludesForThisAction++ + } + } + } + expect(credentialIncludesForThisAction).toBeGreaterThan(0) + await gitConfigHelper.close() // Verify credentials file was removed @@ -78,20 +90,20 @@ describe('git-config-helper integration tests', () => { ) expect(credentialsFilesAfter.length).toBe(0) - // Verify includeIf entries were removed + // Verify includeIf entries pointing to our specific credentials file were removed const includeIfKeysAfter = await git.tryGetConfigKeys( '^includeIf\\.gitdir:' ) - const credentialIncludes: string[] = [] + let credentialIncludesForThisActionAfter = 0 for (const key of includeIfKeysAfter) { const values = await git.tryGetConfigValues(key) for (const value of values) { - if (/git-credentials-[0-9a-f-]+\.config$/i.test(value)) { - credentialIncludes.push(value) + if (value === credentialsPath) { + credentialIncludesForThisActionAfter++ } } } - expect(credentialIncludes.length).toBe(0) + expect(credentialIncludesForThisActionAfter).toBe(0) }) it('tests save and restore of persisted auth (old-style)', async () => { diff --git a/dist/index.js b/dist/index.js index 6dc7025..ecdd195 100644 --- a/dist/index.js +++ b/dist/index.js @@ -979,8 +979,6 @@ class GitCommandManager { else { args.push(globalConfig ? '--global' : '--local'); } - // Use --fixed-value to treat configValue as a literal string, not a regex pattern. - // This is important for file paths which contain regex special characters like '.' args.push('--fixed-value', '--unset', configKey, configValue); const output = yield this.exec(args, { allowAllExitCodes: true }); return output.exitCode === 0; @@ -1363,12 +1361,16 @@ class GitConfigHelper { }); } /** - * Removes includeIf entries that point to git-credentials-*.config files - * and deletes the credentials config files. + * Removes the includeIf entry and credentials config file created by this action instance. + * Only cleans up the specific credentials file tracked in this.credentialsConfigPath, + * leaving credentials created by other actions (e.g., actions/checkout) intact. */ removeIncludeIfCredentials() { return __awaiter(this, void 0, void 0, function* () { - const credentialsPaths = new Set(); + // Only clean up if this action instance created a credentials config file + if (!this.credentialsConfigPath) { + return; + } try { // Get all includeIf.gitdir keys from local config const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:'); @@ -1376,9 +1378,8 @@ class GitConfigHelper { // Get all values for this key const values = yield this.git.tryGetConfigValues(key); for (const value of values) { - // Check if value matches git-credentials-.config pattern - if (this.isCredentialsConfigPath(value)) { - credentialsPaths.add(value); + // Only remove entries pointing to our specific credentials file + if (value === this.credentialsConfigPath) { yield this.git.tryConfigUnsetValue(key, value); core.debug(`Removed includeIf entry: ${key} = ${value}`); } @@ -1389,30 +1390,19 @@ class GitConfigHelper { // Ignore errors during cleanup core.debug(`Error during includeIf cleanup: ${utils.getErrorMessage(e)}`); } - // Delete credentials config files that are under RUNNER_TEMP + // Delete only our credentials config file const runnerTemp = process.env['RUNNER_TEMP']; - if (runnerTemp) { - for (const credentialsPath of credentialsPaths) { - // Only remove files under RUNNER_TEMP for safety - if (credentialsPath.startsWith(runnerTemp)) { - try { - yield fs.promises.unlink(credentialsPath); - core.info(`Removed credentials config file: ${credentialsPath}`); - } - catch (e) { - core.debug(`Could not remove credentials file ${credentialsPath}: ${utils.getErrorMessage(e)}`); - } - } + if (runnerTemp && this.credentialsConfigPath.startsWith(runnerTemp)) { + try { + yield fs.promises.unlink(this.credentialsConfigPath); + core.info(`Removed credentials config file: ${this.credentialsConfigPath}`); + } + catch (e) { + core.debug(`Could not remove credentials file ${this.credentialsConfigPath}: ${utils.getErrorMessage(e)}`); } } }); } - /** - * Tests if a path matches the git-credentials-*.config pattern. - */ - isCredentialsConfigPath(filePath) { - return /git-credentials-[0-9a-f-]+\.config$/i.test(filePath); - } /** * Sets extraheader config directly in .git/config (old-style auth). * Used only for restoring persisted credentials from checkout@v4/v5. diff --git a/src/git-config-helper.ts b/src/git-config-helper.ts index c780644..4af25a0 100644 --- a/src/git-config-helper.ts +++ b/src/git-config-helper.ts @@ -271,11 +271,15 @@ export class GitConfigHelper { } /** - * Removes includeIf entries that point to git-credentials-*.config files - * and deletes the credentials config files. + * Removes the includeIf entry and credentials config file created by this action instance. + * Only cleans up the specific credentials file tracked in this.credentialsConfigPath, + * leaving credentials created by other actions (e.g., actions/checkout) intact. */ private async removeIncludeIfCredentials(): Promise { - const credentialsPaths = new Set() + // Only clean up if this action instance created a credentials config file + if (!this.credentialsConfigPath) { + return + } try { // Get all includeIf.gitdir keys from local config @@ -285,9 +289,8 @@ export class GitConfigHelper { // Get all values for this key const values = await this.git.tryGetConfigValues(key) for (const value of values) { - // Check if value matches git-credentials-.config pattern - if (this.isCredentialsConfigPath(value)) { - credentialsPaths.add(value) + // Only remove entries pointing to our specific credentials file + if (value === this.credentialsConfigPath) { await this.git.tryConfigUnsetValue(key, value) core.debug(`Removed includeIf entry: ${key} = ${value}`) } @@ -298,32 +301,22 @@ export class GitConfigHelper { core.debug(`Error during includeIf cleanup: ${utils.getErrorMessage(e)}`) } - // Delete credentials config files that are under RUNNER_TEMP + // Delete only our credentials config file const runnerTemp = process.env['RUNNER_TEMP'] - if (runnerTemp) { - for (const credentialsPath of credentialsPaths) { - // Only remove files under RUNNER_TEMP for safety - if (credentialsPath.startsWith(runnerTemp)) { - try { - await fs.promises.unlink(credentialsPath) - core.info(`Removed credentials config file: ${credentialsPath}`) - } catch (e) { - core.debug( - `Could not remove credentials file ${credentialsPath}: ${utils.getErrorMessage(e)}` - ) - } - } + if (runnerTemp && this.credentialsConfigPath.startsWith(runnerTemp)) { + try { + await fs.promises.unlink(this.credentialsConfigPath) + core.info( + `Removed credentials config file: ${this.credentialsConfigPath}` + ) + } catch (e) { + core.debug( + `Could not remove credentials file ${this.credentialsConfigPath}: ${utils.getErrorMessage(e)}` + ) } } } - /** - * Tests if a path matches the git-credentials-*.config pattern. - */ - private isCredentialsConfigPath(filePath: string): boolean { - return /git-credentials-[0-9a-f-]+\.config$/i.test(filePath) - } - /** * Sets extraheader config directly in .git/config (old-style auth). * Used only for restoring persisted credentials from checkout@v4/v5.