Secrets management in code: the definitive guide for development teams
In short
Secrets — API keys, tokens, passwords, and certificates — should never be written into source code. The correct approach combines a secrets vault (Vault, AWS Secrets Manager, or Doppler), environment variables injected at runtime, .gitignore blocking, automated secret scanning with gitleaks or trufflehog, and periodic rotation with least privilege. A single commit with an exposed credential can compromise your entire infrastructure, even after it is deleted from history.
Decripte is a cybersecurity company serving businesses from 1 to 100,000+ employees — from MVPs to scale-ups. A full platform and services, starting with the free Threat Management plan.
Key takeaways
- ›No credential should ever touch the git repository — not in private branches, versioned .env files, or code comments.
- ›Secrets vaults (HashiCorp Vault, AWS Secrets Manager, Doppler) are the only reliable way to store and distribute secrets in production.
- ›Git history is permanent: deleting a file does not remove the credential — you must rewrite history and revoke the secret immediately.
- ›Automated secret scanning (GitHub push protection, gitleaks, trufflehog) catches leaks before merge and should run on every pull request.
- ›Least privilege and periodic rotation limit the blast radius when a credential leaks — access should expire, not last forever.
- ›In CI/CD and containers, secrets are injected via encrypted environment variables or secure volume mounts — never baked into a Dockerfile or pipeline YAML.
Why secrets in code are a critical risk
Hardcoded credentials are behind some of the largest data breaches on record. A git repository stores the entire commit history: even if a developer deletes an API key in a later commit, it remains accessible to anyone with access to the history — and repositories that were once public may have been cloned or indexed by monitoring tools before they were made private.
The risk is not limited to GitHub. CI/CD logs frequently print entire environment variables when a configuration error occurs. Docker images built with ARG or ENV that receive secrets write them into every layer of the image, available to anyone who runs docker history. Accidentally committed .env files account for a significant share of the exposure incidents reported on Have I Been Pwned and cataloged by OWASP.
The OWASP Top 10 lists Security Misconfiguration and Cryptographic Failures as high-impact vectors, and secret exposure maps directly into both categories. NIST SP 800-204C reinforces that credentials with indefinite validity and no rotation are a systemic risk, not just a one-off one. For a startup with a small development team and a fast deploy cadence, a single exposed production token can mean anything from the complete destruction of cloud infrastructure to the leak of every customer's data.
Secrets vaults: the correct architecture
The approach recommended by NIST and the DevSecOps community is to centralize all secrets in a dedicated vault and never let them reside in versioned files or environment variables configured by hand on servers. The leading options are HashiCorp Vault (open source, self-hosted or HCP), AWS Secrets Manager (managed, with native IAM integration and automatic rotation), and Doppler (a SaaS aimed at development teams, with environment sync and CI/CD integrations).
The correct flow is: the application authenticates to the vault using a workload identity (not a fixed password), obtains the secret with a defined scope and lifetime, and uses it in memory without ever writing it to disk or a log. In Kubernetes environments, this translates to the External Secrets Operator or the AWS Secrets and Configuration Provider (ASCP), which mount secrets as ephemeral volumes. In serverless functions such as AWS Lambda, the AWS SDK lets you fetch the secret at cold start and cache it in the instance's memory.
Doppler simplifies this flow for smaller teams: a CLI injects the correct variables for each environment (dev, staging, production) without the developer having to create local .env files. The developer runs doppler run -- npm start and the process receives the secrets as environment variables without them ever touching the file system. This eliminates an entire class of accidents.
Start with visibility
See for free what has already leaked and where your startup is exposed.
Decripte's free Threat Management plan maps vulnerabilities, monitors threats and shows leaked credentials — no credit card and no security team required.
Start free nowDay-to-day best practices for the development team
The first control is .gitignore: every repository should ignore .env, .env.local, .env.*.local, *.pem, *.key, credentials.json, and any file that might contain credentials. GitHub provides per-language .gitignore templates that include these patterns, but the team should review them and add project-specific patterns. The .gitignore file is not sufficient on its own — a developer can force a commit with git add -f — which is why it is the first line of defense, not the only one.
Pre-commit hooks with gitleaks or detect-secrets add an automatic layer: before each commit, the hook scans the diffs for credential patterns (AWS keys, GitHub tokens, JWTs, connection strings) and blocks the commit if it finds anything suspicious. Installation is simple via the pre-commit framework, and the team can share the configuration in the repository. GitHub also offers push protection natively for repositories with GitHub Advanced Security, blocking pushes that contain secrets detected by its ML models.
Least privilege is the principle that limits the blast radius. Each service should have credentials with the minimum permissions needed for its function — a Lambda function that only reads from an S3 bucket does not need permissions for EC2 or RDS. API tokens should be tightly scoped (read vs. write, a specific resource). Periodic rotation — recommended every 90 days or after any team change — ensures that old credentials are invalidated even if they leaked without anyone's knowledge.
What to do when a credential leaks
The first action, before any investigation, is to revoke and rotate. Disable the token or key immediately at the provider — AWS Console, GitHub Settings, Stripe Dashboard, wherever it lives. There is no time to lose trying to understand the impact before removing access: the exploitation window can be minutes. Document the exact time of revocation for the incident timeline.
The next step is to audit usage. AWS CloudTrail access logs, GitHub API logs, database authentication logs — any record of the compromised credential's use should be collected and analyzed. The goal is to determine whether anyone used it in an unauthorized way, which resources were accessed, and whether data was exfiltrated. This analysis determines whether the incident is limited to the credential or whether there is a reportable data breach.
Deleting the commit does not fix it. Git history can be rewritten with git filter-repo (which replaces the older git filter-branch), but this requires a force-push across all branches and does not guarantee that earlier clones did not preserve the history. Platforms like GitHub may have cached the old version. The only action that matters is immediate revocation — cleaning up history is a complementary hygiene measure, not a security solution. NIST SP 800-61 places containment before eradication for exactly this reason.
Secrets in CI/CD pipelines and containers
CI/CD pipelines are high-risk vectors because they run code with production credentials in shared environments. GitHub Actions, GitLab CI, and CircleCI offer native encrypted secret storage that is injected as environment variables only during job execution — they never appear in logs (the value is masked automatically) and are not accessible to forks of public repositories. Never use the YAML env: section for hardcoded values; always use secret references such as ${{ secrets.API_KEY }}.
In Dockerfiles, the ARG instruction should never receive secrets — the value is written into the image's intermediate layers and is visible via docker history. The correct approach is to use BuildKit with --secret, which mounts the secret as a temporary file accessible only during the build and does not persist in any layer. For runtime secrets inside containers, the best practice is to inject them via environment variables in the task definition (ECS task definition, Kubernetes secret, Helm values) referencing the secrets vault — never copying the value into the image.
trufflehog can be integrated into the pipeline as a verification step: it scans the entire repository history and the artifacts produced by the build for known credentials, including secrets that may have slipped past the pre-commit controls. This scan should run on every pull request and block the merge when it finds any occurrences. Combining pre-commit hooks on the developer's machine with scanning in the pipeline creates a layered defense that covers the cases where a hook was skipped or bypassed.
AppSec and DevSecOps for startups: from solo founder to enterprise
Secrets management is one of the fundamental AppSec practices Decripte implements for development teams of all sizes — from startups at the product stage to companies with more than 100,000 employees. The process begins with an exposure assessment: scanning the git history, analyzing Docker images, reviewing pipelines, and mapping every credential in use and its permission scope.
Decripte offers a free Threat Management plan that includes monitoring for credential and company-data exposure on the dark web — a practical way to understand your risk level before investing in controls. For teams that need a full DevSecOps implementation — secret scanning integration, vault setup, rotation policies, and training for the development team — the paid plans cover everything from automation to incident response when a credential leaks.
Practical checklist
- 1
1. Install a secret-scanning pre-commit hook
Add gitleaks or detect-secrets to your repository via the pre-commit framework. Create a .pre-commit-config.yaml file at the project root referencing the hook and run pre-commit install to enable it locally. Share the configuration in the repository so the whole team uses the same scanning.
- 2
2. Audit git history for existing credentials
Run trufflehog git file://. or gitleaks detect --source=. to scan the entire repository history. Identify each credential found, revoke it immediately at the provider, and document the finding. Do not try to delete first — revoke first.
- 3
3. Set up a secrets vault for the production environment
Choose between AWS Secrets Manager, HashiCorp Vault, or Doppler based on your team's size and infrastructure. Migrate all production credentials into the vault and update the application to fetch them at runtime via SDK or CLI — never via a versioned .env file.
- 4
4. Update .gitignore and remove versioned .env files
Add .env, .env.*, *.key, *.pem, and credentials.json to .gitignore. If any of these files are already versioned, remove them with git rm --cached and force a new commit. Verify that the file does not appear in any active branch before continuing.
- 5
5. Enable GitHub push protection and configure CI/CD secrets
In GitHub, enable Secret scanning and push protection under Settings > Code security. In the pipeline (GitHub Actions, GitLab CI, etc.), move all credentials into the CI/CD's native secret storage and replace hardcoded values with references to secret variables. Verify that no sensitive value appears in the execution logs.
- 6
6. Deploy automatic rotation and least-privilege policies
Configure automatic rotation in AWS Secrets Manager (natively supported for RDS, Redshift, and managed secrets) or define a manual process with a documented calendar. Review each credential's permissions and reduce them to the minimum needed for the service's function — unused keys should be revoked immediately.
- 7
7. Establish a credential-leak response runbook
Document the process: revoke at the provider, collect usage logs, determine impact, clean up history if needed, issue a new credential with a revised scope, and record the incident. Test the runbook in a tabletop exercise before a real incident occurs — the speed of your response in the first few minutes defines the scale of the damage.
Frequently asked questions
Can I use environment variables instead of a secrets vault?
Environment variables are the correct way to inject secrets at runtime — but they need to be populated from a vault, not configured manually on servers or hardcoded in configuration files. The vault is the source of truth; the environment variable is the delivery channel to the process. In production, combining the two is the standard recommended by NIST and OWASP.
If the repository is private, do I need to worry about secrets in history?
Yes. Private repositories can become public by accident, through a configuration change, or via the compromise of an account with access. Former collaborators whose access was revoked may have cloned the repository while they still had permission. In addition, internal tools, CI/CD bots, and third-party integrations often have read access to the repository — any of them is an exposure vector.
Does deleting the commit with the credential solve the problem?
No. Deleting or overwriting a commit does not remove the data from git history without an explicit rewrite via git filter-repo, and even then it does not guarantee that clones or caches did not preserve the original version. The only action that solves the security problem is revoking the credential immediately at the provider. Cleaning up history is a complementary hygiene measure, but without revocation the risk remains.
What is the difference between gitleaks and trufflehog?
Both detect secrets in git repositories, but with different approaches. gitleaks is faster and well suited to pre-commit hooks and CI/CD, with rules based on regular expressions and support for custom per-project configuration. trufflehog is more thorough for retrospective scans, with entropy-based detectors and active verification of a secret's validity at the provider (when available). Using both together covers more cases.
How do I manage secrets in local development environments without a .env file?
The Doppler CLI is the most ergonomic solution for small teams: the developer authenticates once (doppler login) and runs doppler run -- command, which injects the configured environment's variables without creating local files. Alternatives include direnv with Vault integration or the 1Password CLI (op run). For teams that need to keep local .env files for compatibility with existing tooling, the file must be in .gitignore and never shared — each developer manages their own.
How often should I rotate secrets?
NIST SP 800-63B recommends rotation when there is an indication of compromise, and organizational policies typically define 90-day cycles for high-criticality credentials. For third-party API keys (Stripe, Twilio, AWS IAM), AWS Secrets Manager supports automatic rotation with zero downtime for RDS services. The most important thing is to have the process documented and tested — an untested rotation often fails at exactly the moment it matters most.
Security for startups and fintechs
From the first round to enterprise: Decripte grows with you.
A full platform and services: threat management, 24x7 SOC, incident response, pentest and compliance (LGPD, ISO, BACEN). Start free and see what has already leaked from your business.
