Introduction
In the commands/webperf.toml configuration file of a web performance monitoring tool, we discovered a critical documentation flaw that could lead developers to hardcode API credentials directly in version-controlled files. Line 11 of the file originally stated: "A CrUX API response (requires CRUX_API_KEY or GOOGLE_API_KEY)" without specifying that these must be environment variables. This seemingly innocent documentation pattern creates a dangerous path: developers reading this might add CRUX_API_KEY="AKIAIOSFODNN7EXAMPLE" directly to the TOML file, committing sensitive credentials to Git repositories where they become permanently accessible to anyone with repository access—even after deletion.
The vulnerability isn't in executable code but in the configuration documentation itself, which failed to establish a clear security boundary. When configuration files don't explicitly enforce secure credential handling patterns, developers working under time pressure will often take the path of least resistance: hardcoding values to "just make it work." This is exactly how API keys end up in public GitHub repositories, leaked in Docker images, and exposed in application logs.
The Vulnerability Explained
The vulnerable documentation in commands/webperf.toml at line 11 read:
- A CrUX API response (requires CRUX_API_KEY or GOOGLE_API_KEY)
This documentation describes when "deep mode" can be activated for web performance analysis. The problem is subtle but critical: the phrasing "requires CRUX_API_KEY or GOOGLE_API_KEY" doesn't specify how these keys should be provided. A developer reading this might reasonably assume they should add a configuration section like:
[webperf]
CRUX_API_KEY = "AIzaSyC7V-Example-Key-With-Special@Chars#123"
GOOGLE_API_KEY = "AKIAIOSFODNN7EXAMPLE"
Once this configuration is committed to version control, the damage is done. Even if the commit is later reverted, the API keys remain in Git history. An attacker who gains read access to the repository—through a compromised developer account, a misconfigured CI/CD system, or a public fork—can extract these keys and use them to:
- Impersonate the legitimate service: Make API calls to Google's CrUX API or other services using the stolen credentials
- Exhaust API quotas: Run up costs or trigger rate limits that impact legitimate usage
- Access sensitive data: Depending on the API key's permissions, retrieve performance data or other information
- Pivot to other systems: Use the compromised credentials as a stepping stone to other services if the same keys are reused
The regression test included in the PR demonstrates the exploitation scenario clearly:
@pytest.mark.parametrize("payload", [
# Exact exploit case - API key in clear text
'CRUX_API_KEY="AKIAIOSFODNN7EXAMPLE"',
# Valid input - normal key format but should be masked
'CRUX_API_KEY="sk_live_51H7qY2KZwlk4xXaP3m8fTc9"',
# Additional adversarial - key with special characters
'GOOGLE_API_KEY="AIzaSyC7V-Example-Key-With-Special@Chars#123"',
])
def test_api_keys_not_exposed_in_output(payload):
"""Invariant: API keys must never appear in command output or logs"""
This test verifies that API keys matching common patterns (AWS "AKIA", Stripe "sk_live", Google "AIza") never appear in command output or logs—a critical invariant for any system handling credentials.
The Fix
The fix updates line 11 of commands/webperf.toml to explicitly require environment variables and warn against hardcoding:
Before:
- A CrUX API response (requires CRUX_API_KEY or GOOGLE_API_KEY)
After:
- A CrUX API response (requires $CRUX_API_KEY or $GOOGLE_API_KEY environment variables — never hard-code these values in config files)
This change makes three critical improvements:
- Explicit environment variable syntax: The
$prefix clearly indicates these must be environment variables, not literal values in the config file - Direct prohibition: The phrase "never hard-code these values in config files" removes any ambiguity about acceptable practices
- Security-first documentation: The fix shifts the default assumption from convenience to security
The change is minimal—just 34 characters added—but it fundamentally alters how developers will interact with this configuration. Instead of writing:
CRUX_API_KEY = "AIzaSyC7V-Example"
Developers will now set environment variables:
export CRUX_API_KEY="AIzaSyC7V-Example"
export GOOGLE_API_KEY="AKIAIOSFODNN7EXAMPLE"
Or in production systems:
# AWS Systems Manager Parameter Store
aws ssm get-parameter --name /prod/crux-api-key --with-decryption
# Kubernetes secrets
kubectl create secret generic api-keys \
--from-literal=crux-api-key='AIzaSyC7V-Example'
The security improvement is substantial: environment variables aren't typically committed to version control, they're easier to rotate without code changes, and they can be managed through dedicated secret management systems with audit logging and access controls.
Prevention & Best Practices
To prevent API key exposure vulnerabilities in your configuration files:
1. Enforce Environment Variable Usage in Documentation
Always document credentials with the $VARIABLE_NAME syntax and include explicit warnings:
# ✅ Good: Clear environment variable syntax
api_key = $API_KEY # Set via environment variable - never hardcode
# ❌ Bad: Ambiguous documentation
api_key = API_KEY # Could be interpreted as literal string
2. Implement Configuration Validation
Add validation logic that rejects literal API key patterns:
import re
def validate_config(config):
"""Reject configurations with hardcoded API keys"""
api_key_patterns = [
r'AKIA[0-9A-Z]{16}', # AWS access key
r'sk_live_[0-9a-zA-Z]{24,}', # Stripe live key
r'AIza[0-9A-Za-z_-]{35}', # Google API key
]
for key, value in config.items():
if isinstance(value, str):
for pattern in api_key_patterns:
if re.search(pattern, value):
raise ValueError(
f"Hardcoded API key detected in config field '{key}'. "
f"Use environment variables instead."
)
3. Use Secret Scanning in CI/CD
Integrate tools like GitGuardian, TruffleHog, or AWS git-secrets into your CI/CD pipeline:
# .github/workflows/security.yml
name: Secret Scanning
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Full history for scanning
- name: TruffleHog Scan
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
4. Adopt Secret Management Systems
For production environments, use dedicated secret management:
# Using AWS Secrets Manager
import boto3
import json
def get_api_key():
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='prod/crux-api-key')
return json.loads(response['SecretString'])['api_key']
5. Implement Least Privilege for API Keys
Create API keys with minimal necessary permissions:
- Use separate keys for development, staging, and production
- Set IP allowlists where supported
- Enable key rotation policies
- Monitor key usage with logging and alerting
6. Regular Security Audits
Periodically scan your repositories for exposed credentials:
# Scan Git history for API keys
git log -p | grep -E 'AKIA[0-9A-Z]{16}|sk_live_|AIza[0-9A-Za-z_-]{35}'
# Use dedicated tools
trufflehog git file://. --since-commit main --only-verified
Key Takeaways
-
Configuration documentation must explicitly enforce secure patterns: The
commands/webperf.tomlvulnerability shows that ambiguous documentation ("requires CRUX_API_KEY") can lead developers to hardcode credentials. Always use$VARIABLE_NAMEsyntax and include explicit warnings. -
Environment variables are a minimum baseline, not a complete solution: While the fix correctly mandates environment variables over hardcoded values, production systems should use dedicated secret management services (AWS Secrets Manager, HashiCorp Vault) with rotation, audit logging, and access controls.
-
Secret scanning must cover configuration files, not just code: The regression test validates that API key patterns like "AKIA", "sk_live", and "AIza" never appear in output—this same validation should run in CI/CD to catch hardcoded credentials before they reach production.
-
Git history never forgets: Once an API key is committed, it remains in repository history even after deletion. Prevention through clear documentation and validation is far more effective than remediation after exposure.
-
The 2-step exploitation chain is trivial: An attacker needs only (1) read access to the repository and (2) the ability to make API calls—no sophisticated techniques required. This makes prevention critical for any configuration file handling credentials.
How Orbis AppSec Detected This
- Source: Configuration file documentation at
commands/webperf.toml:11that suggests API key usage without specifying secure handling - Sink: Potential hardcoded credential values in TOML configuration that could be committed to version control
- Missing control: No explicit requirement for environment variables, no validation rejecting literal API key patterns, no warning against hardcoding credentials
- CWE: CWE-798 (Use of Hard-coded Credentials)
- Fix: Updated documentation to require
$CRUX_API_KEYand$GOOGLE_API_KEYenvironment variables with explicit warning: "never hard-code these values in config files"
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
The API key exposure vulnerability in commands/webperf.toml demonstrates that security issues don't always manifest as exploitable code—sometimes they hide in documentation that fails to establish clear security boundaries. By updating a single line of configuration documentation to explicitly require environment variables and warn against hardcoding, this fix prevents a common and dangerous pattern: developers committing sensitive credentials to version control.
The lesson extends beyond this specific file: every configuration option that handles sensitive data must include security guidance as a first-class concern. Documentation isn't just about functionality—it's about establishing secure defaults and making the right choice the easy choice. When you're writing configuration documentation, ask yourself: "Could a developer misinterpret this and create a security vulnerability?" If the answer is yes, add explicit guidance to prevent that misinterpretation.
Remember that API keys in version control create permanent exposure—even after deletion, they remain in Git history accessible to anyone who gains repository access. Prevention through clear documentation, validation, and secret scanning is far more effective than trying to remediate after credentials are exposed.