It’s not about new frameworks or tools; it’s about writing code that never trusts anything you didn’t create yourself.

Introduction: The Habit Developers Forget
A few years ago, I built a small internal dashboard for a client. It worked fine for months until someone reported that opening a page triggered random pop-ups.
No server crashes, no logs, no suspicious users. Just a pop-up.
After digging through code, I realized what had happened: a single form field was being stored and rendered without validation or escaping. A script tag was inserted, and it executed in every browser that opened the page.
That’s all it took, one careless assumption that user input was safe.
The problem wasn’t the framework. It wasn’t the database.
It was me forgetting a simple habit: validate and sanitize every single piece of data.
That habit alone could have prevented both the XSS and the SQL injection vulnerabilities that keep showing up in production systems year after year.
1. The “Never Trust Input” Habit
The simple habit is this:
Never trust any input that didn’t come from your own code.
That means everything from fields, query strings, headers, cookies, third-party API data, and even internal service calls.
Most developers validate on the frontend and assume that’s enough. But attackers don’t care about your UI. They talk directly to your backend using Postman, cURL, or scripts.
Every request that hits your server is just raw data.
If you don’t inspect, validate, and sanitize it, you’ve left your app’s door wide open.
This mindset shift is small but transformative. Once you adopt it, every line of code you write changes slightly:
- Every input is questioned.
- Every query parameter is checked.
- Every string is treated as potentially unsafe until proven clean.
2. How XSS Starts and Why Validation Stops It
Cross-Site Scripting (XSS) happens when malicious data ends up being executed as code in someone’s browser.
The classic example:
// vulnerable rendering
document.getElementById('profile').innerHTML = user.bio;
A normal user might store:
Hi, I love web development!
But a malicious user could store:
<script>alert('Hacked');</script>
When that data is rendered with innerHTML, the script executes because the browser doesn’t know it wasn’t supposed to.
How the simple habit prevents it:
When you treat every input as unsafe:
- You validate it first:
biomust be a string, max 500 characters. - You sanitize or escape it before rendering.
- You avoid functions that interpret text as HTML.
Example fix:
const div = document.createElement('div');
div.textContent = user.bio; // renders text safely
Or use a library like DOMPurify if you allow limited HTML formatting:
import DOMPurify from 'dompurify';
profile.innerHTML = DOMPurify.sanitize(user.bio);
Result:
Even if someone tries to insert <script>, it’ll be stripped out or treated as text.
3. How SQL Injection Happens and Why Validation Stops It
SQL injection works because code mixes data and commands.
Here’s what that looks like:
// vulnerable code
const email = req.body.email;
const query = `SELECT * FROM users WHERE email = '${email}'`;
const user = await db.query(query);
Looks simple until someone sends this:
email: ' OR '1'='1
Now the query becomes:
SELECT * FROM users WHERE email = '' OR '1'='1'
That condition always evaluates to true, giving the attacker full access to your user table.
How the simple habit prevents it:
You validate email before touching the database:
- Must be a valid email format.
- Must not contain quotes or special SQL characters.
- Must match your schema’s expected type.
Then you use parameterized queries instead of string concatenation:
const query = 'SELECT * FROM users WHERE email = $1';
const user = await db.query(query, [email]);
Now, even if the input contains ' OR '1'='1', it’s treated as data, not as part of the SQL command.
The database never executes it as code.
4. What “Validate and Sanitize Everything” Actually Looks Like
The habit becomes practical when you apply it consistently.
Here’s how:
a. Validate Structure and Type
Every request body should match a known schema.
import { z } from 'zod';
const registerSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
password: z.string().min(8).max(100),
});
const result = registerSchema.safeParse(req.body);
if (!result.success) return res.status(400).send('Invalid input');
This stops malformed requests before they reach business logic.
b. Sanitize and Escape Output
Even valid input can become dangerous when displayed.
Sanitize HTML, escape strings in templates, and avoid untrusted rendering functions.
c. Use Framework Features
- React / Vue / Angular: variables in JSX/templates are automatically escaped. Don’t bypass that safety using
dangerouslySetInnerHTML. - SQL Libraries: most modern ORMs (Sequelize, Prisma, TypeORM) parameterize automatically. Never interpolate manually.
- Templating Engines: use safe interpolation (
<%= %>in EJS or{{}}in Handlebars).
d. Reject Anything Unexpected
Reject extra or unknown fields.
If your schema expects email and password, a field like isAdmin should trigger rejection, not insertion.
5. Real-World Example From Bug to Habit
A SaaS company I worked with had a “feedback” form that allowed HTML formatting for styling comments.
A user inserted a <script> tag, which was later loaded on the admin dashboard, giving the attacker access to admin cookies and internal tools.
No external hacker. No complicated exploit. Just stored XSS.
The fix wasn’t complex, just a consistent rule:
Any data displayed in the browser must be sanitized or escaped.
That small policy change, combined with input schema validation at every API endpoint, stopped similar bugs across the codebase.
It wasn’t a single patch. It was a new habit: never render, store, or query data you haven’t verified.
6. Why This Habit Works Better Than “Security Features”
You can use every security feature under the sun, firewalls, WAFs, proxies, encryption, and still be vulnerable if you skip validation and sanitization.
Why? Because these attacks target your logic, not your network.
They exploit assumptions:
- “This field will always be safe.”
- “This API is internal.”
- “The frontend already checks that.”
But the moment you validate and sanitize everything, those assumptions disappear.
You no longer rely on trust; you rely on proof.
And that’s the mindset difference between “hoping your code is safe” and knowing it is.
7. Turning the Habit Into a Culture
Security habits stick when they’re reinforced in code reviews, tests, and CI pipelines.
- In code review: Ask, “Where is this input validated?”
- In tests: Include invalid payloads (empty strings, long inputs, HTML tags, quotes).
- In CI: Run static analysis (Semgrep, ESLint plugins) to catch unsafe patterns.
Example ESLint rule: flag every use of innerHTML or SQL string concatenation.
The goal isn’t paranoia, it’s consistency.
Once validation becomes part of your normal workflow, you won’t have to think about it anymore.
8. Quick Checklist: How to Apply the Habit Everywhere
Server:
- Validate all inputs (body, params, query).
- Use parameterized queries for all database operations.
- Sanitize or escape output before returning to clients.
Frontend:
- Avoid
innerHTML, use textContent or framework-safe rendering. - Sanitize any user-supplied HTML (comments, bios, messages).
- Never trust API data without validation or encoding.
DevOps / CI:
- Add security lint rules.
- Write negative tests (malformed payloads).
- Review security in every PR.
Conclusion: One Habit, Two Vulnerabilities Solved
You don’t need to memorize every kind of exploit.
You just need one habit that prevents most of them:
Validate and sanitize everything.
- XSS fails because unsafe input never reaches the browser unescaped.
- SQL injection fails because unsafe data never reaches the database unsanitized.
This habit scales better than any patch or library because it’s built into how you think about code.
Good developers write working features.
Great developers write features that can’t be weaponized.
Call to Action
Pick one form, one API endpoint, or one database query from your project today.
Ask yourself: “What happens if I send unexpected input?”
If the answer is “I don’t know,” add validation and sanitization right now.
That’s the one habit that will quietly protect every app you ever build.


Leave a Reply