Your Bot Token Is in Your Git History — Rotate It, Don't Scrub It
If your bot token has ever been committed to a repository, pasted into a chat, or shown in a screenshot, the fix is to regenerate the token — not to remove it from your history. Rewriting commits doesn't un-send what was already pushed, and scanners index public repositories within minutes. Treat the old token as burned, issue a new one, and put it where it belongs: an environment variable on the app, set in the panel, never baked into the image or committed to the repo. That takes about two minutes and it's the only step that actually revokes access.
Rotate first, in this order
Do this before anything else, including cleaning up the repo:
- Issue a new token. For Discord, the Python Discord guide puts it simply: "Press the Regenerate button to regenerate your bot token and invalidate the old one." For Telegram, message BotFather and use
/revoke. - Update the environment variable on your app.
- Restart so the new value is picked up — changing an env var recreates the container, so this happens as part of applying it.
- Then clean up the repo, at your leisure. It's hygiene now, not remediation.
The old token stops working the moment you regenerate. Anyone who copied it gets disconnected. That's the whole point, and it's why this step comes before the tempting one.
Why scrubbing history isn't the fix
git push sends history, not your working tree. Deleting a line in a later commit leaves the old blob reachable in the old commit, and anyone who cloned before your cleanup still has it. Even a full rewrite only changes what your copy looks like going forward.
Two things make this worse than it sounds:
- Scanners are fast. Public repositories are watched continuously. The window between pushing and being indexed is not long enough to matter.
- Automated reporting exists and works. Discord is one of GitHub's secret-scanning partners for Discord Bot Tokens, and partner alerts are, per GitHub's docs, "reported directly to secret providers that are part of secret scanning's partner program." That's why the usual first symptom is a bot that abruptly stops working and an email you didn't expect.
There's a platform-specific wrinkle too. The deploy repo here is push-only — you can't clone or fetch from it — so you can't inspect what's in it or verify a cleanup landed. Anything you've ever pushed to it should be considered pushed.
Never put a secret in the image
This is the mistake that outlives the token, because it survives every redeploy.
# don't
ENV DISCORD_TOKEN=MTIzNDU2Nzg5...
An ENV line is baked into an image layer. Anyone who can pull the image can read it back out of the layer metadata — no running container required. The same applies to a token in a config file that gets COPY'd in, and to a hardcoded string in your source.
Secrets belong to the deployment, not the artifact. That separation is what lets you rotate a token without rebuilding, and lets you run the same image in staging with different credentials.
Where the token should live
An environment variable on the app. Set it in the panel, read it at startup, and fail loudly if it's missing:
import os, sys
TOKEN = os.environ.get("DISCORD_TOKEN")
if not TOKEN:
sys.exit("DISCORD_TOKEN is not set")
Exiting beats starting up half-configured — you get a clear message instead of a confusing auth error twenty lines later, and an exit code you can read.
Two useful details: environment variable values allow newlines, so a multi-line private key works fine as a single variable, and there's a generous size ceiling — you won't hit it with a token. If a secret really is file-shaped and your library insists on a path, use a config file instead; those are mounted read-only, so nothing can rewrite them at runtime.
If you deploy by uploading a zip, the AI agent handles this deliberately: when it detects that your app needs a credential, it pauses and asks you to supply it, and the value goes straight into the app's configuration — it isn't part of what the agent reads or writes into your build.
Your deploy URL is also a secret
Easy to overlook. The git deploy remote embeds generated credentials in the URL itself — that's why the panel masks it on screen. Anyone with that URL can push code to your app, which is a more direct kind of access than the bot token.
So: never paste it into a public workflow file, an issue, or a screenshot. In CI it goes in a repository secret, exactly like any other credential — the GitHub Actions setup covers the shape.
Prevent the next one
Cheap habits, in the order that pays off:
.gitignoreyour.envbefore you write it. Most leaks are a file that was never meant to be tracked and got caught bygit add -A.- Commit a
.env.examplewith the variable names and no values. Collaborators learn what's needed; nothing sensitive ships. - Never log the token. A debug line printing the config on startup puts your secret straight into the log stream — and into anyone's screenshot of it.
- Scan before you commit. A pre-commit hook running a secret scanner catches this at the point where cleanup is still free.
- Rotate on team changes. When someone stops working on the project, the token they had still works until you regenerate it. This matters more here because apps are single-owner and shared credentials are the informal workaround.
FAQ
My Discord bot token was committed to GitHub. What do I do?
Regenerate it in the Developer Portal, update the environment variable on your app, and restart. Clean the repo afterwards — that part isn't the fix.
Can I just remove the token in a new commit?
No. The old commit still contains it, anyone who cloned already has it, and scanners see public pushes almost immediately.
Is it safe to put the token in my Dockerfile?
No. ENV values are baked into image layers and readable by anyone who can pull the image, without ever running it.
Where should I store my bot token?
An environment variable on the app, set in the panel. It's part of the deployment, not the build, so you can rotate it without rebuilding.
Why did my bot stop working right after I pushed my code?
Likely the token was detected in a public repository and invalidated. Regenerate it and move it out of the repo.
Is my git deploy URL sensitive?
Yes. It contains credentials that allow pushing code to your app, which is why the panel masks it. Store it as a CI secret.
Wrapping up
Leaking a token is routine — nearly everyone does it once, usually with a .env that slipped into a git add -A. What separates a non-event from a real incident is the response. Regenerate immediately, because that's the only action that revokes anything. Move the value out of the repo and out of the image, so the next leak has nothing to find. Then, whenever you feel like it, tidy the history — knowing it was never the thing protecting you.