During one of our recent architectural assessments, we were reviewing a client's application with a broader objective than just understanding the architecture.
As part of the assessment, we were also looking at some of the security and operational practices around the application. While going through the codebases, we came across something that immediately caught our attention: there were API keys and other credentials present directly in the source code.
Finding a key in a codebase may look like a small issue, especially if the application is working correctly. But from a security perspective, it can become a much bigger problem depending on what the key provides access to, where the code is stored and who can access the repository.
We highlighted the finding as part of our assessment.
The client later rotated the affected keys based on our recommendation.
This is a good example of why architectural assessments should not only focus on application structure and technology choices. We should also look for security practices that can create operational risks later.
In this article, we will look at why hardcoded credentials are a problem, how we can identify them, what should happen after finding one, and how we can prevent similar issues from reaching the codebase again.
Why Are Hardcoded API Keys a Problem?
An API key is effectively a credential.
Depending on the system, it may provide access to APIs, cloud services, databases, third-party platforms or other resources.
When the key is stored directly in source code, anyone who can access that code may potentially access the credential as well.
For example:
String API_KEY = "xxxxxxxxxxxxxxxx";
Or:
const apiKey = "xxxxxxxxxxxxxxxx";
The problem becomes even more serious when the repository is accessible to a larger engineering team, external contractors or third-party systems.
If the repository is public, the situation becomes significantly more serious because the credential may already be exposed to the internet.
Even when the repository is private, we shouldn't assume that the credential is safe forever.
Repositories get copied.
Code gets forked.
Developers download source code to their machines.
Backups are created.
CI/CD systems access repositories.
The more places the source code exists, the more difficult it becomes to control the exposure of a credential embedded inside it.
How Do We Find Hardcoded Credentials?
The first step is identifying whether credentials are present in the codebase.
A simple search can sometimes reveal obvious cases.
For example:
grep -Rni "api_key" .
We can search for other common patterns:
grep -Rni "apikey" .
grep -Rni "api-key" .
grep -Rni "password" .
grep -Rni "secret" .
However, simple text searches aren't enough.
Developers may use different variable names, encoded values or configuration formats.
For example:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
CLIENT_SECRET
PRIVATE_KEY
AUTH_TOKEN
DATABASE_PASSWORD
ACCESS_TOKEN
This is why dedicated secret-scanning tools are useful.
Tools such as Gitleaks, TruffleHog and other security scanners can look for patterns that resemble credentials and secrets.
We can also use Trivy for secret scanning as part of our security checks.
For example:
trivy fs --scanners secret .
The objective isn't to blindly treat every finding as a confirmed credential. The result needs to be reviewed and validated.
Finding a Secret Doesn't Mean the Investigation Is Finished
This is probably the most important part.
Suppose we find an API key in a source file.
Removing the line from the code doesn't solve the problem.
We first need to understand whether the credential was actually exposed and whether it is still active.
A useful investigation starts with questions such as:
- What system does the key provide access to?
- What permissions does the key have?
- Is the key still active?
- Who has access to the repository?
- Is the repository public or private?
- Was the key committed to Git?
- Does the key exist in Git history?
- Was the key copied into another repository?
- Is the key present in build artifacts or deployment packages?
- Could the key have been exposed through logs?
The answers determine the severity and the next steps.
Don't Just Delete the Key
One of the most common mistakes is simply removing the credential from the source file.
For example, changing:
const apiKey = "xxxxxxxxxxxxxxxx";
to:
const apiKey = "";
doesn't invalidate the original credential.
If someone already has the key, deleting it from the current version of the source code doesn't take away their access.
The credential itself needs to be revoked or rotated.
Rotate the Credential
Once we confirm that a credential has been exposed, the next step should generally be to rotate or revoke it.
The exact process depends on the system that issued the credential.
For example, if the key belongs to a third-party API, we should use that provider's credential management system to revoke the existing key and generate a new one.
The new credential should then be stored outside the source code.
In our assessment, this was the action taken by the client after we highlighted the finding. The affected keys were rotated based on our recommendation.
This is an important distinction between identifying a security issue and actually reducing the security risk.
What About Git History?
There is another important detail that is easy to miss.
Removing the credential from the latest version of the source code doesn't necessarily remove it from Git history.
Consider this sequence:
Commit 1
API key added
Commit 2
Application changes
Commit 3
API key removed
The key may no longer exist in the latest version of the code, but it may still exist in Commit 1.
Anyone with access to the repository history may still be able to retrieve it.
This is why we should treat an exposed credential as compromised even if the key has already been removed from the current source code.
Rotating the credential is therefore much more important than simply removing the text from the repository.
Store Secrets Outside the Source Code
Once we've removed the hardcoded credential, we need somewhere appropriate to store it.
The application should retrieve the secret from a secure configuration or secret-management system at runtime.
Depending on the environment, this could be:
Cloud secret management services
HashiCorp Vault
Kubernetes Secrets
CI/CD secret stores
Environment-specific configuration systems
The important principle is that credentials shouldn't be part of the application source code.
For example, instead of:
const apiKey = "xxxxxxxxxxxxxxxx";
the application should retrieve the value from its runtime configuration.
const apiKey = process.env.API_KEY;
The exact implementation will depend on the application and deployment environment.
Be Careful With Environment Variables
Moving a secret from source code to an environment variable is an improvement, but environment variables aren't automatically a complete secrets-management solution.
We still need to consider:
- Who can view the environment?
- Where is the environment variable configured?
- Is it visible in CI/CD logs?
- Is it stored securely?
- Can developers retrieve it unnecessarily?
- Is it exposed through debugging or error messages?
The goal should be controlled access to secrets, not simply moving the secret from one location to another.
Add Secret Scanning to CI/CD
Finding a credential during an architectural assessment is useful.
Finding it automatically before the code is merged is even better.
We can introduce secret scanning into the CI/CD pipeline.
For example:
Developer
↓
Commit
↓
Pull Request
↓
Secret Scan
↓
Build
↓
Tests
↓
Security Scan
↓
Deploy
If a potential credential is detected, the pipeline can stop and require the issue to be reviewed.
This moves security closer to the developer and reduces the chance that credentials make their way into production repositories.
Scan More Than Just the Current Code
A common mistake is scanning only the current working directory.
For repositories with a long history, we should also consider Git history.
A secret may have been committed months ago and removed later.
Depending on the tool we're using, we can scan repository history to identify credentials that may have existed in previous commits.
This is especially important when performing a security or architectural assessment on an existing application.
Common Places Where Secrets Hide
During assessments, we shouldn't look only for obvious API key variables.
Secrets can appear in many places.
Some common examples include:
- Source code
- Configuration files
.envfiles- Dockerfiles
- Docker Compose files
- Kubernetes manifests
- CI/CD configuration
- Infrastructure as Code
- Scripts
- Documentation
- Test configuration
- Sample configuration files
Even documentation can accidentally contain a real credential if developers copy production configuration while creating examples.
What About Configuration Files?
Configuration files are another common place for credentials.
For example:
database:
username: admin
password: mypassword
This might not look like source code, but it can create exactly the same security problem.
Configuration should therefore be included in security scanning and code reviews.
Don't Ignore Test Credentials
Test environments also deserve attention.
Teams sometimes assume that test credentials are harmless because they don't provide access to production.
That isn't always true.
A test credential may still provide access to customer information, internal systems or paid third-party services.
We should understand what every credential can access rather than assuming that a credential is safe simply because it belongs to a non-production environment.
A Practical Response Process
When we discover a credential in a codebase, the following process provides a good starting point.
1. Identify the Credential
Determine what the credential belongs to and what system it can access.
2. Assess the Exposure
Check where the credential exists and who could potentially access it.
3. Check Whether It Is Active
An old or revoked credential may not create the same level of risk as an active credential.
4. Review Its Permissions
A read-only API key is different from a credential with administrative access.
5. Rotate or Revoke It
If the credential is active and exposed, rotate or revoke it as quickly as possible.
6. Remove It From the Code
Remove the credential from the current source code and configuration.
7. Review Git History
Determine whether the credential exists in previous commits.
8. Move the Secret to Proper Secret Management
Use the appropriate secret-management mechanism for the environment.
9. Add Preventive Controls
Introduce secret scanning into developer workflows and CI/CD.
This process helps us move from simply identifying a security finding to actually reducing the risk.
Common Mistakes
There are a few mistakes we should avoid.
Deleting the Credential and Moving On
Removing the credential from the latest source code doesn't invalidate it.
We need to rotate or revoke the credential.
Assuming Private Repositories Are Safe
Private repositories reduce exposure, but they don't eliminate it.
Access should still be controlled.
Storing Secrets in Configuration Files
Moving a password from source code into a committed configuration file doesn't solve the underlying problem.
The configuration file is still part of the codebase.
Putting Secrets in CI/CD Logs
Even when credentials aren't stored in source code, they can accidentally appear in build logs.
We should make sure sensitive variables are masked and never printed.
Giving Credentials More Permissions Than Necessary
If an application only needs read access to a service, the credential shouldn't have administrative privileges.
The principle of least privilege should apply to application credentials as well.
What We Should Check During an Architectural Assessment
When performing an architectural assessment, security shouldn't be limited to reviewing authentication and network diagrams.
A practical assessment should also include questions around:
- Where are application secrets stored?
- How are credentials managed?
- Who can access production secrets?
- Are secrets stored in source control?
- Is Git history scanned?
- Are secrets scanned during CI/CD?
- How are credentials rotated?
- What happens when a credential is compromised?
- Are application credentials granted excessive permissions?
- Are secrets exposed in logs or monitoring systems?
These questions can uncover risks that may not be visible from architecture diagrams alone.