diff --git a/README.md b/README.md
index 9b6176d..a549a7d 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,9 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
     # Default: true
     persist-credentials: ''
 
+    # Custom git credential helper
+    custom-credential-helper: ''
+
     # Relative path under $GITHUB_WORKSPACE to place the repository
     path: ''
 
diff --git a/action.yml b/action.yml
index 75d5ae2..5208bae 100644
--- a/action.yml
+++ b/action.yml
@@ -52,6 +52,8 @@ inputs:
   persist-credentials:
     description: 'Whether to configure the token or SSH key with the local git config'
     default: true
+  custom-credential-helper:
+    description: 'Custom git credential helper'
   path:
     description: 'Relative path under $GITHUB_WORKSPACE to place the repository'
   clean:
diff --git a/dist/index.js b/dist/index.js
index e128adf..c902fe7 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -166,13 +166,16 @@ class GitAuthHelper {
         this.temporaryHomePath = '';
         this.git = gitCommandManager;
         this.settings = gitSourceSettings || {};
-        // Token auth header
+        this.credentialConfigKey = `credential.helper`;
+        const runnerTemp = process.env['RUNNER_TEMP'] || '';
+        assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
+        const uniqueId = (0, uuid_1.v4)();
+        this.credentialStorePath = path.join(runnerTemp, `${uniqueId}_credential_store`);
+        this.credentialConfigValue = `store --file ${this.credentialStorePath}`;
         const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl);
-        this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader`; // "origin" is SCHEME://HOSTNAME[:PORT]
-        const basicCredential = Buffer.from(`x-access-token:${this.settings.authToken}`, 'utf8').toString('base64');
-        core.setSecret(basicCredential);
-        this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`;
-        this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}`;
+        serverUrl.username = `x-access-token`;
+        serverUrl.password = this.settings.authToken;
+        this.tokenCredential = serverUrl.href;
         // Instead of SSH URL
         this.insteadOfKey = `url.${serverUrl.origin}/.insteadOf`; // "origin" is SCHEME://HOSTNAME[:PORT]
         this.insteadOfValues.push(`git@${serverUrl.hostname}:`);
@@ -246,7 +249,7 @@ class GitAuthHelper {
             catch (err) {
                 // Unset in case somehow written to the real global config
                 core.info('Encountered an error when attempting to configure token. Attempting unconfigure.');
-                yield this.git.tryConfigUnset(this.tokenConfigKey, true);
+                yield this.git.tryConfigUnset(this.credentialConfigKey, true);
                 throw err;
             }
         });
@@ -256,18 +259,12 @@ class GitAuthHelper {
             // Remove possible previous HTTPS instead of SSH
             yield this.removeGitConfig(this.insteadOfKey, true);
             if (this.settings.persistCredentials) {
-                // Configure a placeholder value. This approach avoids the credential being captured
-                // by process creation audit events, which are commonly logged. For more information,
-                // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
-                const output = yield this.git.submoduleForeach(
-                // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
-                `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules);
-                // Replace the placeholder
-                const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [];
-                for (const configPath of configPaths) {
-                    core.debug(`Replacing token placeholder in '${configPath}'`);
-                    yield this.replaceTokenPlaceholder(configPath);
+                if (this.settings.customCredentialHelper) {
+                    yield this.git.submoduleForeach(`sh -c "git config --local --add '${this.credentialConfigKey}' '${this.settings.customCredentialHelper}' && git config --local 'credential.useHttpPath' 'true'"`, this.settings.nestedSubmodules);
                 }
+                yield this.git.submoduleForeach(
+                // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
+                `sh -c "git config --local --add '${this.credentialConfigKey}' '${this.credentialConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules);
                 if (this.settings.sshKey) {
                     // Configure core.sshCommand
                     yield this.git.submoduleForeach(`git config --local '${SSH_COMMAND_KEY}' '${this.sshCommand}'`, this.settings.nestedSubmodules);
@@ -306,7 +303,7 @@ class GitAuthHelper {
             const runnerTemp = process.env['RUNNER_TEMP'] || '';
             assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
             const uniqueId = (0, uuid_1.v4)();
-            this.sshKeyPath = path.join(runnerTemp, uniqueId);
+            this.sshKeyPath = path.join(runnerTemp, `${uniqueId}_ssh_key`);
             stateHelper.setSshKeyPath(this.sshKeyPath);
             yield fs.promises.mkdir(runnerTemp, { recursive: true });
             yield fs.promises.writeFile(this.sshKeyPath, this.settings.sshKey.trim() + '\n', { mode: 0o600 });
@@ -357,30 +354,17 @@ class GitAuthHelper {
         return __awaiter(this, void 0, void 0, function* () {
             // Validate args
             assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations');
+            stateHelper.setCredentialStorePath(this.credentialStorePath);
+            yield fs.promises.writeFile(this.credentialStorePath, this.tokenCredential);
             // Default config path
             if (!configPath && !globalConfig) {
                 configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config');
             }
-            // Configure a placeholder value. This approach avoids the credential being captured
-            // by process creation audit events, which are commonly logged. For more information,
-            // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
-            yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, globalConfig);
-            // Replace the placeholder
-            yield this.replaceTokenPlaceholder(configPath || '');
-        });
-    }
-    replaceTokenPlaceholder(configPath) {
-        return __awaiter(this, void 0, void 0, function* () {
-            assert.ok(configPath, 'configPath is not defined');
-            let content = (yield fs.promises.readFile(configPath)).toString();
-            const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue);
-            if (placeholderIndex < 0 ||
-                placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)) {
-                throw new Error(`Unable to replace auth placeholder in ${configPath}`);
+            if (this.settings.customCredentialHelper) {
+                yield this.git.config(this.credentialConfigKey, this.settings.customCredentialHelper, globalConfig, true);
+                yield this.git.config('credential.useHttpPath', 'true', globalConfig);
             }
-            assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined');
-            content = content.replace(this.tokenPlaceholderConfigValue, this.tokenConfigValue);
-            yield fs.promises.writeFile(configPath, content);
+            yield this.git.config(this.credentialConfigKey, this.credentialConfigValue, globalConfig, true);
         });
     }
     removeSsh() {
@@ -413,8 +397,19 @@ class GitAuthHelper {
     }
     removeToken() {
         return __awaiter(this, void 0, void 0, function* () {
-            // HTTP extra header
-            yield this.removeGitConfig(this.tokenConfigKey);
+            var _a;
+            // Credential Helper
+            const credentialStorePath = this.credentialStorePath || stateHelper.CredentialStorePath;
+            if (credentialStorePath) {
+                try {
+                    yield io.rmRF(credentialStorePath);
+                }
+                catch (err) {
+                    core.debug(`${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
+                    core.warning(`Failed to remove credential store '${credentialStorePath}'`);
+                }
+            }
+            yield this.removeGitConfig(this.credentialConfigKey);
         });
     }
     removeGitConfig(configKey_1) {
@@ -1826,6 +1821,8 @@ function getInputs() {
         // Persist credentials
         result.persistCredentials =
             (core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE';
+        // Custom credential helper
+        result.customCredentialHelper = core.getInput('custom-credential-helper');
         // Workflow organization ID
         result.workflowOrganizationId =
             yield workflowContextHelper.getOrganizationId();
@@ -2347,7 +2344,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
     return result;
 };
 Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.setSafeDirectory = exports.setSshKnownHostsPath = exports.setSshKeyPath = exports.setRepositoryPath = exports.SshKnownHostsPath = exports.SshKeyPath = exports.PostSetSafeDirectory = exports.RepositoryPath = exports.IsPost = void 0;
+exports.setCredentialStorePath = exports.setSafeDirectory = exports.setSshKnownHostsPath = exports.setSshKeyPath = exports.setRepositoryPath = exports.CredentialStorePath = exports.SshKnownHostsPath = exports.SshKeyPath = exports.PostSetSafeDirectory = exports.RepositoryPath = exports.IsPost = void 0;
 const core = __importStar(__nccwpck_require__(2186));
 /**
  * Indicates whether the POST action is running
@@ -2369,6 +2366,10 @@ exports.SshKeyPath = core.getState('sshKeyPath');
  * The SSH known hosts path for the POST action. The value is empty during the MAIN action.
  */
 exports.SshKnownHostsPath = core.getState('sshKnownHostsPath');
+/**
+ * The credential store path for git-credential-store
+ */
+exports.CredentialStorePath = core.getState('credentialStorePath');
 /**
  * Save the repository path so the POST action can retrieve the value.
  */
@@ -2402,6 +2403,13 @@ exports.setSafeDirectory = setSafeDirectory;
 if (!exports.IsPost) {
     core.saveState('isPost', 'true');
 }
+/**
+ * Save the credential store path so the POST action can retrieve the value.
+ */
+function setCredentialStorePath(credentialStorePath) {
+    core.saveState('credentialStorePath', credentialStorePath);
+}
+exports.setCredentialStorePath = setCredentialStorePath;
 
 
 /***/ }),
diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts
index 126e8e5..13570df 100644
--- a/src/git-auth-helper.ts
+++ b/src/git-auth-helper.ts
@@ -34,9 +34,10 @@ export function createAuthHelper(
 class GitAuthHelper {
   private readonly git: IGitCommandManager
   private readonly settings: IGitSourceSettings
-  private readonly tokenConfigKey: string
-  private readonly tokenConfigValue: string
-  private readonly tokenPlaceholderConfigValue: string
+  private readonly credentialConfigKey: string
+  private readonly credentialConfigValue: string
+  private readonly tokenCredential: string
+  private readonly credentialStorePath: string
   private readonly insteadOfKey: string
   private readonly insteadOfValues: string[] = []
   private sshCommand = ''
@@ -51,16 +52,20 @@ class GitAuthHelper {
     this.git = gitCommandManager
     this.settings = gitSourceSettings || ({} as unknown as IGitSourceSettings)
 
-    // Token auth header
+    this.credentialConfigKey = `credential.helper`
+    const runnerTemp = process.env['RUNNER_TEMP'] || ''
+    assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
+    const uniqueId = uuid()
+    this.credentialStorePath = path.join(
+      runnerTemp,
+      `${uniqueId}_credential_store`
+    )
+    this.credentialConfigValue = `store --file ${this.credentialStorePath}`
+
     const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl)
-    this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader` // "origin" is SCHEME://HOSTNAME[:PORT]
-    const basicCredential = Buffer.from(
-      `x-access-token:${this.settings.authToken}`,
-      'utf8'
-    ).toString('base64')
-    core.setSecret(basicCredential)
-    this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`
-    this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}`
+    serverUrl.username = `x-access-token`
+    serverUrl.password = this.settings.authToken
+    this.tokenCredential = serverUrl.href
 
     // Instead of SSH URL
     this.insteadOfKey = `url.${serverUrl.origin}/.insteadOf` // "origin" is SCHEME://HOSTNAME[:PORT]
@@ -143,7 +148,7 @@ class GitAuthHelper {
       core.info(
         'Encountered an error when attempting to configure token. Attempting unconfigure.'
       )
-      await this.git.tryConfigUnset(this.tokenConfigKey, true)
+      await this.git.tryConfigUnset(this.credentialConfigKey, true)
       throw err
     }
   }
@@ -153,23 +158,19 @@ class GitAuthHelper {
     await this.removeGitConfig(this.insteadOfKey, true)
 
     if (this.settings.persistCredentials) {
-      // Configure a placeholder value. This approach avoids the credential being captured
-      // by process creation audit events, which are commonly logged. For more information,
-      // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
-      const output = await this.git.submoduleForeach(
+      if (this.settings.customCredentialHelper) {
+        await this.git.submoduleForeach(
+          `sh -c "git config --local --add '${this.credentialConfigKey}' '${this.settings.customCredentialHelper}' && git config --local 'credential.useHttpPath' 'true'"`,
+          this.settings.nestedSubmodules
+        )
+      }
+
+      await this.git.submoduleForeach(
         // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
-        `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`,
+        `sh -c "git config --local --add '${this.credentialConfigKey}' '${this.credentialConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`,
         this.settings.nestedSubmodules
       )
 
-      // Replace the placeholder
-      const configPaths: string[] =
-        output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []
-      for (const configPath of configPaths) {
-        core.debug(`Replacing token placeholder in '${configPath}'`)
-        await this.replaceTokenPlaceholder(configPath)
-      }
-
       if (this.settings.sshKey) {
         // Configure core.sshCommand
         await this.git.submoduleForeach(
@@ -210,7 +211,7 @@ class GitAuthHelper {
     const runnerTemp = process.env['RUNNER_TEMP'] || ''
     assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
     const uniqueId = uuid()
-    this.sshKeyPath = path.join(runnerTemp, uniqueId)
+    this.sshKeyPath = path.join(runnerTemp, `${uniqueId}_ssh_key`)
     stateHelper.setSshKeyPath(this.sshKeyPath)
     await fs.promises.mkdir(runnerTemp, {recursive: true})
     await fs.promises.writeFile(
@@ -282,40 +283,31 @@ class GitAuthHelper {
       'Unexpected configureToken parameter combinations'
     )
 
+    stateHelper.setCredentialStorePath(this.credentialStorePath)
+    await fs.promises.writeFile(this.credentialStorePath, this.tokenCredential)
+
     // Default config path
     if (!configPath && !globalConfig) {
       configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config')
     }
 
-    // Configure a placeholder value. This approach avoids the credential being captured
-    // by process creation audit events, which are commonly logged. For more information,
-    // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
-    await this.git.config(
-      this.tokenConfigKey,
-      this.tokenPlaceholderConfigValue,
-      globalConfig
-    )
+    if (this.settings.customCredentialHelper) {
+      await this.git.config(
+        this.credentialConfigKey,
+        this.settings.customCredentialHelper,
+        globalConfig,
+        true
+      )
 
-    // Replace the placeholder
-    await this.replaceTokenPlaceholder(configPath || '')
-  }
-
-  private async replaceTokenPlaceholder(configPath: string): Promise<void> {
-    assert.ok(configPath, 'configPath is not defined')
-    let content = (await fs.promises.readFile(configPath)).toString()
-    const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue)
-    if (
-      placeholderIndex < 0 ||
-      placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)
-    ) {
-      throw new Error(`Unable to replace auth placeholder in ${configPath}`)
+      await this.git.config('credential.useHttpPath', 'true', globalConfig)
     }
-    assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined')
-    content = content.replace(
-      this.tokenPlaceholderConfigValue,
-      this.tokenConfigValue
+
+    await this.git.config(
+      this.credentialConfigKey,
+      this.credentialConfigValue,
+      globalConfig,
+      true
     )
-    await fs.promises.writeFile(configPath, content)
   }
 
   private async removeSsh(): Promise<void> {
@@ -346,8 +338,20 @@ class GitAuthHelper {
   }
 
   private async removeToken(): Promise<void> {
-    // HTTP extra header
-    await this.removeGitConfig(this.tokenConfigKey)
+    // Credential Helper
+    const credentialStorePath =
+      this.credentialStorePath || stateHelper.CredentialStorePath
+    if (credentialStorePath) {
+      try {
+        await io.rmRF(credentialStorePath)
+      } catch (err) {
+        core.debug(`${(err as any)?.message ?? err}`)
+        core.warning(
+          `Failed to remove credential store '${credentialStorePath}'`
+        )
+      }
+    }
+    await this.removeGitConfig(this.credentialConfigKey)
   }
 
   private async removeGitConfig(
diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts
index 4e41ac3..ab122e9 100644
--- a/src/git-source-settings.ts
+++ b/src/git-source-settings.ts
@@ -104,6 +104,11 @@ export interface IGitSourceSettings {
    */
   persistCredentials: boolean
 
+  /**
+   * Use following command/script as value for "credential.<URL>.helper"
+   */
+  customCredentialHelper: string | undefined
+
   /**
    * Organization ID for the currently running workflow (used for auth settings)
    */
diff --git a/src/input-helper.ts b/src/input-helper.ts
index 059232f..807916a 100644
--- a/src/input-helper.ts
+++ b/src/input-helper.ts
@@ -149,6 +149,9 @@ export async function getInputs(): Promise<IGitSourceSettings> {
   result.persistCredentials =
     (core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'
 
+  // Custom credential helper
+  result.customCredentialHelper = core.getInput('custom-credential-helper')
+
   // Workflow organization ID
   result.workflowOrganizationId =
     await workflowContextHelper.getOrganizationId()
diff --git a/src/state-helper.ts b/src/state-helper.ts
index aa3eecc..28d08ae 100644
--- a/src/state-helper.ts
+++ b/src/state-helper.ts
@@ -25,6 +25,11 @@ export const SshKeyPath = core.getState('sshKeyPath')
  */
 export const SshKnownHostsPath = core.getState('sshKnownHostsPath')
 
+/**
+ * The credential store path for git-credential-store
+ */
+export const CredentialStorePath = core.getState('credentialStorePath')
+
 /**
  * Save the repository path so the POST action can retrieve the value.
  */
@@ -58,3 +63,10 @@ export function setSafeDirectory() {
 if (!IsPost) {
   core.saveState('isPost', 'true')
 }
+
+/**
+ * Save the credential store path so the POST action can retrieve the value.
+ */
+export function setCredentialStorePath(credentialStorePath: string) {
+  core.saveState('credentialStorePath', credentialStorePath)
+}