- Stealth Security
- Posts
- 10 Signs Your Code Is Vulnerable (And How to Protect Against It)
10 Signs Your Code Is Vulnerable (And How to Protect Against It)
If you want secure code, you must first know what bad code looks like.
Writing code is easy. Writing secure code is hard.
The truth is, most developers don’t realize their code is vulnerable until something breaks. Or, worse, until someone attacks it.
In this tutorial, we’ll see 10 clear signs that your code might be vulnerable to attacks. And more importantly, how to fix it.
1. Hardcoded Credentials
This one is everywhere. Maybe you’ve seen it yourself — an API key sitting right there in the code. A database password written in plain text.
It looks like this:
DB_PASSWORD = "supersecret123"
API_KEY = "sk_test_abc123"
If this code leaks (and it will), attackers can do whatever they want. They can log into your systems, steal your data, or run up huge bills on cloud services — all without breaking a sweat.
And here’s the scary part: this kind of leak doesn’t just happen when your whole project gets hacked. It can happen when someone pushes code to GitHub and forgets to add .env
to .gitignore
. Boom — your secret keys are now public.
How to protect against it
Never hardcode sensitive data like API keys, database passwords, or tokens. Instead:
Use environment variables. These are hidden from the source code and can be safely managed per environment (dev, test, production).
Use a secure vault, like HashiCorp Vault, AWS Secrets Manager, or even a
.env
file (with proper access control).Keep secrets out of version control. Always use
.gitignore
to exclude config files that hold secrets.
Run a secret scanner (like GitLeaks or TruffleHog) before every push. These tools can catch sensitive info before it leaks.
2. No Input Validation
If you trust user input, you’re already in trouble. Attackers love sending weird stuff — super long strings, funky characters, or unexpected formats.
Here’s what it looks like:
username = request.GET['username']
print("Hello " + username)
Now someone enters:
username=Robert'); DROP TABLE users; --
Boom. You’ve just been SQL injected. Your database table? Gone.
Without validation, your app can break or even be hijacked. Bad input can lead to issues like SQL injection, cross-site scripting (XSS), and general bugs.
Basically, you’re giving attackers a blank check.
How to Protect Against It
Validate everything. If it’s supposed to be an email, check the format. If it’s a number, make sure it’s a number.
Use strict data types. Don’t just assume input is clean. Make it pass a test.
Limit input length. No one needs a 5,000-character username.
Escape special characters. Especially if you’re using input in HTML or SQL.
Use parameterized queries. Never build SQL strings from raw user input.
3. Poor Error Handling
This is what lazy error handling looks like:
except Exception as e:
print(e)
Or worse:
except:
pass
Silent errors are dangerous. And showing full error messages to users? That’s handing over a map to your system.
Imagine a database error pops up in production, and your app spits out something like:
psycopg2.OperationalError: could not connect to server: Connection refused
Great — now attackers know what database you’re using, and they might start poking around.
How to Protect Against It
Log detailed errors — but do it securely. Use logging tools or services, and don’t store logs where users can see them.
Show users simple messages like:
"Oops! Something went wrong. Please try again later."
That’s all they need to know.Never expose stack traces in production. Turn off debug mode and use proper error pages.
Handle specific exceptions where possible, so you know exactly what failed and why.
Example:
try:
process_data()
except ValueError as e:
logger.error(f"Data error: {e}")
return "Invalid input. Please check your data."
except Exception as e:
logger.exception("Unexpected error")
return "Something went wrong. Try again later."
Use error monitoring tools like Sentry, Rollbar, or LogRocket. They catch errors, track them, and help you fix them — before users even notice.
4. Outdated Dependencies
Using old packages is like leaving your front door wide open. Attackers know exactly where the weak spots are — and they actively scan for them.
If your package.json
or requirements.txt
file hasn’t changed in years, that’s a red flag.
How to Protect Against It
Update regularly. New versions often patch security flaws.
Audit your dependencies. Use tools like
npm audit
andpip-audit
based on your codebase.Automate updates with tools like Dependabot, Renovate, or PyUp.
Even small packages can have big impacts. Stay updated, stay safe.
5. No Authentication or Weak Authentication
If your app lets anyone in without verifying who they are — that’s game over. Weak logins are just as dangerous.
Common mistakes include
No password complexity rules
Storing passwords in plain text
No account lockout after repeated failed logins
How to Fix It
Hash passwords using strong algorithms like
bcrypt
Enforce strong password policies (min length, symbols, etc.)
Use Multi-Factor Authentication (MFA) for extra protection
Example in Python:
import bcrypt
hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
A few extra lines of code can stop a full-blown breach.
Authentication checks who you are. Authorization checks what you can do. Skipping the second one is like giving everyone admin access.
Example Mistake
@app.route('/user/<id>')
def get_user(id):
return User.query.get(id)
No check to see if the current user is allowed to view that data.
How to Fix It
Always verify ownership and roles before showing or modifying data.
Implement access control rules across your API and frontend.
Don’t trust IDs from the frontend — verify on the backend too.
7. Exposed Sensitive Data in URLs
Ever seen a password reset link like this?
https://example.com/reset-password?token=abcd1234
Looks harmless — but it’s not. Tokens, session IDs, and API keys should never be in URLs. They get saved in:
Browser history
Server logs
Analytics tools
How to Fix It
Use HTTP headers or POST requests to send sensitive data
Avoid GET-based auth actions — stick with secure alternatives
8. No Rate Limiting
Without rate limits, attackers can hit your server with:
Brute-force login attempts
Form spam
Denial of Service (DoS) attacks
How to Fix It
Set a max request limit per IP or user. Use tools like:
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address)
Or configure NGINX with built-in rate limiting. Stop abuse before it starts.
9. Unsafe File Uploads
Letting users upload files? Cool. But if you’re not careful, they can:
Upload malware
Overwrite key files
Execute scripts on your server
Example Mistake
file. Save(f"/uploads/{file.filename}")
That filename could be anything — even "../../../etc/passwd"
.
How to Fix It
Check file types (not just extensions — inspect content)
Rename files before saving
Store files outside your web root
Limit file sizes to prevent denial of storage attacks
10. Missing HTTPS
If your app still uses plain old HTTP, all data travels in the open — including:
Passwords
Tokens
Personal info
Attackers can sniff it all with tools like Wireshark.
How to Fix It
Use HTTPS everywhere
Get a free SSL cert from Let’s Encrypt
In Flask, redirect insecure traffic:
@app.before_request
def before_request():
if not request.is_secure:
return redirect(request.url.replace("http://", "https://"))
Encrypting traffic is not optional — it’s table stakes for modern apps.
Final Thoughts
Writing secure code isn’t about being perfect. It’s about being careful. Slow down. Look at your code with fresh eyes. Think like an attacker. Plan for failure before it happens.
The best security isn’t patched in later — it’s baked in from the start.
New to cybersecurity? Check out our Security Starter Course.
Reply