# GO-SEC-004: Hardcoded Credentials in Source Code

> **Severity:** HIGH | **CWE:** CWE-798, CWE-259, CWE-321 | **OWASP:** A07:2021, A02:2021

- **Language:** Go
- **Category:** Security
- **URL:** https://codepathfinder.dev/registry/golang/security/GO-SEC-004
- **Detection:** `pathfinder scan --ruleset golang/GO-SEC-004 --project .`

## Description

Hardcoded credentials are secrets — passwords, API keys, tokens, signing keys, and
connection strings — embedded directly as string literals in source code. This is one
of the most frequently occurring and impactful security vulnerabilities in software
development, consistently appearing in real breaches with catastrophic consequences.

**Why hardcoded secrets are permanently dangerous**:

Source code is copied, forked, backed up, and diffed far more broadly than developers
realize. A secret committed to any version control system persists in the commit history
indefinitely — even `git rm` and `git commit` do not remove it. An attacker with read
access to any historical snapshot (via GitHub, GitLab, Bitbucket, backup, or zip export)
recovers the credential. Forcing a secret into git history is effectively publishing it.

**Surface area of secret exposure beyond git**:
- "**Compiled Go binaries**: Go embeds string literals directly in the binary's `.rodata`"
  section. The Unix `strings` command extracts all printable strings of 4+ characters.
  Tools like Ghidra with the "ghostrings" plugin specifically hunt for string constants
  that look like credentials. A hardcoded JWT secret or database password survives
  unchanged in every compiled artifact shipped to customers or placed in container images.
- "**Container image layers**: Docker layers are union filesystem snapshots. Each layer"
  is a tar archive. `docker history --no-trunc` and `docker inspect` expose build-time
  ARG values. If a binary containing hardcoded credentials is in the image, `docker save`
  followed by `tar xf` and `strings` extracts them.
- "**CI/CD pipeline artifacts**: Build systems cache compiled binaries and run logs. A"
  test output that prints a hardcoded constant, or a build artifact in a public artifact
  store, exposes the credential to anyone with artifact access.
- "**Log files and crash dumps**: Go programs that log their configuration at startup"
  (common in 12-factor app patterns) may log environment variables or struct fields —
  including credential-named fields initialized from string literals.

**Real-world incidents involving hardcoded credentials**:

*Toyota T-Connect breach (2022–2023)*: Toyota disclosed that a contractor had
accidentally published source code to a public GitHub repository containing credentials
for a data server. The exposed data server contained information of approximately
296,019 Toyota T-Connect customers — names, phone numbers, and vehicle identification
numbers. The credentials remained valid and exposed for approximately 4 years and 9 months
before discovery. Toyota stated no financial data was accessed but could not rule out
third-party access during the exposure window.

*Internet Archive breach (October 2024)*: Threat actors obtained a GitLab personal
access token that was exposed in a public repository. Using this token, they accessed
the Internet Archive's GitLab instance and exfiltrated configuration files. The breach
exposed 31 million user records (email addresses, usernames, bcrypt-hashed passwords,
and other internal data). The token, once hardcoded in the repository, became the
entry point for the entire breach.

*Slack GitHub repository credential exposure (2022)*: Slack disclosed that hashed
employee credentials and GitHub repository cloning tokens were exposed when an attacker
gained access to a subset of Slack's GitHub repositories. The exposure included tokens
that could be used to access Slack's internal systems. While Slack rotated all affected
credentials immediately, the incident demonstrated how a single exposed token creates
a lateral movement path from external repository access to internal infrastructure.

**Scale of the problem — GitGuardian research (2024)**:
GitGuardian's "State of Secrets Sprawl" report analyzed over 1 billion commits:
- 23.8 million new plaintext secrets were detected in public GitHub repositories in 2023
- 70% of secrets exposed in 2021 remain valid and active 2 years later — secret rotation
  after discovery is the exception, not the rule
- "The most common secret types: API keys (35%), generic credentials (28%), private keys (17%)"
- "Developer-to-secret ratio: approximately 1 hardcoded secret per 1,000 commits in"
  enterprise repositories

**Extraction techniques attackers use**:
1. `strings /path/to/binary | grep -iE "(password|secret|key|token)" | grep -v "^[[:space:]]*$"`
2. TruffleHog v3: `trufflehog git file:///path/to/repo --only-verified` — scans git history
   with entropy analysis and regex patterns against known-secret formats
3. Gitleaks: `gitleaks detect --source /path/to/repo` — rule-based scanning with 150+
   built-in patterns for AWS keys, GitHub tokens, Stripe keys, etc.
4. Docker layer inspection: `docker save image:tag | tar xf - | find . -name "*.tar" -exec tar xf {} \; | strings | grep -i secret`
5. Ghidra ghostrings plugin: static analysis of Go binaries to identify Go string constants
   and cross-reference them against credential patterns

**CWE taxonomy for this vulnerability class**:
- "**CWE-798** (parent): General hardcoded credentials — covers any credential type"
- "**CWE-259** (child): Specifically hardcoded passwords in authentication code"
- "**CWE-321** (child): Hardcoded cryptographic keys — JWT signing secrets, AES keys,"
  HMAC secrets. These are both a credential and a cryptographic failure simultaneously.

**Go-specific remediation hierarchy** (in order of increasing security):
1. `os.Getenv("SECRET_NAME")` — environment variables; acceptable for development,
   simple deployments. Note: env vars are visible in `/proc/<pid>/environ`, `docker inspect`,
   and CI/CD log output if logged.
2. `github.com/spf13/viper` with config files excluded from git — supports env var binding,
   remote config (etcd, Consul), and layered config. Better than bare os.Getenv for complex apps.
3. `github.com/hashicorp/vault/sdk` — HashiCorp Vault dynamic secrets. Vault generates
   short-lived credentials on demand and revokes them automatically. Compromised credentials
   expire without requiring rotation. Vault audit logs all secret accesses.
4. AWS Secrets Manager (`github.com/aws/aws-sdk-go-v2/service/secretsmanager`) / GCP Secret
   Manager / Azure Key Vault — cloud-native secrets management with IAM-based access control,
   automatic rotation for supported services (RDS, Redshift), and CloudTrail audit logging.

**CISA Secure by Design guidance**:
CISA's "Secure by Design" principles explicitly list elimination of default and hardcoded
credentials as a foundational requirement. The guidance states that software manufacturers
should make it technically impossible to deploy software with default credentials by
requiring credential configuration as part of the installation process.


## Vulnerable Code

```python
# --- file: vulnerable.go ---
// GO-SEC-004 positive test cases — credential-named variables passed to functions
package main

import "database/sql"

// Credential-named variables flowing into function calls — SHOULD be detected

func hardcodedDBPassword(db *sql.DB) {
	password := "super_secret_123"             // hardcoded credential
	db.Exec("ALTER USER admin PASSWORD ?", password) // variable 'password' as arg
}

func hardcodedAPIKey() {
	apikey := "sk-1234567890abcdef"            // hardcoded API key
	makeRequest(apikey)                         // variable 'apikey' as arg
}

func hardcodedToken() {
	token := "ghp_xxxxxxxxxxxx"                // hardcoded token
	authenticate(token)                         // variable 'token' as arg
}

func hardcodedSecret() {
	secret := "my-signing-secret"              // hardcoded secret
	signData(secret)                            // variable 'secret' as arg
}

func makeRequest(key string) {}
func authenticate(tok string) {}
func signData(s string) {}

# --- file: go.mod ---
module example.com/go-sec-004/positive

go 1.21

# --- file: go.sum ---

```

## Secure Code

```python
// SECURE: environment variables (simple, suitable for containers/12-factor apps)
func connectDB() (*sql.DB, error) {
    password := os.Getenv("DB_PASSWORD")
    if password == "" {
        return nil, fmt.Errorf("DB_PASSWORD environment variable not set")
    }
    return sql.Open("postgres",
        fmt.Sprintf("postgres://app:%s@localhost/prod",
            url.QueryEscape(password)))
}

// SECURE: Viper for structured config with env var binding
import "github.com/spf13/viper"

func initConfig() {
    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv() // MYAPP_DB_PASSWORD → DB_PASSWORD
    viper.BindEnv("db_password")
    viper.BindEnv("jwt_secret")
}

// SECURE: HashiCorp Vault dynamic secrets
import (
    "github.com/hashicorp/vault/api"
)

func getDBCredentials(vaultAddr, role string) (username, password string, err error) {
    config := vault.DefaultConfig()
    config.Address = vaultAddr
    client, err := vault.NewClient(config)
    if err != nil {
        return "", "", err
    }
    // Vault generates a short-lived credential that auto-expires
    secret, err := client.Logical().Read("database/creds/" + role)
    if err != nil {
        return "", "", err
    }
    return secret.Data["username"].(string), secret.Data["password"].(string), nil
}

// SECURE: AWS Secrets Manager
import (
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
)

func getSecret(secretName string) (string, error) {
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        return "", err
    }
    svc := secretsmanager.NewFromConfig(cfg)
    result, err := svc.GetSecretValue(context.TODO(),
        &secretsmanager.GetSecretValueInput{SecretId: &secretName})
    if err != nil {
        return "", err
    }
    return *result.SecretString, nil
}

// SECURE: JWT secret from environment with minimum length validation
func getJWTSecret() ([]byte, error) {
    secret := os.Getenv("JWT_SECRET")
    if len(secret) < 32 {
        return nil, fmt.Errorf("JWT_SECRET must be at least 32 characters; got %d", len(secret))
    }
    return []byte(secret), nil
}

```

## Detection Rule (Python SDK)

```python
"""GO-SEC-004: Hardcoded credentials (passwords, API keys, tokens) in source code."""

from codepathfinder import variable, Or
from codepathfinder.go_decorators import go_rule


@go_rule(
    id="GO-SEC-004",
    severity="HIGH",
    cwe="CWE-798",
    owasp="A07:2021",
    tags="go,security,hardcoded-credentials,secrets,CWE-798,OWASP-A07",
    message=(
        "Detected a variable with a name suggesting credential storage "
        "(password, secret, api_key, token, etc.) being passed to a function. "
        "Hardcoded credentials in source code can be extracted from repositories, "
        "compiled binaries, or version control history. "
        "Use environment variables (os.Getenv) or a secrets manager instead."
    ),
)
def go_hardcoded_credentials():
    """Detects hardcoded passwords, API keys, and tokens passed to functions in Go."""
    return Or(
        variable(pattern="*password*"),
        variable(pattern="*secret*"),
        variable(pattern="*api_key*"),
        variable(pattern="*apikey*"),
        variable(pattern="*token*"),
        variable(pattern="*credential*"),
    )
```

## How to Fix

- Use os.Getenv() as a minimum for all credential values — never hardcode string literals.
- Validate that required secrets are present at startup; fail fast with a clear error message.
- Use HashiCorp Vault or AWS/GCP/Azure Secrets Manager for production workloads.
- Add .env and config files containing secrets to .gitignore before initial commit.
- Run TruffleHog or Gitleaks as a pre-commit hook and in CI to catch secrets before push.
- If a credential is ever committed, rotate it immediately — assume it is compromised.
- Use per-environment secrets — staging and production must never share credentials.
- Store JWT signing keys with minimum 256-bit entropy (32 random bytes from crypto/rand).
- Audit binary artifacts for embedded credentials using `strings binary | grep -iE 'password|secret|key|token'`.
- Follow the principle of least privilege — each service should have its own credentials with minimal scope.

## Security Implications

- **Permanent Exposure via Git History:** Any secret committed to version control persists indefinitely in git history. Even
`git rm` + `git push` leaves the secret in every historical clone, CI/CD artifact,
and backup. Tools like TruffleHog and Gitleaks scan all commits, not just the current
HEAD. The only remediation is credential rotation — the history cannot be practically
sanitized once other parties have cloned the repository.

- **Binary Reverse Engineering:** Go embeds string literals verbatim in compiled binaries. A database password or API
key in a Go string constant appears in the binary's .rodata section readable by the
`strings` command without any disassembly. Compiled binaries distributed to customers,
deployed in container images, or included in GitHub releases expose credentials to
anyone who downloads them.

- **Supply Chain Compromise:** An attacker who compromises a developer's workstation, a CI/CD pipeline, or a source
code repository recovers all hardcoded credentials with a single search. This provides
immediate access to every environment — development, staging, and production — where
those credentials are used. Hardcoded credentials make a single point of compromise
(developer laptop) equivalent to full infrastructure compromise.

- **Credential Reuse Across Environments:** Hardcoded credentials typically use the same value across all environments. When a
staging environment credential is exposed, it often also works in production because
developers hardcoded the same value in both. Per-environment rotation is only possible
with externalized configuration.


## References

- [CWE-798: Use of Hard-coded Credentials — MITRE](https://cwe.mitre.org/data/definitions/798.html)
- [CWE-259: Use of Hard-coded Password — MITRE](https://cwe.mitre.org/data/definitions/259.html)
- [CWE-321: Use of Hard-coded Cryptographic Key — MITRE](https://cwe.mitre.org/data/definitions/321.html)
- [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html)
- [CISA Secure by Design — hardcoded credential guidance](https://www.cisa.gov/resources-tools/resources/secure-by-design)
- [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html)
- [HashiCorp Vault Go SDK documentation](https://pkg.go.dev/github.com/hashicorp/vault/api)
- [AWS Secrets Manager Go SDK (aws-sdk-go-v2)](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/secretsmanager)
- [GCP Secret Manager Go client library](https://pkg.go.dev/cloud.google.com/go/secretmanager/apiv1)
- [Spf13/Viper — Go configuration library with env var support](https://pkg.go.dev/github.com/spf13/viper)
- [TruffleHog v3 — secrets scanning with git history support](https://github.com/trufflesecurity/trufflehog)
- [Gitleaks — secrets detection for git repos](https://github.com/gitleaks/gitleaks)
- [NIST SP 800-57 Part 1 — Recommendation for Key Management](https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final)
- [NIST SP 800-53 Rev 5 — IA-5 Authenticator Management](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final)
- [Go os package — Getenv documentation](https://pkg.go.dev/os#Getenv)
- [12-Factor App — Config (store config in environment)](https://12factor.net/config)

---

Source: https://codepathfinder.dev/registry/golang/security/GO-SEC-004
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
