Don’t Let the “If It Works, It’s Fine” Mindset Deceive You
When I first started dabbling in web development, I used to think that as long as the code produced the right results, I was done. Solid logic, a smooth UI, green test cases – that was enough to deploy with confidence. But reality is different. A single eval() function used carelessly for string processing, or an “ancient” library forgotten without updates, is enough for a hacker to turn your server into their own playground.
My project grew from an initial 200 lines to over 2,000 lines in just a few months. At this stage, manually scrutinizing every line of code for security flaws is impossible. That’s why you need to integrate DevSecOps thinking into your workflow from the start. Instead of scrambling to patch things after being attacked, scanning for vulnerabilities automatically while coding will help you sleep much better.
Why Is Python Source Code an Easy Target?
Python is famous for being easy to learn, but this very flexibility often makes developers complacent. The problems usually lie in these three blind spots:
- Logic Black Holes: Carelessly hardcoding API keys, using
exec()for flexible script execution, or calling system commands viasubprocesswithout filtering input. - Third-party Libraries (Dependencies): We often
pip installanything we find online. Are you sure that library doesn’t contain a CVE vulnerability that’s been public for ages? - Configuration Mistakes: Forgetting to turn off
DEBUG = Truein production is a classic error, exposing all environment information when an error occurs.
Bandit: A “Microscope” for Source Code Flaws
Bandit is a static analysis tool (SAST) specifically designed for Python. It doesn’t execute code but scans source files to find unsafe programming patterns based on a predefined set of rules.
Quick Installation
pip install bandit
Applying it in Practice
Suppose your app.py file contains a few “time bombs” like this:
import subprocess
import yaml
# Injection Risk: Hackers can insert additional commands like '&& rm -rf /'
def run_ping(ip):
subprocess.run(f"ping -c 4 {ip}", shell=True)
# Using load() instead of safe_load() is vulnerable to remote code execution
def load_config(data):
return yaml.load(data)
# Never leave passwords here!
DB_PASSWORD = "admin123"
When running the command bandit -r app.py, the tool will immediately flag these issues. Bandit categorizes errors by severity: Low, Medium, and High. For example, it will specifically point out that using shell=True is extremely dangerous. A hacker only needs to enter 127.0.0.1; cat /etc/passwd into the input field to steal your system data.
Safety: A Shield Against Malicious Libraries
While Bandit examines the code you write, Safety checks what you “borrow” from the community. Most Python projects today rely on dozens of libraries from PyPI. Safety cross-references your requirements.txt file with a security vulnerability database (like PyUp.io) to provide early warnings.
Installation
pip install safety
Quick Dependency Scan
You only need to run a single command:
safety check -r requirements.txt
The results will be very specific. For instance, if you’re using Django==2.2.1, Safety will immediately report that this version is affected by CVE-2019-14234 and require you to upgrade to 2.2.2 or higher for safety. This is a vital step before packaging your application into Docker or pushing it to a server.
Integrating Security into the Automation Pipeline
Don’t wait until you’re free to run security scans. Integrate them directly into Git Hooks or your CI/CD pipeline. Every time you commit, the system will automatically scan. If a “High” severity error is detected, the commit will be blocked until you fix it.
A practical workflow I often use through a simple script file:
# run_security_scan.sh
echo "--- Scanning for source code flaws (Bandit) ---"
bandit -r . -x ./venv
echo "\n--- Checking libraries (Safety) ---"
safety check
Nipping Vulnerabilities in the Bud
Tools are just assistants; your security mindset is what matters most. After several times of being “called out” by Bandit, I’ve drawn three non-negotiable principles:
- Always distrust all input: Treat every piece of user-submitted data as malicious. Use whitelists for filtering instead of trying to block individual characters.
- Choose secure defaults: Prioritize
yaml.safe_load()overyaml.load(). When using subprocess, pass parameters as a list instead of usingshell=True. - Isolate Secrets: Never commit passwords to GitHub. Use a
.envfile combined withpython-dotenvto manage environment variables.
Conclusion
Security is not the exclusive privilege of security experts. With Bandit and Safety, anyone can build a basic yet incredibly solid defense. Try scanning your project today. You might just discover some “silly” vulnerabilities that have been lurking for a long time. Good luck building systems that are both fast and impenetrable.

