Using dedicated secret stores

A secret store holds sensitive values outside the main codebase. GitHub supports organization, repository, and environment secrets that are encrypted with Libsodium sealed boxes before they reach the platform. Once stored, the values remain encrypted until a workflow explicitly references them. Organization secrets can reach up to 1,000 entries while repository and environment secrets are limited to 100 each. Environment secrets are read only when a job that references the environment begins, which adds an extra layer of control.

OWASP recommends applying least privilege at every layer of the secret management system. Engineers should not have blanket access to all secrets. Instead, configure fine-grained permissions on individual objects so that only the required automation jobs can retrieve specific values.

  • Create secrets through the repository settings or REST API rather than embedding them in files.
  • Register any transformed value, such as a Base64-encoded token, as a separate secret so redaction still works.
  • Remove secrets that are no longer referenced by active workflows.

Injecting secrets through environment variables

Once a secret exists in the store, it must be passed to the running process. The recommended method is to map the secret to an environment variable inside the workflow file. GitHub Actions supplies the secrets context for this purpose. The action or script then reads the variable at runtime without ever seeing the plaintext value in the workflow definition.

Avoid passing secrets on the command line. Command-line arguments can be observed by other users through process listings or captured in security audit events. Environment variables and standard input are safer channels. When an action requires a secret as an input, declare it explicitly in the workflow so the mapping remains visible and auditable.

  • Reference secrets only in jobs that actually need them.
  • Use the GITHUB_TOKEN with the narrowest permission set possible and increase scope only for individual jobs.
  • Test workflows with both valid and invalid secret values to confirm redaction behaves as expected.

Maintaining log hygiene

GitHub Actions automatically redacts the contents of registered secrets from workflow logs. This protection works only when the exact secret value reaches the runner. Structured data such as JSON blobs can defeat redaction because the system looks for exact matches. Therefore, never wrap a secret inside a larger data structure before logging.

OWASP advises logging every action that touches secrets and retaining those logs for at least 90 days. Review logs after changes to verify that no secret appears in plain text. If an unredacted secret reaches a log, delete the log file and rotate the affected secret immediately.

  • Mask any locally generated sensitive value with the ::add-mask:: workflow command before it can appear in output.
  • Audit workflow source and any actions used to ensure secrets are not sent to unintended hosts.
  • Check both STDOUT and STDERR streams, because some tools write errors to standard error.

Rotating secrets on a schedule

Static secrets that never change increase the window of exposure if they are compromised. OWASP recommends automated rotation of static secrets and the use of dynamic secrets when the application can request fresh credentials at startup. Dynamic credentials expire automatically, limiting reuse even if they are captured.

GitHub does not rotate secrets for you. You must implement a rotation process that updates the stored value and any dependent systems. Periodically review registered secrets to confirm each one is still required. Secrets that are no longer needed should be deleted rather than left in place.

  • Set expiration policies through the secret management system so that credentials become invalid after a defined period.
  • Automate the rotation steps to reduce human error during key changes.
  • Verify that applications check secret validity before trusting a retrieved value.

Putting the pieces together

Combine the controls described above into a repeatable workflow. Store secrets only in the platform-provided store, inject them exclusively through environment variables, enforce log redaction, and rotate values on a schedule. Review the source of every action that receives a secret to prevent accidental exfiltration. When these steps are followed consistently, automation can access the credentials it needs without leaving them scattered across repositories or logs.