Hardcoded Credentials and Secrets
Hardcoded credentials occur when passwords, API keys, cryptographic keys, or other secrets are embedded directly in source code, configuration files, or build artifacts, making them accessible to anyone with code access.
What is Hardcoded Credentials?
Hardcoded Credentials (CWE-798) is a vulnerability where sensitive authentication material — passwords, API keys, tokens, cryptographic keys, database connection strings, or other secrets — is embedded directly in source code, configuration files committed to version control, or compiled binaries. This practice is dangerous because:
- Source code is not secret — Code is shared among developers, stored in repositories (sometimes public), included in build artifacts, and deployed across environments
- Secrets in code are permanent — Even after removal, secrets persist in version control history, backups, and cached builds
- Credentials grant access — A leaked database password, API key, or SSH key gives attackers direct access to the associated resource
- Rotation is difficult — When credentials are hardcoded, changing them requires code changes, rebuilds, and redeployments
Why it matters
Hardcoded secrets are one of the most common and most consequential security issues:
- GitHub scanning data — GitHub reports detecting over 100 million secret leaks in public repositories in a single year. Automated scanners continuously monitor public repos for credentials.
- Uber breach (2016) — Attackers found AWS credentials hardcoded in a private GitHub repository, leading to access to 57 million user records.
- Supply chain attacks — Secrets in open-source dependencies or Docker images are inherited by all downstream consumers.
- Cloud cost abuse — Leaked cloud provider API keys are used within minutes by automated bots to spin up cryptocurrency mining instances, resulting in bills of tens of thousands of dollars.
- Lateral movement — A single hardcoded credential often provides a foothold that enables further compromise of internal systems.
Automated tools scan public repositories, Docker images, mobile app binaries, and JavaScript bundles continuously. The window between a secret being pushed and it being discovered by attackers is often under 5 minutes.
How exploitation works
Discovery from source code
Attackers search public repositories using patterns:
# GitHub search queries attackers use
"password" language:python filename:.env
"AKIA" language:javascript # AWS access key prefix
"sk_live_" language:ruby # Stripe live API key
"-----BEGIN RSA PRIVATE KEY-----"
"mongodb+srv://" password
Extraction from compiled binaries
Secrets in compiled code are trivially extractable:
# Extract strings from a .NET assembly
strings MyApp.dll | grep -i "password\|secret\|key\|connectionstring"
# Extract from a Java JAR
jar xf app.jar
grep -r "password" --include="*.properties" --include="*.xml" --include="*.class"
# Extract from Android APK
apktool d app.apk
grep -r "api_key\|secret" res/ assets/ smali/
Using leaked credentials
Once an attacker has credentials, exploitation is straightforward:
# AWS key found in code
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
aws s3 ls # List all S3 buckets
aws iam list-users # Enumerate IAM users
# Database connection string found in code
psql "postgresql://admin:SuperSecret123@prod-db.internal:5432/customers"
# Stripe API key found in JavaScript bundle
curl https://api.stripe.com/v1/charges -u sk_live_4eC39HqLyjWDarjtT1zdp7dc:
Vulnerable code examples
C# / ASP.NET
// VULNERABLE: Database password in source code
public class DatabaseConfig
{
private const string ConnectionString =
"Server=prod-db.internal;Database=customers;User Id=admin;Password=Pr0d!Secret#2026;";
public SqlConnection GetConnection()
{
return new SqlConnection(ConnectionString);
}
}
// VULNERABLE: API key in code
public class PaymentService
{
private readonly string _stripeKey = "sk_live_4eC39HqLyjWDarjtT1zdp7dc";
public async Task<Charge> CreateCharge(decimal amount)
{
StripeConfiguration.ApiKey = _stripeKey;
// ...
}
}
Java / Spring
// VULNERABLE: Credentials in application.properties committed to git
// src/main/resources/application.properties
spring.datasource.url=jdbc:mysql://prod-db:3306/myapp
spring.datasource.username=root
spring.datasource.password=MyDBPassword123!
aws.access-key=AKIAIOSFODNN7EXAMPLE
aws.secret-key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
// VULNERABLE: API key hardcoded in Java class
public class EmailService {
private static final String SENDGRID_API_KEY =
"SG.xxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
}
Python
# VULNERABLE: Secrets in Python source
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
DATABASE_URL = "postgresql://admin:password123@db.prod.internal:5432/app"
# VULNERABLE: .env file committed to repository
# .env
SECRET_KEY=django-insecure-abc123def456
STRIPE_SECRET=sk_live_4eC39HqLyjWDarjtT1zdp7dc
Node.js / JavaScript
// VULNERABLE: Secrets in frontend JavaScript (exposed to all users)
const firebaseConfig = {
apiKey: "AIzaSyD-ACTUAL-KEY-HERE",
authDomain: "myproject.firebaseapp.com",
databaseURL: "https://myproject.firebaseio.com",
};
// VULNERABLE: Server-side secrets in code
const stripe = require('stripe')('sk_live_4eC39HqLyjWDarjtT1zdp7dc');
const dbPassword = 'SuperSecretProd2026!';
Go
// VULNERABLE: Hardcoded credentials
func connectDB() (*sql.DB, error) {
dsn := "admin:ProductionPassword!@tcp(db.internal:3306)/myapp"
return sql.Open("mysql", dsn)
}
const slackWebhook = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
Docker / Infrastructure
# VULNERABLE: Secrets in Dockerfile
ENV DATABASE_PASSWORD=MySecret123
ENV AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# VULNERABLE: Secrets in docker-compose.yml committed to git
services:
db:
environment:
POSTGRES_PASSWORD: SuperSecret123
app:
environment:
JWT_SECRET: my-jwt-secret-key
Secure code examples
C# — Use configuration and secret management
// SECURE: Read from environment or secret manager
public class DatabaseConfig
{
private readonly string _connectionString;
public DatabaseConfig(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("Default");
// Reads from environment variables, Azure Key Vault, AWS Secrets Manager, etc.
}
public SqlConnection GetConnection() => new SqlConnection(_connectionString);
}
// SECURE: appsettings.json with placeholder (actual values from env/vault)
// appsettings.json (committed to git — no secrets)
{
"ConnectionStrings": {
"Default": "" // Set via environment variable or secret manager
}
}
// Program.cs
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddAzureKeyVault(new Uri("https://myvault.vault.azure.net/"),
new DefaultAzureCredential());
Java — Use environment variables and secret managers
// SECURE: application.properties with environment variable references
// application.properties (committed to git)
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DATABASE_USER}
spring.datasource.password=${DATABASE_PASSWORD}
aws.access-key=${AWS_ACCESS_KEY_ID}
aws.secret-key=${AWS_SECRET_ACCESS_KEY}
// SECURE: Use AWS Secrets Manager at runtime
public class SecretConfig {
public static String getSecret(String secretName) {
AWSSecretsManager client = AWSSecretsManagerClientBuilder.defaultClient();
GetSecretValueRequest request = new GetSecretValueRequest()
.withSecretId(secretName);
return client.getSecretValue(request).getSecretString();
}
}
Python — Use environment variables
# SECURE: Read from environment
import os
DATABASE_URL = os.environ["DATABASE_URL"]
AWS_ACCESS_KEY = os.environ["AWS_ACCESS_KEY_ID"]
# SECURE: .env file in .gitignore (never committed)
# .gitignore
.env
*.pem
*.key
Node.js — Use environment variables and vault
// SECURE: Environment variables (set by deployment platform)
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
};
// SECURE: Use HashiCorp Vault
const vault = require('node-vault')({ endpoint: process.env.VAULT_ADDR });
const secret = await vault.read('secret/data/myapp');
const dbPassword = secret.data.data.db_password;
Docker — Use secrets
# SECURE: Docker secrets (not environment variables)
services:
app:
secrets:
- db_password
- jwt_secret
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
external: true
jwt_secret:
external: true
What Offensive360 detects
Our SAST engine uses multiple techniques to identify hardcoded secrets. We detect:
- Known credential patterns — AWS access keys (
AKIA...), Stripe keys (sk_live_), GitHub tokens (ghp_), Slack webhooks, and 100+ other provider-specific patterns - High-entropy strings — Strings with high Shannon entropy that appear in assignment to variables named
password,secret,key,token,credential, or similar - Connection strings — Database URIs and connection strings containing embedded passwords
- Private keys — RSA, ECDSA, and Ed25519 private keys in PEM format
- Password assignments — Variables or constants named
password,passwd,pwd,secretassigned string literal values - Configuration files — Secrets in
application.properties,appsettings.json,.env,docker-compose.yml, and other configuration files tracked in version control - Comments containing credentials — Commented-out code or TODO comments containing actual credentials
Each finding identifies the specific secret type, its location, and provides guidance on migrating to a secrets management solution.
Remediation guidance
-
Use environment variables — Store secrets in environment variables, not in code. Every deployment platform (AWS, Azure, GCP, Heroku, Vercel) supports secure environment variable injection.
-
Use a secrets manager — For production systems, use dedicated secret management services: AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, GCP Secret Manager. These provide rotation, auditing, and access control.
-
Add sensitive files to .gitignore — Ensure
.env,*.pem,*.key,credentials.json, and similar files are in.gitignorebefore the first commit. -
Rotate leaked credentials immediately — If a secret has been committed to version control, assume it has been compromised. Rotate the credential immediately, even if the repository is private. Removing the file does not remove it from git history.
-
Use pre-commit hooks — Install tools like
detect-secrets,gitleaks, ortrufflehogas pre-commit hooks to prevent secrets from being committed. -
Scan git history — Use
trufflehogorgitleaksto scan the full git history for previously committed secrets. -
Use different credentials per environment — Development, staging, and production should use separate credentials. Never share production secrets with development environments.
False-positive considerations
Offensive360 may flag strings that are not actual secrets:
- Example/placeholder values — Strings like
YOUR_API_KEY_HERE,changeme,TODO: replace with real keyare placeholders, not real secrets - Test credentials — Test fixtures and integration test configuration using local or mock service credentials
- Public API keys — Some API keys are designed to be public (e.g., Google Maps browser API keys with domain restrictions, Firebase client config)
- Hash values — SHA-256 hashes, commit SHAs, and other hex strings that happen to have high entropy but are not secrets
- Encrypted values — Values that are encrypted/sealed (e.g., Ansible Vault, SOPS) and cannot be used without the decryption key
Our analysis uses contextual clues (variable names, surrounding code, string format) to minimize false positives, but some edge cases require manual review.
References
Related vulnerabilities
Detect Hardcoded Credentials and Secrets in your code
Run Offensive360 SAST against your codebase to find this and hundreds of other vulnerabilities.