← All news
configuration4 min read

Your AI Assistant Wrote a Secret Into main.py Again

By Seaworthy · 1 October 2026

3D render of a cloud computing concept

Photo by Growtika on Unsplash

The demo worked. That is the problem.

I watched someone paste a freshly generated FastAPI snippet into a repo last week. The AI assistant had produced a clean route, a tidy client object, and this line right at the top:

OPENAI_API_KEY = "sk-proj-abc123..."

They ran it, it worked, they committed it. Ten minutes later the key was in a pull request. This pattern shows up constantly now because AI coding assistants are very good at giving you a complete, runnable example. A runnable example needs a key. The assistant does not know where your secrets live, so it puts one where the code can see it, which is the source file.

I think this is one of the most predictable security mistakes of the current tooling era, and it barely gets treated like a mistake. It looks like normal code. It runs. It ships.

Why source-stored secrets are worse than they look

A hardcoded secret is not a config choice. It is a disclosure with a delay timer.

Source code travels further than people expect. It goes into git history, CI logs, local backups, editor caches, container layers, and screenshots in a pull request. Once a key is in a commit, rotating it is the only real fix, because deleting the line does nothing to the history. Anyone with a clone still has it.

There is also the environment problem. A key that lives in settings.py is the same key on your laptop, in staging, and in production. You lose the ability to scope access, and you lose the ability to revoke one environment without breaking the others. Some teams learn this when a developer leaves and takes a laptop with them.

AI assistants make it faster to produce this pattern. They also make it easy to miss, because the code is surrounded by other plausible generated code. Nothing looks wrong.

The fix is boring, and boring is good here

Read secrets from the environment. Keep a local .env file that is ignored by git, and let your deployment platform inject real values. Here is the difference:

# Bad: source-stored secret
CLIENT_ID = "9f3a..."
CLIENT_SECRET = "d8c1..."

# Better: read from environment
import os

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]

A common next step is a small loader so local development does not need manual exports:

# config.py
import os
from dotenv import load_dotenv

load_dotenv()  # reads .env locally; no-op when real env vars exist

def require(name: str) -> str:
    value = os.environ.get(name)
    if not value:
        raise RuntimeError(f"Missing required environment variable: {name}")
    return value

CLIENT_ID = require("CLIENT_ID")
CLIENT_SECRET = require("CLIENT_SECRET")

Then add .env to .gitignore and commit a .env.example with the names but no values. That example file is what you paste to your AI assistant when you need it to generate config code. It tells the assistant which variables exist without telling it what they are.

I would go one step further. When you ask an assistant for a snippet that touches an external service, say up front: use environment variables and do not invent values. It is a small prompt change that prevents a large cleanup.

What I tell people who push back

A frequent argument is that env vars are annoying in development. They are slightly annoying. So is rotating a leaked production key at 11pm while checking audit logs. The annoyance is not equivalent.

Another argument is that the secret is low value. Maybe it is a test key. Test keys still hit real APIs, real rate limits, and real billing accounts. Treat them as real.

A third argument is that secrets managers add complexity. They do, at scale. But you can start with plain environment variables and a .gitignore entry. That is not complex. That is basic hygiene, and it is available right now.

The thing that bothers me is how often this shows up in otherwise careful codebases. People review function names and ignore line one. Reviewers scan for logic, not for a string that looks like a UUID. I would like to see more teams add a pre-commit check for high-entropy strings and known key prefixes. It catches the accidental case before the commit exists.

Check your repo today

Search for common prefixes: sk-, ghp_, AKIA, AIza, and long random-looking assignments. Check the git history, not just the working tree. If you find something, rotate it first and clean up after. The cleanup is optional. The rotation is not.

Seaworthy is a tool that scans repositories for exposed credentials and other security issues in source code.

This article was generated by AI and summarises publicly available sources.

From the makers of Seaworthy

Seaworthy scans your repo for the issues covered in articles like this one.

Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.

$ npx seaworthycode