CWE-798 — Use of Hard-Coded Credentials is one of the most consistently exploited vulnerability classes in application security. It appears on the MITRE CWE Top 25 Most Dangerous Software Weaknesses, is flagged as Critical or High by every major SAST tool (Checkmarx, Fortify, Veracode, SonarQube), and is routinely exploited in real-world breaches — from exposed GitHub repositories to reverse-engineered mobile applications.
This is the complete reference for CWE-798: what it means, how it manifests in code, why it’s dangerous, how automated tools detect it, and how to fix it correctly across every major language.
What Is CWE-798?
CWE-798 (Use of Hard-Coded Credentials) is defined by MITRE as:
“The software contains hard-coded credentials, such as a password or cryptographic key, which it uses for its own inbound authentication, outbound communication to external components, or encryption of internal data.”
In plain terms: the application has a password, API key, token, private key, or connection string embedded directly in the source code as a literal value — rather than loaded from a secure external source at runtime.
CWE-798 vs. Related CWEs
CWE-798 is the parent weakness. It has two child weaknesses that SAST tools may also report:
| CWE | Name | Description |
|---|---|---|
| CWE-798 | Use of Hard-Coded Credentials | Parent: any hard-coded credential |
| CWE-259 | Use of Hard-Coded Password | Specifically a password literal |
| CWE-321 | Use of Hard-Coded Cryptographic Key | Specifically a cryptographic key |
Most SAST tools report all three under the CWE-798 umbrella, but some vendors separate them. If your scanner reports CWE-259 or CWE-321, the remediation is identical.
What Counts as a Hard-Coded Credential?
CWE-798 covers more than just an obvious password = "abc123". Hard-coded credentials include any of the following embedded directly in code:
Passwords and passphrases
// VULNERABLE — CWE-798
private static final String DB_PASSWORD = "S3cur3P@ssw0rd!";
private static final String ADMIN_PASS = "admin";
API keys and access tokens
# VULNERABLE — CWE-798
STRIPE_API_KEY = "sk_live_51abc123..."
SENDGRID_API_KEY = "SG.abcdefg..."
OPENAI_KEY = "sk-proj-..."
Connection strings with embedded credentials
// VULNERABLE — CWE-798
private const string ConnectionString =
"Server=prod-db;Database=AppDB;User Id=sa;Password=ProdPass123!;";
Private keys and certificates embedded as strings
// VULNERABLE — CWE-798
const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA7n...
-----END RSA PRIVATE KEY-----`;
Base64-encoded credentials
# VULNERABLE — CWE-798 (encoding ≠ encryption)
import base64
ENCODED_PASS = "U2VjcmV0UGFzc3dvcmQhMjAyNg==" # base64 of "SecretPassword!2026"
password = base64.b64decode(ENCODED_PASS).decode()
Default credentials in firmware and container images
# VULNERABLE — CWE-798
ENV ADMIN_PASSWORD=default_admin_pass
ENV DB_ROOT_PASSWORD=mysql_root_1234
Hardcoded in configuration files committed to version control
# VULNERABLE — database.yml committed to git
production:
username: app_user
password: prod_database_password_here
database: myapp_production
Why CWE-798 Is Rated Critical
SAST tools consistently rate CWE-798 as High or Critical severity. Here is why:
1. Every Developer with Repository Access Has the Credential
Once a credential is in source code, everyone who can clone the repository has it: current developers, former employees whose accounts were revoked but who kept their local clone, contractors, third-party auditors, open-source contributors. Access control at the repository level does not protect the credential itself.
2. Git History Is Permanent
Removing the credential in a new commit does not delete it from the repository. The credential remains accessible in every previous commit. Tools like truffleHog, gitleaks, git-secrets, and git log -S "password" recover committed credentials instantly from any complete clone.
# An attacker with a clone can recover all committed secrets
git log --all --full-history -p | grep -A 2 -B 2 -i "password\|secret\|apikey\|token"
3. Public Repository Exposure = Immediate Exploitation
GitHub, GitLab, and Bitbucket are continuously scanned by automated bots searching for credential patterns. A repository accidentally made public is typically scraped within minutes. Several high-profile cloud credential leaks have resulted in six-figure cloud bills within hours of exposure.
4. Credential Rotation Is Impossible Without Redeployment
A hardcoded credential cannot be changed without modifying code, rebuilding, and redeploying the application. This means a compromised credential may remain active throughout an extended breach investigation window.
5. Mobile and Binary Applications Are Reversible
Android APKs can be decompiled with jadx or apktool. iOS IPAs can be analyzed with class-dump. Compiled binaries can be inspected with strings. Hardcoded API keys in mobile applications are routinely extracted — this is CWE-798 in the mobile context.
Where CWE-798 Most Commonly Appears
Based on security assessment data across enterprise codebases:
| Location | Frequency | Risk Level |
|---|---|---|
| Database connection strings | Very High | Critical |
| Third-party API keys (payment, email, cloud) | High | Critical |
| Internal service authentication tokens | High | Critical |
| Cryptographic keys for data encryption | Medium | Critical |
| SMTP/mail server credentials | Medium | High |
| Default admin credentials (firmware, IoT) | High | Critical |
| CI/CD configuration files | Medium | High |
| Container images (Dockerfile ENV) | Medium | High |
How SAST Tools Detect CWE-798
SAST tools use two main detection strategies for CWE-798:
Pattern Matching
The scanner searches for variable names that match credential patterns combined with string literal values:
Pattern: (variable name contains "password", "passwd", "pwd", "secret", "apikey",
"api_key", "token", "private_key", "credentials", "auth")
AND (assigned a string literal value of non-trivial length)
This catches obvious cases immediately but can generate false positives on placeholder values like "YOUR_API_KEY_HERE" or "change_me_in_production".
Taint Flow Analysis
More sophisticated tools (including Checkmarx, Fortify, and Offensive360) track string literals through the code to identify when they reach authentication, encryption, or connection sinks:
Source: string literal "S3cur3P@ss" assigned to variable
Flow: variable passed to DriverManager.getConnection(url, user, pass)
Sink: authentication method called with literal value
Result: CWE-798 confirmed — literal flows to auth sink
Taint flow detection catches cases where the credential passes through intermediate variables or is concatenated into a connection string before reaching the authentication call.
Regex Pattern Libraries
SAST tools maintain regex libraries for specific credential formats:
- AWS access key format:
AKIA[0-9A-Z]{16} - Stripe API key:
sk_(test|live)_[0-9a-zA-Z]{24} - JWT secrets embedded in code
- Private key PEM headers:
-----BEGIN (RSA |EC )?PRIVATE KEY----- - Connection string patterns for MySQL, PostgreSQL, MSSQL, MongoDB
Remediation: How to Fix CWE-798
The fix for CWE-798 is always the same principle: remove the credential from the codebase and load it from a secure external source at runtime. The implementation varies by platform.
Fix 1: Environment Variables (Minimum Viable Fix)
Environment variables are the simplest fix and are accepted as resolving the Checkmarx, Fortify, or SonarQube CWE-798 finding:
Java:
// VULNERABLE — CWE-798
private static final String DB_PASSWORD = "S3cur3P@ssw0rd!";
// FIXED — environment variable
private static final String DB_PASSWORD = System.getenv("DB_PASSWORD");
Python:
# VULNERABLE — CWE-798
DB_PASSWORD = "S3cur3P@ssw0rd!"
# FIXED — environment variable
import os
DB_PASSWORD = os.environ["DB_PASSWORD"]
C# / .NET:
// VULNERABLE — CWE-798
private const string DbPassword = "S3cur3P@ssw0rd!";
// FIXED — IConfiguration from environment
private readonly string _dbPassword;
public MyService(IConfiguration config)
{
_dbPassword = config["DB_PASSWORD"]; // Reads from env var or appsettings
}
Node.js:
// VULNERABLE — CWE-798
const DB_PASSWORD = "S3cur3P@ssw0rd!";
// FIXED — environment variable (use dotenv for local dev)
require('dotenv').config();
const DB_PASSWORD = process.env.DB_PASSWORD;
Fix 2: Secrets Manager (Production Best Practice)
For production systems, a dedicated secrets manager provides credential rotation, audit logging, and fine-grained access control:
AWS Secrets Manager (Python):
import boto3
import json
def get_db_credentials():
client = boto3.client("secretsmanager", region_name="us-east-1")
secret = client.get_secret_value(SecretId="prod/myapp/database")
return json.loads(secret["SecretString"])
creds = get_db_credentials()
# Use creds["username"] and creds["password"]
HashiCorp Vault (Java):
// Using Spring Vault
@Value("${secret.db.password}")
private String dbPassword;
// Spring Cloud Vault loads this from Vault at startup
Azure Key Vault (C#):
// In Program.cs / Startup.cs
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{keyVaultName}.vault.azure.net/"),
new DefaultAzureCredential()
);
// Access via IConfiguration as normal — no code change required
Fix 3: Configuration Files Outside Version Control
For local development, use .env files excluded from Git:
# .env (add to .gitignore — never commit this file)
DB_PASSWORD=your_local_dev_password
API_KEY=your_dev_api_key
# .env.example (commit this — documents required variables without values)
DB_PASSWORD=
API_KEY=
Always verify .env is in .gitignore:
echo ".env" >> .gitignore
git rm --cached .env 2>/dev/null || true # Remove from tracking if already committed
Cleaning the Credential from Git History
Critical: Removing the credential from current code does not remove it from Git history. If the credential was ever committed, you must:
Step 1: Rotate the Credential Immediately
Assume the credential is already compromised. Change the password, revoke and re-issue the API key, rotate the encryption key — before doing anything else. The exposure window is the entire history of the repository.
Step 2: Remove from Git History with git-filter-repo
# Install git-filter-repo
pip install git-filter-repo
# Replace the exposed credential in all historical commits
git filter-repo --replace-text <(echo "S3cur3P@ssw0rd!==>[CREDENTIAL REMOVED]")
# Force-push all branches
git push origin --force --all
git push origin --force --tags
Step 3: Invalidate All Existing Clones
After a force-push rewrite, all collaborators must delete their local clone and re-clone. Cached copies in CI/CD systems must also be cleared. Any branch cached in a pull request that was opened before the rewrite retains the old history.
Preventing CWE-798 Recurrence
Pre-Commit Hook with gitleaks
# Install gitleaks
brew install gitleaks # macOS
apt install gitleaks # Ubuntu/Debian (or download from GitHub releases)
# Create pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
gitleaks protect --staged --redact --verbose
if [ $? -ne 0 ]; then
echo "CWE-798: Potential credential detected. Commit blocked."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
CI/CD Pipeline Scanning
Run credential scanning on every pull request and every push to protected branches:
# GitHub Actions
- name: Scan for hardcoded credentials (CWE-798)
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SAST in the Merge Pipeline
A SAST scanner that checks for CWE-798 as part of every pull request merge gate prevents credentials from reaching the main branch. Offensive360 SAST flags CWE-798 findings at CI/CD integration time with the exact file, line number, and credential type — allowing developers to fix the finding before the merge is approved.
CWE-798 in Different Technology Contexts
Mobile Applications (Android / iOS)
Hardcoded API keys in mobile apps are a particularly high-risk variant of CWE-798 because mobile APKs and IPAs can be obtained by anyone who downloads the application:
// Android — VULNERABLE (CWE-798)
private static final String MAPS_API_KEY = "AIzaSyB1abc123...";
private static final String PAYMENT_API_KEY = "pk_live_51abc...";
For mobile applications, the fix is to serve sensitive API keys from a backend proxy — never embed them in the client application. Use Android’s BuildConfig + Gradle Secrets for keys that are absolutely required at build time, and rotate exposed keys immediately.
Infrastructure as Code (Terraform, Helm, Kubernetes)
IaC files committed to version control are a common source of CWE-798 findings:
# Terraform — VULNERABLE (CWE-798)
resource "aws_db_instance" "default" {
password = "mysecretpassword" # CWE-798
}
# FIXED — use variable with no default
variable "db_password" {
type = string
sensitive = true
}
resource "aws_db_instance" "default" {
password = var.db_password
}
# Kubernetes Secret — VULNERABLE if the base64 value is a real credential
apiVersion: v1
kind: Secret
data:
password: bXlzZWNyZXRwYXNz # base64("mysecretpass") — CWE-798
For Kubernetes, use external secrets operators (External Secrets Operator, Sealed Secrets) that store encrypted references rather than base64-encoded values.
CWE-798 and Compliance Frameworks
CWE-798 findings are relevant to multiple compliance frameworks:
| Framework | Relevant Control |
|---|---|
| PCI-DSS | Requirement 2.2 — no default or hardcoded credentials in any system component |
| NIST SP 800-53 | IA-5 — authenticator management; credentials must not be hardcoded |
| OWASP Top 10 | A07:2021 — Identification and Authentication Failures |
| CIS Controls | Control 3.3 — ensure no sensitive data is stored in unsecured locations |
| SOC 2 | CC6.1 — logical access controls include credential management |
Regulators and auditors will flag CWE-798 findings as evidence of inadequate credential management practices. A SAST report showing CWE-798 findings without mitigations will draw questions in a PCI-DSS QSA assessment or a SOC 2 audit.
Interpreting a CWE-798 SAST Finding
When your SAST tool reports CWE-798, the finding typically includes:
- Severity: High or Critical
- File and line: The exact location of the hardcoded credential
- Snippet: The surrounding code showing the credential assignment
- Remediation guidance: Whether to use environment variables or a secrets manager
Is it a false positive?
CWE-798 false positives occur when:
- The “credential” is a placeholder like
"REPLACE_ME","YOUR_API_KEY", or"test" - The variable name resembles a credential but the value is not a real secret (e.g.,
passwordLength = 16) - The value is a test credential in a unit test context — mark it as false positive in your SAST tool with justification
To confirm the finding is a real credential: check whether the value looks like a real secret (entropy analysis), whether it is actually used in an authentication or encryption context, and whether it appears in any real deployed environment.
Frequently Asked Questions
Is CWE-798 always Critical severity?
Most SAST tools rate CWE-798 as High or Critical. The actual severity depends on what the credential grants access to: a hardcoded password for a production database is Critical; a hardcoded API key for a low-privilege read-only external service might be rated High. Never dismiss a CWE-798 finding without investigating what the credential controls.
Does CWE-798 apply to test credentials?
Test credentials that only exist in test environments and have no access to production systems are generally treated as low-severity or false-positive by security reviewers. However, they are still a code quality issue — if a credential is in the codebase and has the word “password” in the variable name, your SAST tool will flag it. The clean approach is to use clearly named constants like TEST_DB_PASSWORD_NOT_REAL = "only_for_unit_tests" and document them in your SAST suppression policy.
How do I fix CWE-798 in a legacy codebase with hundreds of findings?
Start with the highest-severity findings: production database passwords, payment API keys, and encryption keys. Rotate each credential first, then move it to environment variables or a secrets manager. Then clean git history. For a large legacy codebase, prioritize by the criticality of what the credential accesses rather than trying to fix all findings at once.
What is the difference between CWE-798 and CWE-312?
CWE-312 (Cleartext Storage of Sensitive Information) is about storing sensitive data without encryption — for example, saving a password to a log file in plaintext. CWE-798 is specifically about embedding credentials in source code. They can overlap (a hardcoded credential in source code is also cleartext storage), but CWE-312 is broader — it applies to databases, log files, config files, and other storage mechanisms beyond just source code.
Summary
CWE-798 (Use of Hard-Coded Credentials) is rated Critical by every major SAST vendor because any credential embedded in source code is effectively accessible to everyone who has ever had repository access — and once committed to Git, it cannot be fully removed without history rewriting and credential rotation.
The fix is always the same direction: move the credential out of source code and into environment variables, a secrets manager, or a secure configuration system that is never committed to version control. Then rotate the exposed credential, clean git history, and add pre-commit hooks and CI/CD scanning to prevent recurrence.
Offensive360’s SAST engine detects CWE-798 across all 60+ supported languages — including connection strings, API key patterns, private key PEM blocks, and base64-encoded credentials — and reports each finding with the exact file, line number, and credential type. Book a demo to get a complete inventory of CWE-798 findings with remediation guidance, or book a demo to see the platform in action.