How Hardcoded AWS Secret Access Keys Happen in Configuration Files and How to Fix Them
Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Hardcoded AWS Secret Access Key in Example Configuration |
| CWE | CWE-798 — Use of Hard-coded Credentials |
| Language | Configuration / Environment File |
| Risk | Developers copying the example file may accidentally commit real credentials in the same format |
| Root Cause | Placeholder value matches the exact format of a real AWS Secret Access Key |
| Fix | Replace with an angle-bracket token that is syntactically invalid as a real credential |
Summary
A hardcoded AWS Secret Access Key pattern was detected in the settings.example configuration file of an nginx AWS credentials module. While the value itself was a placeholder string, its format matched a real AWS secret key pattern, making it a dangerous template that could mislead developers into committing real credentials. The fix replaces the lookalike secret value with an unambiguous placeholder that cannot be mistaken for or used as a real credential.
Introduction
The settings.example file in this nginx AWS credentials library exists for one purpose: to show developers exactly what their environment configuration should look like before they deploy. That's a useful thing to have. But there's a subtle trap hiding in example files — if the placeholder values you choose look too much like real secrets, you've created a template that trains developers to put real secrets in the wrong place.
That's precisely what happened here. At line 3 of settings.example, the AWS_SECRET_ACCESS_KEY variable was set to:
AWS_SECRET_ACCESS_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
That 40-character all-lowercase string is not a real AWS credential — but it is formatted exactly like one. AWS Secret Access Keys are 40-character base64 strings. Semgrep's rule generic.secrets.security.detected-aws-secret-access-key matched it immediately, and for good reason: any automated tool scanning a repository that inherits this file would flag it as a potential live credential.
This matters especially because this is a Node.js library — vulnerabilities and patterns in it propagate to every downstream project that uses it as a dependency or copies its configuration as a starting point.
The Vulnerability Explained
What Made This Risky?
AWS Secret Access Keys follow a well-known format: exactly 40 characters, alphanumeric, base64-encoded. Secret scanning tools — including Semgrep, TruffleHog, GitLeaks, and GitHub's own push protection — use regular expressions to match this pattern. The original placeholder:
# settings.example — BEFORE the fix (line 3)
AWS_SECRET_ACCESS_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
passes that regex check. The string is 40 characters long and composed entirely of valid base64 characters. From a scanner's perspective, this is indistinguishable from a real secret.
The Exploit Primitive
The term "exploit primitive" is apt here. The value itself isn't exploitable — aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa won't authenticate against any real AWS account. But this pattern creates two concrete risks:
Risk 1: Developer Confusion
A developer sets up the project for the first time, copies settings.example to .env, and replaces the obviously fake values — but misses AWS_SECRET_ACCESS_KEY because it already looks like it has a value. They paste their real key next to it in a comment, or accidentally leave a real key in a file they thought was already sanitized.
Risk 2: Downstream Scanner Fatigue
Projects that inherit or copy this configuration will have Semgrep, TruffleHog, or their CI pipeline flag this line on every scan. Teams that see this false positive repeatedly may begin to suppress or ignore the rule — exactly the behavior that allows real secrets to slip through undetected.
Real-World Context: awscredentials.js
This settings.example file is the configuration template for common/etc/nginx/include/awscredentials.js — a module that handles AWS credential fetching via IMDS, ECS, EKS Pod Identity Agent, and STS. The credentials configured here are used to authenticate outbound calls to AWS services. A real secret key in this position would grant full programmatic AWS access scoped to the associated IAM role or user.
The full context of the vulnerable file:
# settings.example — BEFORE the fix
S3_BUCKET_NAME=my-bucket
AWS_ACCESS_KEY_ID=ZZZZZZZZZZZZZZZZZZZZ
AWS_SECRET_ACCESS_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # ← flagged
AWS_SESSION_TOKEN=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
S3_SERVER=s3.us-east-1.amazonaws.com
S3_SERVER_PORT=443
Notice that AWS_ACCESS_KEY_ID uses ZZZZZZZZZZZZZZZZZZZZ — clearly fake, all uppercase Z's — and AWS_SESSION_TOKEN uses bbb... — also clearly fake. Only AWS_SECRET_ACCESS_KEY used a value that could pass a format check as real.
The Fix
The fix is a single-line change in settings.example, but it carries meaningful security weight:
- AWS_SECRET_ACCESS_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ AWS_SECRET_ACCESS_KEY=<YOUR_AWS_SECRET_ACCESS_KEY>
Why This Specific Change Works
The replacement value <YOUR_AWS_SECRET_ACCESS_KEY> has three properties that make it unambiguously safe:
-
Syntactically invalid as a credential: The angle brackets
<and>are not valid base64 characters. No AWS SDK, CLI, or API will accept this as a real secret key. -
Immediately recognizable as a placeholder: The
<YOUR_...>convention is a well-established documentation pattern. Any developer reading this file knows they need to replace it. -
Won't trigger secret scanners: Regex patterns for AWS Secret Access Keys require 40 alphanumeric characters. The angle brackets break the match, so this value will not generate false positives in Semgrep, TruffleHog, or GitHub's push protection.
Before and After
# BEFORE — settings.example line 3
AWS_SECRET_ACCESS_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# AFTER — settings.example line 3
AWS_SECRET_ACCESS_KEY=<YOUR_AWS_SECRET_ACCESS_KEY>
The change is minimal in size but significant in intent: it removes an exploit primitive that could contribute to credential exposure in downstream projects.
Prevention & Best Practices
1. Use Unambiguous Placeholders in Example Files
Always use placeholder formats that cannot be confused with real values. Preferred patterns:
# Good — angle brackets signal "replace this"
AWS_SECRET_ACCESS_KEY=<YOUR_AWS_SECRET_ACCESS_KEY>
# Good — descriptive and clearly fake
DATABASE_PASSWORD=REPLACE_WITH_YOUR_DATABASE_PASSWORD
# Bad — looks like it might be real
AWS_SECRET_ACCESS_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# Bad — too short to be real but still triggers some scanners
API_KEY=abc123
2. Add Secret Scanning to Your CI Pipeline
Integrate secret scanning at the point of code commit, not just in periodic audits:
- Semgrep: Use the
generic.secretsruleset to catch AWS keys, GitHub tokens, and other credential formats - TruffleHog: Scans git history for high-entropy strings and known secret patterns
- GitHub Push Protection: Blocks pushes containing detected secrets before they reach the remote
# Example: Semgrep in GitHub Actions
- name: Semgrep secret scan
uses: semgrep/semgrep-action@v1
with:
config: p/secrets
3. Use .env.example with a .gitignore for .env
The standard pattern for environment configuration:
# .gitignore
.env
*.env
!.env.example
!settings.example
This ensures the example file (with placeholders) is committed, while the real file (with actual credentials) is never tracked.
4. Prefer IAM Roles Over Static Credentials
For production deployments, avoid static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY pairs entirely. The awscredentials.js module already supports IMDS and ECS credential providers — use them:
- EC2 instances: Use instance profiles (IMDS)
- ECS tasks: Use task roles (ECS metadata endpoint)
- EKS pods: Use Pod Identity or IRSA (EKS Pod Identity Agent)
Static credentials should be a last resort, not a default.
5. Rotate Credentials Immediately If Exposure Is Suspected
If a real AWS Secret Access Key is ever committed to a repository — even briefly — treat it as compromised:
- Deactivate the key immediately in the AWS IAM console
- Generate a new key pair
- Audit CloudTrail logs for unauthorized use during the exposure window
- Review IAM policies attached to the key for blast radius assessment
Security Standards
- CWE-798: Use of Hard-coded Credentials — https://cwe.mitre.org/data/definitions/798.html
- OWASP A07:2021 — Identification and Authentication Failures covers credential management weaknesses
- OWASP Secrets Management Cheat Sheet provides detailed guidance on handling secrets safely
Key Takeaways
- The format of a placeholder matters as much as its value:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaais 40 alphanumeric characters — the exact shape of a real AWS Secret Access Key — and will trigger secret scanners even though it's fake. - Example configuration files are a common vector for accidental credential exposure: Developers copy them verbatim; if the placeholder looks real, a real value might end up next to it or replacing it without scrutiny.
- Angle-bracket placeholders (
<YOUR_VALUE>) are the safest choice: They are syntactically invalid for every credential format, visually obvious, and universally recognized as "replace this." - This library's
settings.exampleconfigures credentials forawscredentials.js, which makes outbound calls to AWS IMDS, ECS, EKS, and STS — meaning a real leaked key here could expose significant AWS infrastructure. - False positives from fake-but-realistic placeholders cause scanner fatigue, which is itself a security risk: teams that suppress noisy rules stop catching real secrets.
How Orbis AppSec Detected This
- Source: The
AWS_SECRET_ACCESS_KEYvariable assignment atsettings.example:3, where the placeholder value matched the 40-character alphanumeric pattern of a real AWS Secret Access Key - Sink: The value as written could be directly copied into a real
.envfile and used to authenticate AWS API calls via theawscredentials.jscredential fetching module - Missing control: No unambiguous signal in the placeholder value that it must be replaced; the format was indistinguishable from a real credential to both automated scanners and human reviewers
- CWE: CWE-798 — Use of Hard-coded Credentials
- Fix: Replaced
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaawith<YOUR_AWS_SECRET_ACCESS_KEY>, an angle-bracket token that is syntactically invalid as a credential and clearly communicates intent to the reader
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
A single line in an example configuration file — AWS_SECRET_ACCESS_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa — illustrates how easy it is to inadvertently introduce a security risk through well-intentioned documentation. The value was never meant to be a real credential, but its format made it indistinguishable from one to both automated tools and developers under time pressure.
The fix is as simple as it is effective: replace any placeholder that could pass a format check for a real credential with one that cannot. <YOUR_AWS_SECRET_ACCESS_KEY> communicates the same information to a developer setting up the project, but eliminates the risk of scanner fatigue, accidental credential commits, and downstream confusion in projects that inherit this configuration.
For teams building libraries and frameworks — especially those that handle authentication and cloud credentials like awscredentials.js — the example files you ship are part of your security surface. Treat them accordingly.
References
- CWE-798: Use of Hard-coded Credentials
- OWASP Secrets Management Cheat Sheet
- OWASP A07:2021 — Identification and Authentication Failures
- AWS IAM Best Practices — Use Roles Instead of Long-Term Access Keys
- Semgrep rule: detected-aws-secret-access-key
- harden: aws secret access key detected in settings.example...