DEV Community

CopperSunDev
CopperSunDev

Posted on • Originally published at coppersun.dev

Hardcoded Credentials in AI-Generated Python

BrassCoders found hardcoded credentials in two of the fifteen AI-generated Python files in its published benchmark 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:

SECRET_KEY = "s3cr3t-signing-key-change-me"
Enter fullscreen mode Exit fullscreen mode

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 — 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:

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

"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 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:

import os

SECRET_KEY = os.environ["HMAC_SECRET_KEY"]
Enter fullscreen mode Exit fullscreen mode

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:

smtp_password = os.environ["SMTP_PASSWORD"]
server.login("noreply@example.com", smtp_password)
Enter fullscreen mode Exit fullscreen mode

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

git clone https://github.com/CopperSunDev/brasscoders
pip install brasscoders
brasscoders --offline scan brasscoders/docs/benchmarks/ai-code-findings-corpus/files
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)