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.
This commit is contained in:
parent
64240115db
commit
ca2f66fc96
3 changed files with 54 additions and 59 deletions
|
|
@ -69,6 +69,18 @@ describe('git-config-helper integration tests', () => {
|
||||||
const includeIfKeys = await git.tryGetConfigKeys('^includeIf\\.gitdir:')
|
const includeIfKeys = await git.tryGetConfigKeys('^includeIf\\.gitdir:')
|
||||||
expect(includeIfKeys.length).toBeGreaterThan(0)
|
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()
|
await gitConfigHelper.close()
|
||||||
|
|
||||||
// Verify credentials file was removed
|
// Verify credentials file was removed
|
||||||
|
|
@ -78,20 +90,20 @@ describe('git-config-helper integration tests', () => {
|
||||||
)
|
)
|
||||||
expect(credentialsFilesAfter.length).toBe(0)
|
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(
|
const includeIfKeysAfter = await git.tryGetConfigKeys(
|
||||||
'^includeIf\\.gitdir:'
|
'^includeIf\\.gitdir:'
|
||||||
)
|
)
|
||||||
const credentialIncludes: string[] = []
|
let credentialIncludesForThisActionAfter = 0
|
||||||
for (const key of includeIfKeysAfter) {
|
for (const key of includeIfKeysAfter) {
|
||||||
const values = await git.tryGetConfigValues(key)
|
const values = await git.tryGetConfigValues(key)
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
if (/git-credentials-[0-9a-f-]+\.config$/i.test(value)) {
|
if (value === credentialsPath) {
|
||||||
credentialIncludes.push(value)
|
credentialIncludesForThisActionAfter++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(credentialIncludes.length).toBe(0)
|
expect(credentialIncludesForThisActionAfter).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests save and restore of persisted auth (old-style)', async () => {
|
it('tests save and restore of persisted auth (old-style)', async () => {
|
||||||
|
|
|
||||||
38
dist/index.js
vendored
38
dist/index.js
vendored
|
|
@ -979,8 +979,6 @@ class GitCommandManager {
|
||||||
else {
|
else {
|
||||||
args.push(globalConfig ? '--global' : '--local');
|
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);
|
args.push('--fixed-value', '--unset', configKey, configValue);
|
||||||
const output = yield this.exec(args, { allowAllExitCodes: true });
|
const output = yield this.exec(args, { allowAllExitCodes: true });
|
||||||
return output.exitCode === 0;
|
return output.exitCode === 0;
|
||||||
|
|
@ -1363,12 +1361,16 @@ class GitConfigHelper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Removes includeIf entries that point to git-credentials-*.config files
|
* Removes the includeIf entry and credentials config file created by this action instance.
|
||||||
* and deletes the credentials config files.
|
* Only cleans up the specific credentials file tracked in this.credentialsConfigPath,
|
||||||
|
* leaving credentials created by other actions (e.g., actions/checkout) intact.
|
||||||
*/
|
*/
|
||||||
removeIncludeIfCredentials() {
|
removeIncludeIfCredentials() {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
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 {
|
try {
|
||||||
// Get all includeIf.gitdir keys from local config
|
// Get all includeIf.gitdir keys from local config
|
||||||
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:');
|
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:');
|
||||||
|
|
@ -1376,9 +1378,8 @@ class GitConfigHelper {
|
||||||
// Get all values for this key
|
// Get all values for this key
|
||||||
const values = yield this.git.tryGetConfigValues(key);
|
const values = yield this.git.tryGetConfigValues(key);
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
// Check if value matches git-credentials-<uuid>.config pattern
|
// Only remove entries pointing to our specific credentials file
|
||||||
if (this.isCredentialsConfigPath(value)) {
|
if (value === this.credentialsConfigPath) {
|
||||||
credentialsPaths.add(value);
|
|
||||||
yield this.git.tryConfigUnsetValue(key, value);
|
yield this.git.tryConfigUnsetValue(key, value);
|
||||||
core.debug(`Removed includeIf entry: ${key} = ${value}`);
|
core.debug(`Removed includeIf entry: ${key} = ${value}`);
|
||||||
}
|
}
|
||||||
|
|
@ -1389,30 +1390,19 @@ class GitConfigHelper {
|
||||||
// Ignore errors during cleanup
|
// Ignore errors during cleanup
|
||||||
core.debug(`Error during includeIf cleanup: ${utils.getErrorMessage(e)}`);
|
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'];
|
const runnerTemp = process.env['RUNNER_TEMP'];
|
||||||
if (runnerTemp) {
|
if (runnerTemp && this.credentialsConfigPath.startsWith(runnerTemp)) {
|
||||||
for (const credentialsPath of credentialsPaths) {
|
|
||||||
// Only remove files under RUNNER_TEMP for safety
|
|
||||||
if (credentialsPath.startsWith(runnerTemp)) {
|
|
||||||
try {
|
try {
|
||||||
yield fs.promises.unlink(credentialsPath);
|
yield fs.promises.unlink(this.credentialsConfigPath);
|
||||||
core.info(`Removed credentials config file: ${credentialsPath}`);
|
core.info(`Removed credentials config file: ${this.credentialsConfigPath}`);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
core.debug(`Could not remove credentials file ${credentialsPath}: ${utils.getErrorMessage(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).
|
* Sets extraheader config directly in .git/config (old-style auth).
|
||||||
* Used only for restoring persisted credentials from checkout@v4/v5.
|
* Used only for restoring persisted credentials from checkout@v4/v5.
|
||||||
|
|
|
||||||
|
|
@ -271,11 +271,15 @@ export class GitConfigHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes includeIf entries that point to git-credentials-*.config files
|
* Removes the includeIf entry and credentials config file created by this action instance.
|
||||||
* and deletes the credentials config files.
|
* 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<void> {
|
private async removeIncludeIfCredentials(): Promise<void> {
|
||||||
const credentialsPaths = new Set<string>()
|
// Only clean up if this action instance created a credentials config file
|
||||||
|
if (!this.credentialsConfigPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get all includeIf.gitdir keys from local config
|
// Get all includeIf.gitdir keys from local config
|
||||||
|
|
@ -285,9 +289,8 @@ export class GitConfigHelper {
|
||||||
// Get all values for this key
|
// Get all values for this key
|
||||||
const values = await this.git.tryGetConfigValues(key)
|
const values = await this.git.tryGetConfigValues(key)
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
// Check if value matches git-credentials-<uuid>.config pattern
|
// Only remove entries pointing to our specific credentials file
|
||||||
if (this.isCredentialsConfigPath(value)) {
|
if (value === this.credentialsConfigPath) {
|
||||||
credentialsPaths.add(value)
|
|
||||||
await this.git.tryConfigUnsetValue(key, value)
|
await this.git.tryConfigUnsetValue(key, value)
|
||||||
core.debug(`Removed includeIf entry: ${key} = ${value}`)
|
core.debug(`Removed includeIf entry: ${key} = ${value}`)
|
||||||
}
|
}
|
||||||
|
|
@ -298,31 +301,21 @@ export class GitConfigHelper {
|
||||||
core.debug(`Error during includeIf cleanup: ${utils.getErrorMessage(e)}`)
|
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']
|
const runnerTemp = process.env['RUNNER_TEMP']
|
||||||
if (runnerTemp) {
|
if (runnerTemp && this.credentialsConfigPath.startsWith(runnerTemp)) {
|
||||||
for (const credentialsPath of credentialsPaths) {
|
|
||||||
// Only remove files under RUNNER_TEMP for safety
|
|
||||||
if (credentialsPath.startsWith(runnerTemp)) {
|
|
||||||
try {
|
try {
|
||||||
await fs.promises.unlink(credentialsPath)
|
await fs.promises.unlink(this.credentialsConfigPath)
|
||||||
core.info(`Removed credentials config file: ${credentialsPath}`)
|
core.info(
|
||||||
|
`Removed credentials config file: ${this.credentialsConfigPath}`
|
||||||
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
core.debug(
|
core.debug(
|
||||||
`Could not remove credentials file ${credentialsPath}: ${utils.getErrorMessage(e)}`
|
`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).
|
* Sets extraheader config directly in .git/config (old-style auth).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue