
Passwords leak from boring places, not dramatic ones
What you will learn
- Three ways a password escapes with nobody attacking anything
- The one you should go and check on your own machine right now
- Why saying it out loud immediately is the whole difference
Nobody broke into anything. There was no attacker, no clever technique, no moment of drama. In one ordinary week I found three of my own passwords sitting somewhere they should not have been, and all three got there through completely mundane, sensible at the time decisions.
The first: written straight into a program
I had put it there while getting something working, meaning to move it later, and later never arrived because the thing worked.
This is how almost every password in a program is born. Not carelessness. Momentum.
The second: go and check this one right now
I had done the right thing and made a backup of a settings file before editing it. The original was locked down so only I could read it.
The backup copy was created with ordinary permissions, which meant anything running on that machine could read it.
I had carefully protected the front door and left an identical copy of the key on the doorstep.
Backups inherit your data and not your protection. Every time you copy a sensitive file, the copy starts with whatever the default is, not whatever the original had.
If you have ever made a backup of a settings file, you probably have one of these right now.
The third: my favourite, because it was so avoidable
I was testing something that sends a notification, and to test it safely I replaced the real sending part with a fake one.
My replacement only covered the first line of a three line section. The remaining two lines still ran, and one of them contained the password. It printed straight into the output of my session.
The value was never sent anywhere, the real program was untouched, and I rebuilt the test so the fake now proves it replaced the whole thing.
But it was in output I had not intended it to be in, so the honest response is to say so immediately and replace it. Not later. Not after checking whether it really counts.
The instinct to quietly decide it was probably fine is the instinct that turns a non event into an actual incident.
The thing that makes all three worse
Reuse.
That password was not only in a program. It was the same password used somewhere else, because it was easier to remember one.
So a single leak from the most boring possible place opens more than one door, and replacing it means chasing down every place it lives, which is a list you probably do not have.
A secret in your code, your backups, or your test output is not a secret any more. It is a countdown.
Get passwords out of your programs, lock down the copies you have already made, keep them out of test output, and search for the ones you have forgotten. Every command here is safe to run and prints no actual values.
1. One place, referred to by name
What these do: make a locked folder, write the secret into a file inside it, and lock that file so only you can read it.
The idea: the secret lives in exactly one place, and everything else refers to it by its name. The name is safe to write anywhere. The value never appears in your code.
mkdir -p ~/.config/app-secrets
chmod 700 ~/.config/app-secrets
printf 'SERVICE_TOKEN=%s\n' "$TOKEN" > ~/.config/app-secrets/env
chmod 600 ~/.config/app-secrets/env
2. Read it when the program starts, and fail loudly
What this does: looks the secret up by name and stops with a clear message if it is missing.
What it must never do: fall back to a built-in default. A default password is worse than no password.
import os
TOKEN = os.environ.get("SERVICE_TOKEN")
if not TOKEN:
raise SystemExit("SERVICE_TOKEN is not set. Refusing to start.")
3. Fix the permissions on every copy you have made
What this does: finds sensitive files that other users on the machine can read, including backups you forgot you created.
This is the highest value command on this page. Run it now.
What should happen: ideally nothing is listed. Fix anything that is, one at a time.
find ~ -type f \( -name "*.env*" -o -name "*secret*" -o -name "*.pem" \) \
! -perm 600 -ls 2>/dev/null
chmod 600 path/to/that/file.env.bak
4. Make your backup habit keep the protection
What this does: copies the file and its protection.
The difference: one extra character. Without it, the copy gets ordinary permissions and you have just created the problem from the story above.
cp -p config.env config.env.bak # -p keeps the protection
5. Search your own project
What these do: the first looks for credential-shaped names in your tracked files. The second tells your project to ignore secret files in future.
Note it searches for names, not values, so it is safe to run and safe to read the output.
git grep -nIE '(API_)?KEY|TOKEN|SECRET|PASSWORD' -- . | grep -v '\.example'
printf '*.env\n*.env.*\n*secrets*\n' >> .gitignore
6. Keep secrets out of test output
What this does: checks that your fake replacement actually replaced the whole thing, and refuses to run if any of the real version is still there.
Why: my leak happened because a replacement covered one line out of three and the rest still ran.
grep -q 'REAL_SEND_MARKER' ./test-harness.sh \
&& { echo "STUB INCOMPLETE, refusing to run"; exit 1; }
7. Hide most of it when you print
What this does: shows the first two and last two characters and hides the rest.
Use it everywhere you are tempted to print a secret while working something out. The result is safe to paste anywhere.
def mask(v):
return "unset" if not v else v[:2] + "..." + v[-2:]
print("token:", mask(TOKEN))
8. Replace it on discovery, everywhere it lives
What this does: lists which files mention that secret by name.
What to do: the moment a secret turns up somewhere it should not be, replace it. Do not sit and assess whether it really counts.
grep -rlI --exclude-dir=.git 'SERVICE_TOKEN' ~/ 2>/dev/null
9. Say it out loud immediately
If you are working with anybody, tell them the moment you notice, including when you caused it.
Handling it quietly is how a small exposure becomes a real incident, and the person who reports their own mistake early is the person worth trusting with the next secret.
Related: one key per job so a leak stays contained, and measure before you delete, which is how that readable backup was found in the first place.
This page in the original Kyber Cypher voice: Secrets leak from the boring places