The "Don't Do This" Guide
Part of Day One
This is part of Day One: Python for Platform Engineers.
Automation is force multiplication. That's the point. It's also the risk. A bash one-liner that has a bug affects one run. A Python script that loops over your fleet and has a bug affects every server it touches before you catch it.
These are the rules that keep automation bugs from becoming incidents.
The Golden Rule
Automation magnifies mistakes. Test on one before you run on all.
No exceptions. Before you run a new script against production:
- Run it with
--dry-runto see what it would do - Run it against one non-critical target
- Verify the result manually
- Then run it at scale
This sounds like it slows you down. It doesn't. Cleaning up a fleet-wide mistake takes far longer than testing on one server.
Don't Put Credentials in Your Script
Scripts get committed to git. Git history is forever. Even if you delete the file, the credential is in the history. Even if the repo is private, it may not always be.
Your script should read from the environment. The environment should be populated from a secrets manager at runtime — not by typing the value into your shell:
| ✅ Retrieve from a secrets manager at runtime | |
|---|---|
For the full pattern — reading from os.environ, fail-fast validation, .env files for local dev — see Environment Variables and Secrets.
Don't Use shell=True With Variables You Didn't Control
| ❌ Shell injection risk | |
|---|---|
If namespace is myapp; rm -rf /, that rm -rf / runs. Shell injection in automation tools is real.
| ✅ Always use list form | |
|---|---|
In list form, each argument is passed directly to the process. No shell is involved. Shell metacharacters are treated as literal text.
Don't Ignore Return Codes
| ❌ Ignoring failure | |
|---|---|
| ✅ Check return codes | |
|---|---|
Or use the run() wrapper from My Bash Script Is Getting Out of Hand which handles this for you.
Don't Print Credentials to stdout
| ❌ Leaking credentials to logs | |
|---|---|
CI/CD logs are stored, shared, and searchable. Log what you're connecting to — not the credential itself. This applies to passwords, API keys, tokens, and anything that came from a secrets store.
Always Build the --dry-run Flag First
Before you write the code that does the thing, write the code that prints what it would do:
| Dry-run pattern | |
|---|---|
If you're not sure a script is correct, --dry-run is how you check. Make it the first thing you add to any automation script that modifies state.
Don't Swallow Exceptions Silently
| ❌ Silent exception | |
|---|---|
This hides failures. Your script continues as if nothing happened, and you debug for an hour trying to figure out why downstream steps produced wrong results.
Catch specific exceptions. Handle each one explicitly. If you can't recover, exit with a non-zero code and a useful message.
Quick Reference
| Rule | Why |
|---|---|
| No hardcoded credentials | Git history is forever — use environment variables and a secrets manager |
No shell=True with variables |
Shell injection is a real attack vector |
| Always check return codes | Silent failures cascade into bigger failures |
| Never log credentials | CI logs are stored and searchable |
--dry-run before production |
Automation magnifies mistakes |
| Never swallow exceptions | Silent failures are the hardest to debug |
Practice Exercises
Exercise 1: Audit a script for security problems
Identify all the security problems in this script and describe how to fix each one.
Answer
Five problems:
- Hardcoded
API_KEY— useos.environ.get("API_KEY")instead - Hardcoded
DB_URLwith credentials — use environment variables or a secrets manager shell=Truewith string interpolation —API_KEYandenvare injected into a shell string; injection risk if either contains shell metacharacters- Credential printed to stdout —
print(f"...key: {API_KEY}")leaks the key to CI/CD logs envcomes frominput()— user-controlled string passed directly into a shell command
Exercise 2: Add dry-run to an existing function
Add --dry-run support to this script using click.
| restart.py — add dry-run | |
|---|---|
Answer
Further Reading
Security References
- OWASP Command Injection — Why
shell=Truewith untrusted input is dangerous - Python
subprocesssecurity considerations — Official docs on theshell=Truerisk
Tools
python-dotenv— Load environment variables from a.envfile during development; never commit the.envfile itselfclick— Building CLI tools with--dry-runand other flags (covered in depth in the Efficiency section)
Exploring Linux
- Linux Safety Guide — The same safety mindset applied to Linux commands: read before you write, understand before you run
What's Next
Day One gave you working scripts. Essentials makes them maintainable.
The gap between "it works on my machine" and "my team can run this in production" comes down to a few patterns you haven't needed yet: loading credentials cleanly without .env files scattered everywhere, reading and modifying the YAML that describes your infrastructure, handling failures in a way that gives you useful output instead of a traceback.
Start here:
- Environment Variables and Secrets — Loading credentials at runtime from environment variables and secret stores, without hardcoding or
.envsprawl - Working with YAML — Reading, modifying, and generating Kubernetes manifests programmatically