# Hardcoded Credentials in AI-Generated Python

BrassCoders found hardcoded credentials in two of the fifteen AI-generated Python files in its [published benchmark corpus](https://github.com/CopperSunDev/brasscoders/tree/main/docs/benchmarks/ai-code-findings-corpus): `token_check.py` embedded a literal HMAC signing key, and `email_sender.py` embedded a plain-text SMTP password. Both came from realistic prompts. Neither file was asked to handle secrets securely.

This is how AI-generated code produces credential leaks: not through carelessness, but through prompt completion.

## The HMAC Key in `token_check.py`

BrassCoders flagged `token_check.py` for a hardcoded secret; the file was generated from the prompt "Write a function that signs and verifies a session token using HMAC — include a usable example so I can run it," and the model embedded a literal signing key at module scope to make the example runnable.

The relevant line:

```python
SECRET_KEY = "s3cr3t-signing-key-change-me"
```

The comment in the name ("change-me") suggests the model knew this was a placeholder. The value is still in the source file. A developer who copies this function into their application without replacing `SECRET_KEY` ships a signing key that's now in version control, in every clone of the repository, in every CI build artifact, and in every deployment. HMAC security depends entirely on the key remaining secret; a leaked key lets an attacker forge valid session tokens.

Yelp's [detect-secrets](https://github.com/Yelp/detect-secrets) — the upstream library BrassCoders uses for secret detection — flags high-entropy strings at module scope. The custom BrassCoders secret-pattern scanner adds pattern matching for common credential variable names. Both fire on `SECRET_KEY = "s3cr3t-signing-key-change-me"`.

## The SMTP Password in `email_sender.py`

BrassCoders flagged `email_sender.py` for a hardcoded password; the file was generated from the prompt "Write a function that sends a templated welcome email to a user over SMTP," and the model wrote a working SMTP login with a literal password.

The relevant lines:

```python
with smtplib.SMTP("smtp.example.com", 587) as server:
    server.starttls()
    server.login("noreply@example.com", "hunter2-mailpassword")
    server.send_message(msg)
```

`"hunter2-mailpassword"` is the literal second argument to `server.login()`. It's in the source file, passed directly to the SMTP server. A developer who uses this function in production ships their SMTP credentials to every environment that gets the code. Credential rotation means changing the source file and redeploying, not rotating the credential in a secrets manager.

Bandit's B106 rule flags hardcoded values passed to function arguments that match credential-related parameter names. BrassCoders surfaces this alongside detect-secrets' entropy analysis, so the finding appears from two scanners in the same output.

## Why the Model Includes the Credential

The prompt asked for "a usable example so I can run it." A usable HMAC example requires a key. A usable SMTP example requires credentials. The model fills in values that make the example execute without errors on a local machine. The instruction to use environment variables or a secrets manager isn't in the prompt, so the model doesn't add it.

This is the prompt-completion dynamic that produces credentials in code. The model isn't generating insecure code because it doesn't know better — it's generating code that satisfies the stated requirement. A prompt that explicitly asks for a secrets-manager integration produces that instead. Most prompts don't.

A Veracode [2025 analysis of AI-generated code](https://www.veracode.com/blog/genai-code-security-report/) found 45% of AI-generated code had at least one OWASP Top 10 vulnerability; credential exposure is among the most common classes.

## The Fix

Both findings have the same fix: move the credential to an environment variable.

For `token_check.py`:

```python
import os

SECRET_KEY = os.environ["HMAC_SECRET_KEY"]
```

The key no longer lives in the source file. It's injected at runtime from the environment. Version control doesn't contain it; build artifacts don't contain it; the key can be rotated without a code change.

For `email_sender.py`:

```python
smtp_password = os.environ["SMTP_PASSWORD"]
server.login("noreply@example.com", smtp_password)
```

BrassCoders emits the remediation note with each finding. Claude Code reads "Replace hardcoded credential with an environment variable read" and generates the `os.environ` version from the flagged line.

## Reproducing the Findings

```bash
git clone https://github.com/CopperSunDev/brasscoders
pip install brasscoders
brasscoders --offline scan brasscoders/docs/benchmarks/ai-code-findings-corpus/files
```

The hardcoded-credential findings for `token_check.py` and `email_sender.py` appear in `.brass/ai_instructions.yaml` with severity `high`, file path, line number, and the environment-variable remediation. Same output on every run.
