Jun 11, 2025
10 min read

How to remove hardcoded secrets from GitHub Actions

How to remove hardcoded secrets from GitHub Actions

TL;DR

Hardcoded secrets in GitHub Actions increase security risk and surface area for breaches. This blog walks through how to replace them with Doppler’s doppler run command, enabling dynamic, secure secret injection at runtime. You'll improve auditability, reduce exposure, and streamline your CI/CD workflows.

GitHub Actions & Doppler

GitHub Actions and popular CI/CD tooling like it have changed the way organizations' development workflows look and have greatly reduced time to market. But let's be honest: managing secrets within them can feel like handling a bomb that can go off at any minute. Hardcoding credentials directly into your workflows, forgetting the .gitignore, or simply pushing the wrong commit is easy to do. Human error will always come into play sooner or later; it's not a matter of if, but when.

The numbers do not lie; over 12.8 million secrets were leaked in GitHub repos in 2024. Reports like GitGuardians' 2024 annual State of secret sprawl confirm that this is a persistent headache among organizations of all sizes. Even some of the largest organizations in the world have had this problem at least once. CVE-2024-26052, where the Azure CLI commands would leak secrets within GitHub Actions Logs.

The numbers do not lie; over 12.8 million secrets were leaked in GitHub repos in 2024. Reports like GitGuardian's 2024 annual State of Secret Sprawl confirm that this is a persistent headache among organizations of all sizes. Even some of the largest organizations in the world have encountered this problem at least once. CVE-2024-26052, where the Azure CLI commands would leak secrets within GitHub Actions logs, exemplifies this issue.

Committing secrets, even to GitHub's encrypted storage, means those secrets are static and often long-lived within the environment workflow, increasing the attack surface of your application and its data. What if there were a way to fetch secrets using JIT (Just-In-Time), directly within your GitHub Actions runtime, without ever having to store them in your codebase or repo secret settings? This is where Doppler comes into play. This blog will walk you through setting up your GitHub Actions security by replacing static, plaintext secrets with Doppler's runtime-secure fetch capabilities. We will discard the risky practices and show you how to hydrate secrets securely and dynamically, exactly when your workflow needs them. Let's go.

Establishing secure secrets at runtime

Instead of fetching secrets during your workflow run, this method automatically pushes secrets from your chosen Doppler project and config directly into the native GitHub Actions Secrets storage for your repository or organization. Your workflow then accesses these secrets just like any other GitHub Action Secret.

Why is this the preferred method?

  • No workflow API rate limit issues: The synchronization happens between Doppler and GitHub separately from your workflow runs. Your jobs pulling secrets from GitHub's native storage don't hit Doppler's API, avoiding potential rate limiting on busy repositories.
  • Automatic secret masking: Secrets synced this way are treated as native GitHub Actions Secrets. GitHub automatically masks these values in your logs, preventing accidental exposure – no need for manual ⁠::add-mask commands in your workflow!
  • Standard workflow syntax: You access secrets using the familiar ⁠${{ secrets.SECRET_NAME }} syntax within your YAML files. It's clean and intuitive.
  • Centralized updates: You still benefit from Doppler's central management. Update a secret in Doppler, and the integration automatically syncs the change to GitHub Actions Secrets automatically!

Let's get into configuring the sync within your Doppler dashboard:

Step 1: Navigate to integrations

Log in to Doppler, go to your Project, and select "Syncs".

Step 2: Choose GitHub Actions

Click to add the GitHub Actions integration.

Step 3: Authorize Doppler

You'll be guided through authorizing Doppler to access your desired GitHub repositories (you can grant access to specific repos or all).

From here, you will be able to configure the GitHub integration. You have the option of choosing between GitHub features like Actions, Dependabot, and Codespaces, selecting which repository secret to target, as well as which Doppler configuration to target.

This will result in you having a synced integration between Doppler and your GitHub secrets within your repository settings.

Using synced secrets in your workflow

Once the sync is active, accessing your secrets in your ./github/workflows/your-workflow.yml can be done using the ${{ secrets.SOME_KEY }} notation that is built into GitHub .yml syntax.

Notice how clean that is? No extra setup steps for Doppler CLI, no ⁠doppler run commands. The secrets managed in Doppler are securely available just like standard GitHub secrets because, thanks to the sync, they are standard GitHub secrets, benefiting from automatic masking and avoiding runtime API calls to Doppler.

For the majority of workflows needing access to secrets managed in Doppler, this direct sync integration offers the best balance of security, performance, and ease of use.

Using the Doppler Fetch Secrets Action

While syncing secrets directly to GitHub Actions is often the preferred route, there might be scenarios where it's not ideal (e.g., needing secrets from many different Doppler configs dynamically, or organization policies restricting direct integration). For these cases, Doppler provides a dedicated GitHub Marketplace Action: Fetch Doppler Secrets.

How it works

This action runs as a step within your workflow. It authenticates to Doppler using a Service Token (which you store in GitHub Secrets), fetches the required secrets from your specified Doppler project/config, and makes them available as outputs for subsequent steps in the same job. Crucially, it also automatically registers these fetched secrets for masking in GitHub Actions logs.

Advantages:

  • Automatic secret masking: Unlike using the Doppler CLI directly with doppler run, this Action handles adding the ::add-mask command for you, preventing accidental exposure of fetched secrets in logs.
  • No need for sync setup: It doesn't require configuring the backend sync integration between Doppler and GitHub.
  • Flexibility: It can potentially fetch secrets from different configs if needed within a workflow (though be mindful of complexity).
  • No Static Doppler Token needed (with OIDC): Enhances security by eliminating the need to manage and potentially rotate a long-lived ⁠DOPPLER_TOKEN. Authentication is handled via trusted identity exchange using GitHub's OIDC provider. Learn more about OIDC setup.
  • Circumvents per-token rate limits (with OIDC): Since each workflow run using OIDC authenticates with a unique, short-lived token, it effectively bypasses the standard per-token API rate limits that could be a concern with static Service Tokens on high-frequency workflows.

Disadvantages:

  • API calls during workflow: Like using doppler run directly, this action makes API calls to Doppler during your workflow execution. On high-frequency workflows or large projects, this could potentially lead to hitting Doppler API rate limits.
  • Slightly more complex workflow: It requires adding the Action step and referencing its outputs in subsequent steps, compared to the direct ${{ secrets.NAME }} syntax used with the Sync method.

Let's walk through using the Fetch Secrets Action:

Step 1: Ensure secrets are in Doppler

(Same as before) Make sure the secrets your workflow needs (e.g., ⁠APIKEY, ⁠DATABASEURL) are configured in the correct Project and Config within your Doppler dashboard.

Step 2: Create a Doppler Service Token

(Same as before) This Action needs credentials to fetch secrets.

  • In your Doppler Project, go to "Access" or "Service Tokens."
  • Create a Service Token scoped to the specific Project and Config the Action needs access to.
  • Copy the token value immediately – you'll only see it once.

Step 3: Store the Doppler Token in GitHub Secrets

(Same as before) This is the only credential you store directly in GitHub for this method.

  • Go to your GitHub Repository: ⁠Settings > ⁠Secrets and variables > ⁠Actions.
  • Click "New repository secret."
  • Name: ⁠DOPPLER_TOKEN
  • Secret: Paste the service token value.
  • Save.

Modify your GitHub Actions workflow

The Fetch Secrets Action provides a good middle ground when the direct Sync integration isn't feasible, offering automatic masking while still centralizing secret management in Doppler. However, always consider the potential impact of API calls during workflow runs compared to the Sync method.

When doppler run makes sense (and it's caveats)

You might encounter or want to consider using the Doppler CLI directly within your workflow via the doppler run command. This command injects secrets fetched from Doppler as environment variables specifically for a single command execution.

How doppler run works

You install the Doppler CLI in a workflow step, then prefix the command that needs secrets with ⁠doppler run. Authentication typically uses a service token stored in GitHub Secrets.

Why this is generally not the first choice

While doppler run offers precise, just-in-time injection, it comes with significant caveats in the context of GitHub Actions:

  • Manual secret masking required: This is the biggest drawback. Unlike the Sync method or the Fetch Secrets Action, doppler run does not automatically mask the secrets it injects. If your script or command outputs these secrets (even accidentally), they will appear in plain text in your GitHub Actions logs unless you manually fetch each sensitive secret and register it for masking using GitHub's '::add-mask::' logging command before it might be printed, as shown in the example above. This adds complexity and risk if not done diligently for every sensitive value.
  • API calls during workflow: Similar to the Fetch Secrets Action, this method makes API calls to Doppler during the workflow run, potentially contributing to API rate limit issues on active repositories.
  • More Setup: Requires explicitly installing the Doppler CLI.

Potential use cases (use with caution)

Direct use of doppler run might be considered only if:

  • The Sync and Fetch Action methods genuinely don't meet a specific, niche requirement.
  • You have very few secrets involved, reducing the burden of manual masking.
  • You fully understand and meticulously implement the necessary ::add-mask:: commands for all sensitive values fetched.
  • API rate limits are not a concern for the specific workflow frequency, which in the majority of cases of the GitHub cloud platform they are.

Recommendation

For most standard GitHub Actions workflows, prefer the Sync Integration (Method 1) for its robustness and automatic masking, or the Fetch Secrets Action (Method 2) as a reliable alternative that also handles masking. Only resort to direct doppler run (Method 3) if you have a compelling reason and are prepared to handle the manual masking requirements carefully.

Securing the future

By integrating Doppler's runtime secret fetching into your actions, you are reducing a massive risk to the SDLC (Software Development Life Cycle) of your application. It's time to stop leaving your keys under the doormat and elevate to the next generation of application security. If you are ready to start securing your workflows today, try Doppler for free and make sure to look at our official documentation.

GitHub Actions FAQs

Even encrypted secrets in GitHub can be exposed through logs or misuse. Static credentials increase the attack surface and are often long-lived.

Enjoying this content? Stay up to date and get our latest blogs, guides, and tutorials.

Related Content

Explore More