CWE-798 is the Common Weakness Enumeration identifier for “Use of Hard-Coded Credentials” — a vulnerability that occurs when authentication secrets (passwords, API keys, tokens, cryptographic keys) are embedded directly in source code rather than loaded from a secure external source at runtime.
CWE-798 is classified in the CWE Top 25 Most Dangerous Software Weaknesses and consistently ranks among the highest-severity findings in enterprise security assessments. Every major SAST platform — Checkmarx, Fortify, Veracode, SonarQube, and Offensive360 — flags CWE-798 as High or Critical severity.
CWE-798 at a Glance
| Property | Value |
|---|---|
| CWE ID | CWE-798 |
| Name | Use of Hard-Coded Credentials |
| Parent | CWE-344 (Use of Invariant Value in Dynamically Changing Context) |
| OWASP Mapping | A07:2021 — Identification and Authentication Failures |
| CVSS Base Score | Varies; typically 7.5–9.8 (High to Critical) |
| CWE Top 25 | Yes — consistently in top 25 most dangerous software weaknesses |
| Common SAST query names | Use_Of_Hard_Coded_Password (Checkmarx), Password_Management_Hard_Coded (Fortify), HardcodedCredentials (Veracode) |
What CWE-798 Covers
CWE-798 covers any secret that is embedded as a literal value in source code. This includes:
Passwords and passphrases:
private static final String DB_PASSWORD = "S3cur3P@ssw0rd!";
API keys and tokens:
api_key = "sk-live-abc123def456xyz789"
stripe_secret = "sk_live_51Habcdefghijklmnop"
Connection strings with embedded credentials:
const connectionString = "mongodb://admin:[email protected]:27017/app";
Private cryptographic keys:
$private_key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAK...";
Base64-encoded credentials (encoding does not constitute encryption):
// STILL CWE-798 — base64 is trivially reversible
string encodedCreds = "YWRtaW46cGFzc3dvcmQxMjM="; // admin:password123
Default credentials in firmware or applications:
const DefaultAdminPassword = "admin" // Ships with every deployment
Why CWE-798 Is Rated Critical
SAST tools rate CWE-798 as High or Critical because the exploit path is trivially short:
1. No Skills Required to Exploit
Once the credential is exposed, exploitation requires only the credential itself — no vulnerability chaining, no exploitation technique. Any person with access to the code (or a repository leak, or a decompiled binary) can immediately use the credential.
2. Git History Retains Secrets Permanently
# Anyone with repository access can recover the credential instantly
git log -S "S3cur3P@ssw0rd" --all --oneline
git log --all -p | grep -A3 -B3 "password"
Removing the credential in a later commit does not remove it from git history. The credential remains accessible via git log indefinitely.
3. Automated Scanning Finds Exposed Credentials Within Minutes
When a repository is accidentally made public, automated scanners (run by both security researchers and malicious actors) scan GitHub, GitLab, and Bitbucket for credential patterns within minutes of publication.
4. Every Developer With Repository Access Has the Credential
Past and present developers, contractors, third-party auditors, and CI/CD systems — anyone who has ever cloned the repository has the credential. This is true even after access is revoked at the platform level.
5. Rotation Requires Redeployment
A hardcoded credential cannot be rotated without modifying code and redeploying. If the credential is compromised, the window of exposure is the entire period the code has been running — potentially years.
How Attackers Exploit CWE-798
Path 1: Repository Access → Credential Extraction
# Direct extraction from source code
grep -r "password\|api_key\|secret\|token\|credential" . --include="*.java" --include="*.py"
# Using tools designed for this purpose
trufflehog git file://./my-repo
gitleaks detect --source . --verbose
git-secrets --scan -r .
Path 2: Binary / APK Reverse Engineering
Android APKs, compiled Java JARs, and native binaries can be decompiled or disassembled to recover hardcoded strings:
# Android APK
jadx -d output/ target.apk && grep -r "api_key\|password\|secret" output/
# Java JAR
javap -c target.jar | strings | grep -i "password\|key\|secret"
# Native binary
strings ./binary | grep -E "[a-zA-Z0-9]{20,}"
Path 3: Docker Image Layer Inspection
Credentials set via ENV instructions in Dockerfiles or embedded in image layers are recoverable:
# Inspect Docker image layers for credentials
docker history --no-trunc myapp:latest
docker inspect myapp:latest | python3 -m json.tool | grep -i "env\|password"
docker run --rm -it myapp:latest env | grep -i "pass\|key\|secret"
CWE-798 in Practice: Language-Specific Patterns
Java / Spring Boot
Vulnerable:
// CWE-798 — credentials in source code
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:postgresql://prod-db.internal:5432/app");
ds.setUsername("dbadmin");
ds.setPassword("Pr0d_DB_P@ssw0rd!"); // CWE-798
return ds;
}
}
Fixed:
// Credentials from environment — resolves CWE-798
@Configuration
public class DatabaseConfig {
@Value("${DB_URL}")
private String dbUrl;
@Value("${DB_USERNAME}")
private String dbUsername;
@Value("${DB_PASSWORD}")
private String dbPassword;
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(dbUrl);
ds.setUsername(dbUsername);
ds.setPassword(dbPassword); // Value injected at runtime from environment
return ds;
}
}
# application.properties — values set via environment variables
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
Python
Vulnerable:
# CWE-798 — hardcoded credentials
import psycopg2
def get_db_connection():
return psycopg2.connect(
host="prod-db.internal",
database="app",
user="admin",
password="Pr0d_DB_P@ss!" # CWE-798
)
STRIPE_SECRET = "sk_live_51HabcXYZ..." # CWE-798
SENDGRID_KEY = "SG.abcdefghijklmnop" # CWE-798
Fixed:
import os
import psycopg2
import boto3
import json
# Option 1: Environment variables (minimum fix — resolves CWE-798)
def get_db_connection():
return psycopg2.connect(
host=os.environ["DB_HOST"],
database=os.environ["DB_NAME"],
user=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"]
)
# Option 2: AWS Secrets Manager (recommended for production)
def get_db_credentials() -> dict:
client = boto3.client("secretsmanager", region_name="us-east-1")
response = client.get_secret_value(SecretId="prod/myapp/database")
return json.loads(response["SecretString"])
C# / .NET
Vulnerable:
// CWE-798 — connection string hardcoded
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(
"Host=prod-db;Database=app;Username=admin;Password=Pr0d_P@ss!" // CWE-798
);
}
}
// Also CWE-798:
private readonly string _apiKey = "sk-live-abc123def456"; // CWE-798
Fixed:
// Credentials via IConfiguration (environment variables or secrets manager)
public class AppDbContext : DbContext
{
private readonly IConfiguration _configuration;
public AppDbContext(IConfiguration configuration)
{
_configuration = configuration;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Value comes from environment variable or Azure Key Vault
optionsBuilder.UseNpgsql(_configuration.GetConnectionString("DefaultConnection"));
}
}
// appsettings.json — connection string resolved from environment at runtime
{
"ConnectionStrings": {
"DefaultConnection": ""
}
}
# Runtime: set environment variable (Kubernetes secret, Azure App Service setting, etc.)
export ConnectionStrings__DefaultConnection="Host=prod-db;Database=app;Username=admin;Password=${DB_PASSWORD}"
Node.js / JavaScript
Vulnerable:
// CWE-798 — credentials in source code
const mysql = require('mysql2');
const pool = mysql.createPool({
host: 'prod-db.internal',
user: 'root',
password: 'MySuperSecret123!', // CWE-798
database: 'app'
});
const AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"; // CWE-798
const AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; // CWE-798
Fixed:
require('dotenv').config(); // .env file in development; real env vars in production
const mysql = require('mysql2');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
# .env (development only — never commit)
DB_HOST=localhost
DB_USER=app_user
DB_PASSWORD=local_dev_password
DB_NAME=app_dev
# .env.example (commit this — shows required vars, no values)
DB_HOST=
DB_USER=
DB_PASSWORD=
DB_NAME=
How SAST Tools Detect CWE-798
Checkmarx
Query name: Use_Of_Hard_Coded_Password (CxSAST) / Hardcoded_Credentials (Checkmarx One)
Checkmarx detects CWE-798 through data flow analysis:
- Source identification: String literals that match credential patterns (variable names containing
password,passwd,pwd,secret,apikey,token,key) - Sink identification: Security-sensitive method calls —
DriverManager.getConnection(),new SecretKeySpec(),HttpBasicAuthentication(), etc. - Taint path: The literal value flows directly from the constant declaration to the sensitive sink
Typical Checkmarx CWE-798 finding:
Severity: High
CWE: CWE-798
Query: Use_Of_Hard_Coded_Password
File: src/config/DatabaseConfig.java
Line: 15
Source: "Pr0d_DB_P@ssw0rd!"
Sink: DriverManager.getConnection(url, user, pass)
Common Checkmarx CWE-798 false positives:
- Test fixtures:
"test_password"in unit test files - Placeholder strings:
"YOUR_API_KEY_HERE","REPLACE_ME" - Non-sensitive variable names accidentally matching:
password_length = 12
Fortify SCA
Category name: Password Management: Hard-Coded Password
Fortify uses semantic analysis to detect string literals flowing to authentication APIs. It also specifically detects:
- Credentials in Spring
@Value("hardcoded_value")annotations - Credentials in JDBC URL strings:
jdbc:postgresql://user:pass@host/db - Cryptographic keys in
new SecretKeySpec(new byte[]{...})calls
Offensive360
Rule name: HardcodedCredentials
Offensive360 SAST detects CWE-798 across 60+ languages using pattern matching combined with data-flow analysis. The detection covers:
- String literals with credential-pattern variable names
- Embedded connection strings with username:password format
- Base64-encoded values flowing to authentication methods
- Cryptographic key material as byte array literals
- Dockerfile ENV instructions and docker-compose credentials
Findings include the file path, line number, credential type (password/API key/connection string/private key), and a direct link to the remediation guidance.
Remediation Checklist for CWE-798
When you receive a CWE-798 finding from a SAST scanner, follow this sequence:
Step 1: Rotate the Credential Immediately
Treat the credential as already compromised. Rotate it before making any code changes:
- Change the database password
- Revoke and reissue the API key
- Regenerate the cryptographic key
- Update all systems that use the credential to use the new value
Do not wait until after the code fix to rotate — the credential should be considered burned the moment it’s identified.
Step 2: Check Git History for Scope
# Check if the credential exists in git history
git log --all -p -S "the_credential_value" | head -50
# Check how far back it goes
git log --all --oneline -- path/to/affected/file
If the credential appears in git history, history cleanup is required regardless of when it was introduced.
Step 3: Replace With Environment Variable or Secrets Manager
Move the credential to the appropriate external source:
- Development:
.envfile (added to.gitignore) - CI/CD: Environment secrets (GitHub Actions secrets, GitLab CI variables, Jenkins credentials store)
- Production: Dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, GCP Secret Manager)
Step 4: Clean Git History
# Install git-filter-repo
pip install git-filter-repo
# Replace all occurrences of the credential in history
git filter-repo --replace-text <(echo 'old_credential==>[REDACTED]')
# Force push all branches
git push origin --force --all
git push origin --force --tags
After cleaning history, all collaborators must delete local clones and re-clone. Any cached copies in CI/CD systems must also be invalidated.
Step 5: Add Prevention Controls
Prevent the same class of issue from recurring:
# Pre-commit hook with gitleaks
brew install gitleaks
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
gitleaks protect --staged --redact --verbose
if [ $? -ne 0 ]; then
echo "Potential secrets detected. Commit blocked."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
Step 6: Re-scan to Confirm Resolution
After making code changes, run the SAST scan again to confirm the finding is resolved. Most SAST tools show the CWE-798 finding as resolved once the string literal is removed from the source file.
Secrets Management Solutions
Moving credentials from source code to a secrets manager is the production-grade fix for CWE-798:
| Solution | Best for | Automatic Rotation |
|---|---|---|
| AWS Secrets Manager | AWS-hosted applications | ✅ Yes (built-in rotation lambdas) |
| HashiCorp Vault | Multi-cloud / on-premise | ✅ Yes (dynamic secrets) |
| Azure Key Vault | Azure-hosted applications | ✅ Yes (managed rotation) |
| GCP Secret Manager | GCP-hosted applications | ✅ Yes |
| Doppler | Developer-friendly SaaS | ✅ Yes |
| 1Password Secrets Automation | Small teams | Manual |
| Environment variables | Any deployment | Manual (minimum viable fix) |
Environment variables are the minimum fix that resolves the SAST finding. For regulated environments (PCI-DSS, HIPAA, SOC 2), a dedicated secrets manager with audit logging and automatic rotation is the appropriate solution.
CWE-798 in Regulatory and Compliance Frameworks
CWE-798 maps directly to requirements in major compliance frameworks:
| Framework | Requirement |
|---|---|
| PCI-DSS 4.0 | Requirement 8.6.1 — No hardcoded accounts/passwords for application/system accounts |
| OWASP ASVS 4.0 | V2.10.1 — No hardcoded passwords in the codebase |
| SOC 2 | CC6.1 — Logical and physical access controls (hardcoded credentials violate the principle) |
| ISO 27001 | A.9.4.3 — Password management system (credentials must be managed, not hardcoded) |
| NIST SP 800-53 | IA-5 (Authenticator Management) — credentials must be changeable |
A CWE-798 finding in a pre-audit SAST scan is a compliance blocker in all of the above frameworks. Most compliance auditors will flag it as a material finding requiring immediate remediation.
Frequently Asked Questions
Does CWE-798 apply to test code?
SAST tools typically flag CWE-798 in test files as well as production code. Test credentials in unit tests and integration tests should ideally be replaced with environment variables or placeholder values — not because test-only credentials pose the same risk as production credentials, but because the same developers write test and production code, and the habit of hardcoding “just for testing” regularly migrates to production code.
For legitimate test credentials where the SAST finding is a false positive, document the suppression with a specific reason.
What’s the difference between CWE-798 and CWE-259?
- CWE-798 (Use of Hard-Coded Credentials): General class covering any hardcoded secret
- CWE-259 (Use of Hard-Coded Password): Specific subtype of CWE-798, covering specifically passwords (as opposed to API keys, tokens, or cryptographic keys)
Most SAST tools report hardcoded passwords as CWE-798 and may additionally cite CWE-259. The remediation is identical.
Is base64-encoding a credential sufficient to resolve CWE-798?
No. Base64 encoding is not encryption — it is a reversible encoding scheme. A base64-encoded credential is still a hardcoded credential (CWE-798). atob("dXNlcjpwYXNzd29yZA==") takes milliseconds to decode.
My CI/CD pipeline has the credential in an environment variable — is that still CWE-798?
No. If the credential is in a CI/CD environment variable (GitHub Actions secret, GitLab CI variable, Jenkins credential store) and is not present as a string literal in the source code, it is not CWE-798. The finding resolves when the literal value is removed from source code.
Summary
CWE-798 (Use of Hard-Coded Credentials) is one of the most consistently exploitable vulnerability classes in enterprise software. It requires no exploitation skill — the credential is already exposed. The fix is straightforward: move every authentication secret from source code to an environment variable or secrets manager, rotate the exposed credential immediately, and clean git history.
The five-step remediation sequence — rotate → scope history → remove from code → clean history → add prevention controls — resolves both the immediate risk and the structural conditions that allow CWE-798 to recur.
Offensive360 SAST detects CWE-798 (hardcoded credentials) across 60+ languages including Java, C#, Python, JavaScript, PHP, Go, and Ruby. Run a one-time code scan for $500 to identify all hardcoded credential findings in your codebase — results within 48 hours. Or book a demo to see the full platform.