Security
Key points

Key points

  1. 1A secret belongs in one central store that supplies it at runtime, never in source code, chat messages, client apps or a private repository.
  2. 2Every key should carry the least power it needs: restricted scopes, one key per service and environment, and IP limits where the provider offers them.
  3. 3Scanning at commit time and push time stops most leaks before they enter git history, which is far cheaper than cleaning history afterwards.
  4. 4Treat any exposure as a compromise: rotate first, then check logs, then fix the path that let the key escape.

What counts as a secret

A secret is any value that grants access on its own. The OWASP Secrets Management Cheat Sheet lists the usual set: API keys, database credentials, identity and access management permissions, SSH keys and certificates. It also observes that many organizations keep them hardcoded in plaintext in source code, and that services often share the same secret, which makes it hard to find the source of a leak (OWASP).

For a typical web product, the inventory looks like this:

TypeExamplesBlast radius if leaked
Payment and billing keysPayment processor secret keys, webhook signing secretsCharges, refunds, customer data
Cloud credentialsAccess keys, service account filesEverything in the account
Database credentialsConnection strings, admin passwordsAll stored data
Third-party API keysEmail, SMS, search, AI model providersSpend on your bill, data sent in your name
Signing materialJWT signing keys, TLS private keys, SSH keysImpersonation of your service or users

Not every value in a config file is a secret. Microsoft's Key Vault guidance draws the line clearly: store credentials, connection strings and access keys in the vault, and keep IP addresses, service names and feature flags in ordinary configuration (Microsoft Learn). Mixing the two makes the vault noisy and the config hard to change.

Where secrets should live

The rule is one central store per environment that hands secrets to applications at runtime. Stripe's guidance for its secret keys puts it simply: use your platform's secrets vault to supply keys to your code, and if the platform does not provide one, use environment variables instead (Stripe). A vault keeps the value out of source files, CI configuration and developer machines, and adds an audit log of every access, support for automatic rotation and the ability to revoke access.

OWASP adds two points that matter for small teams (OWASP):

  • Centralize and standardize. It should be immediately clear what each secret is used for and where to find it. Every extra secrets tool adds documentation and confusion.
  • Store metadata with each secret. Record who created it, who to contact about it, what it is used for and what type it is. Without that, nobody knows which service breaks when the secret changes.

Places a secret must never live:

  1. Source code, including private repositories. GitGuardian argues that private repositories are high-value targets because people store secrets in them, and that git "is designed to sprawl": repositories get cloned, forked and handed to every contributor who joins, with full history (GitGuardian).
  2. Client-side code. Stripe warns that attackers unpack applications to search for embedded keys, and that only publishable keys belong in web pages, SDKs and mobile apps (Stripe).
  3. Chat, email and tickets. Stripe says to share secret keys through secret-management tools, not emails or chat messages. GitGuardian lists unencrypted sharing in messaging tools as one of its core don'ts.
  4. Error pages and debug output. Stripe specifically warns against showing environment variables on error pages or exposing diagnostic modes to a broad audience.

Give every key the least power it needs

A leaked key does as much damage as it has permissions. Least privilege applies to keys the same way it applies to people.

  • Use restricted keys. Stripe recommends restricted API keys with limited permissions over unrestricted secret keys. Its example is giving an invoicing system the ability to manage invoices and nothing else (Stripe).
  • One key per service and environment. If the email worker and the web app share a key, you cannot rotate one without breaking the other, and you cannot tell from logs which one misbehaved. OWASP names shared secrets as the reason leaks are hard to trace (OWASP).
  • Limit who can read them. OWASP warns that when a user can read or update a secret, the secret can leak through that user and the machine they used. Engineers should not have access to every secret in the store.
  • Lock keys to your network where possible. Stripe lets you restrict a key to known IP addresses, so a stolen key fails from anywhere else.
  • Separate test and live. Development and staging keys should never touch production data. Stripe asks teams to make sure developers are not using live keys where a sandbox key would do.

Stop leaks before they reach git

Once a secret is committed, it lives in every clone. Removing it later is slow, and the key must be rotated anyway. Prevention therefore happens at three points.

At commit time. Stripe recommends a pre-commit hook that rejects commits containing keys your secret scanner detects (Stripe). GitGuardian makes the same case on developer experience grounds: when a server blocks a push, the secret has to be removed from every commit it appears in, and rewriting local history is not trivial, so catching it before the commit is the least painful option (GitGuardian on push protection). GitGuardian also suggests adding files by name instead of wildcard commands like git add ., which pull in config files and temporary files by accident (GitGuardian).

At push time. GitHub's push protection blocks pushes that contain secrets before they reach the repository and explains why. According to GitHub's documentation, read on 2026-10-17, repository push protection is off by default and an administrator or organization owner must turn it on. Anyone with write access can bypass a block by giving a reason, and each bypass creates an alert and emails the people watching the repository (GitHub Docs). Delegated bypass lets you restrict who may approve exceptions.

After the fact. Secret scanning looks across the entire git history on all branches and raises an alert when it finds a credential. GitHub's documentation also describes partner programs, where a detected key is reported to the issuing provider so it can revoke it (GitHub Docs). Scanners differ in coverage. GitGuardian notes that push protection focuses on highly identifiable token formats and can miss generic credentials such as passwords in connection strings (GitGuardian on push protection), so a generic or custom pattern for your own key formats is worth adding.

A minimal setup for a small team:

1. .gitignore covers .env, *.pem, credential files and local config
2. pre-commit hook runs a secret scanner on staged files
3. push protection enabled on every organization repository
4. secret scanning alerts routed to a channel someone reads
5. a planted fake key is blocked at least once, to prove the chain works

Rotate on a schedule and on demand

Rotation limits how long a stolen key stays useful. OWASP recommends regular rotation so stolen credentials work only for a short time, and notes that the right lifetime ranges from minutes to years depending on what the secret protects. It excludes user passwords from scheduled rotation, in line with NIST, and says to rotate those only on suspicion of compromise (OWASP). Microsoft's Key Vault guidance gives an example interval for service secrets, "for example, 60-90 days," and recommends automating rotation (Microsoft Learn).

Stripe adds a reason to rotate that has nothing to do with theft: periodic rotation confirms that you know where each key is used and that your team can replace one on short notice (Stripe). A team that has never rotated a key will find out during an incident that it lives in four places nobody documented.

The safe way to rotate without downtime is to run two credentials side by side. HashiCorp describes it as a blue and green pattern: keep the current secret, create a second one, move services to the second one, confirm they connect, then retire the first (HashiCorp). Microsoft calls the same idea dual credentials.

Where possible, skip rotation entirely by using short-lived credentials. OWASP recommends dynamic secrets, which are generated per session with a short lease and revoked when no longer needed, to reduce credential reuse (OWASP). Cloud identity federation for CI jobs is the most common example: the job proves who it is and receives a token that expires in minutes.

Secrets in CI and deployment

The CI system holds the most powerful credentials most teams have. OWASP's advice is to treat CI/CD tooling as a production environment: harden and patch it, give developers only the permissions they need to run pipelines, make sure pipeline output does not leak secrets, prevent shell access to runners, and require review before any pipeline is created (OWASP). It also warns that forks of a repository or copies of a job definition must not carry the secret with them.

OWASP lists three acceptable places for a CI secret: in the CI tool's own encrypted settings, which it notes "is not the same as committing it to code"; in a secrets manager that the pipeline reads at run time; or not in CI at all, with the deployed service fetching its own secrets when it starts. The last option keeps the pipeline from ever seeing production credentials.

Two habits close most gaps:

  • Give each pipeline its own service account, scoped to the secrets that pipeline needs.
  • Log every secret read from CI and alert on unusual patterns, such as a job printing an encoded value.

The first hour after a leak

Stripe defines two states. Exposure means the key became visible somewhere it should not have been, such as a public repository, a log file or an email. Compromise means there is evidence of unauthorized use. Its instruction for both is the same: rotate immediately, even if you are not sure anyone saw it, and treat any exposure as a potential compromise (Stripe).

The order of work:

  1. Rotate or revoke the key. Do this before anything else. GitHub's documentation notes that removing a secret from git history is time-intensive and often unnecessary once the credential is revoked (GitHub Docs).
  2. Deploy the replacement to every service that used the old key. Your secret metadata tells you where that is.
  3. Read the provider's logs for requests you do not recognize, from the time of exposure onward. Stripe recommends auditing API request logs routinely, not only after a leak.
  4. Find the path. Was it a commit, a log line, a screenshot, a contractor's laptop? Fix that path, for example by adding a scanner pattern or a log redaction rule.
  5. Write it down. A short note on what leaked, how, what was done and what changed feeds the next review.
FAQ

FAQ

Are environment variables a safe place for secrets?

They are acceptable when the platform offers nothing better, and Stripe lists them as the fallback when no secrets vault is available. The risks are that environment variables show up in crash reports, debug pages and process listings. Mark them as sensitive where the host supports it, and move to a vault when the number of secrets or people grows.

Is a private repository safe enough for secrets?

No. Private repositories are cloned to many machines, forked and shared with every contributor, along with their full history. GitGuardian's position, which most security guidance shares, is that any secret that enters a repository should be considered compromised.

How often should we rotate API keys?

It depends on the key's power and on whether rotation is automated. Microsoft's Key Vault guidance gives 60 to 90 days as an example for service secrets, and OWASP notes that lifetimes range from minutes to years depending on use. Whatever the schedule, rotate immediately on any exposure.

Do we need a dedicated secrets manager as a small team?

Usually the one built into your cloud or hosting platform is enough. What matters is that there is one place, that access is limited by role, and that reads are logged. A separate product starts to pay off when you run across several clouds or need dynamic database credentials.

Should we rewrite git history after leaking a key?

Rotate first, because rewriting history does not remove the key from existing clones, forks or caches. After the key is revoked, cleaning history is optional and mainly reduces noise from scanners. GitHub's own documentation describes history rewriting as time-intensive and often unnecessary once the credential is revoked.

Sources

Sources

  1. https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
  2. https://learn.microsoft.com/en-us/azure/key-vault/secrets/secure-secrets
  3. https://docs.stripe.com/keys-best-practices
  4. https://blog.gitguardian.com/secrets-api-management/
  5. https://blog.gitguardian.com/github-push-protection-enhancing-open-source-security-with-limitations-to-consider/
  6. https://docs.github.com/en/code-security/concepts/secret-security/push-protection
  7. https://docs.github.com/code-security/secret-scanning/about-secret-scanning
  8. https://developer.hashicorp.com/well-architected-framework/secure-systems/secrets/rotate-secrets
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles