Skip to content

CWE-259: Use of Hard-coded Password

Overview

A hard-coded password is a password value embedded directly in source code, a configuration file, or a compiled binary. It is either checked against for inbound authentication, such as a backdoor or default login, or used to authenticate outbound to another system such as a database, API, or service account. Because the value ships with every copy of the software and cannot be changed without a code or config update, discovery of one hard-coded password compromises every installation running that code at once.

Relationship to Other CWEs

  • CWE-259 (this page) - a password embedded in source, config, or a compiled artifact, whether it gates an inbound authentication check or authenticates outbound to another system.
  • CWE-798 (Use of Hard-coded Credentials) - the parent of this CWE. CWE-798 is the general case covering any hard-coded secret - passwords, API keys, tokens, connection strings. CWE-259 is the password-specific variant. The full remediation mechanics - secrets manager integration, rotation, git-history cleanup, detection tooling - are on the CWE-798 page; this page covers what is specific to passwords.
  • CWE-257 (Storing Passwords in a Recoverable Format) - a peer weakness. CWE-257 is about how a password is stored, reversibly or hashed, not where it originates. A hard-coded password is often also stored in a recoverable format, but the two findings call for different fixes.
  • CWE-321 (Use of Hard-coded Cryptographic Key) - a peer weakness: the same root problem applied to a cryptographic key instead of a password. Fix it the same way, with an external secrets store rather than an embedded literal.
  • CWE-656 (Reliance on Security Through Obscurity) - can precede this weakness: hard-coded passwords are sometimes introduced deliberately as an undocumented bypass, relying on the value staying secret rather than on a real access control.

OWASP Classification

A07:2025 - Authentication Failures

Risk

Critical: Attackers can extract hard-coded passwords from source code, decompiled binaries, or version control history, then use them directly for unauthorized access - and because the value is identical across every deployment, a single leak compromises every installation running that code.

Remediation Steps

Core Principle: Never embed a password in source, config, or binary artifacts; load it at runtime from a dedicated secrets store, and treat it as a replaceable, least-privilege credential. See CWE-798 for the full secrets-management remediation path.

Trace the Data Path

  • Source: a string literal in source code, a constant, a committed config file, or a value baked into a compiled binary or container image
  • Sink: either an inbound check, such as if password == "hardcoded_value" in an authentication routine, or an outbound connection, where the app authenticates to a database, API, or service using the embedded value
  • Missing control: no separation between code/artifact and secret - the value is fixed at build time instead of injected at deploy/runtime

Remove the Hard-coded Password (Primary Defense)

  • Delete the literal from code, config, and build artifacts
  • Load it at runtime from a secrets manager (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, GCP Secret Manager), or have the deployment platform inject it into the process environment at start-up where that is the only mechanism available - an environment variable is a way to hand a process a secret, not a place to keep one, and CWE-526 covers the difference
  • For inbound authentication checks specifically: remove any backdoor/debug account entirely rather than moving its password out-of-band - a hidden account is its own weakness even once its password is no longer hard-coded
  • Use different credentials per environment and per service so one leaked value doesn't compromise everything

Rotate and Revoke Immediately

  • Rotate the exposed password at the source system as soon as it's found - removing it from code does not undo the exposure
  • Audit access logs for use of the old credential before rotation
  • Purge the value from git history (BFG Repo-Cleaner, git filter-repo) and treat any clone, fork, or CI cache as still compromised until the credential itself is invalidated at the source system

Test the Remediation

  • Confirm the application fails to start, with a clear error, if the runtime secret is missing - rather than silently falling back to a default
  • Re-scan the codebase and git history with a secret-detection tool (Gitleaks, TruffleHog) to confirm no occurrence remains
  • Verify the old hard-coded value no longer authenticates anywhere it previously worked

Common Vulnerable Patterns

Inbound: a fixed value that grants access

// VULNERABLE - inbound: hard-coded password gates an authentication check
function login(username, password):
    if password == "Adm1n_Backd00r!":     // fixed value, ships with every install
        return grant_admin_access()

Why this is vulnerable: the secret is not per-installation, so neither is the compromise. Every copy of the software accepts the same value, which means one disclosure - from a leaked repository, a decompiled binary, a support ticket, or a vendor's own documentation - is a working credential against every deployment that has ever shipped, including ones the vendor does not know about.

It is also cheap to find. The value sits in the binary as a literal, so strings on the shipped artifact surfaces it without any reverse engineering, and a comparison against a string constant next to a function called grant_admin_access is not hard to recognise. And there is no rotation path: changing the password means editing code, building, releasing, and persuading every operator to upgrade, so the window between disclosure and remediation is measured in versions rather than minutes.

Outbound: a credential to another system, committed

// VULNERABLE - outbound: hard-coded password used to reach another system
db_connection = connect(host, user="app", password="SuperSecret123!")

Why this is vulnerable: the credential is now in version control, and that is a broader exposure than the file suggests. It is in every clone and every fork, in the CI logs that echoed the file, in any build artifact that embedded it, and in the repository's history - so deleting the line changes what the code does and not who has the secret. Anyone who has ever had read access to the repository still holds a working password.

That is why the order of operations matters more here than the code change. Rotate the credential at the system it authenticates to first, then move the new value into a secret store or an injected environment variable, and treat the old one as public from the moment it was committed. Removing it from history is worth doing and is not a substitute: forks and existing clones are outside your reach, and a secret that has been rotated does not need to be un-published.

Secure Patterns

// SECURE - password loaded at runtime, not embedded
password = secrets_manager.get_secret("db/app/password")
if password is missing:
    fail_startup("required secret not available")

db_connection = connect(host, user="app", password=password)

Why this works: the value never exists in source, config, or the built artifact, so there is nothing for an attacker with code or artifact access to extract. It can be rotated at the secrets store without a code change or redeploy, and different environments can use different values.

Common Pitfalls

  • Renaming the hard-coded value to a "default" fallback: replacing a literal check with a comparison against a DEFAULT_PASSWORD constant used when no other value is configured - this is the same hard-coded password with an extra layer of naming, not a fix.
  • Obfuscating instead of externalizing: Base64-encoding, XOR-ing, or splitting the password into concatenated string fragments to defeat a plain-text grep scan - the value is still embedded and just as recoverable once decoded; see CWE-261 for why encoding is not protection.
  • Fixing the login form but missing outbound uses: removing a hard-coded password from the user-facing authentication path while a background job, legacy integration, or service-to-service call still embeds a separate hard-coded password to reach another system - MITRE's own definition treats inbound and outbound as two distinct manifestations, and a review that only checks the login screen misses the second one.
  • Leaving a "temporary" debug or backdoor account in place past release: hard-coding a password for local testing with the intent to remove it before shipping, then shipping it anyway because there was no tracked follow-up.

Additional Resources