October 25, 2026
Rule #1 for Developers: Never Trust User Input
Rule #1 for Developers: Never Trust User Input

By Kheang Panda
5 min read
The security lesson every software developer should learn before writing production code.
Imagine you've just finished developing a new application.
The user interface looks great. Your APIs are working. Your database is connected, and all your tests are passing.
You deploy the application to production.
Everything looks perfect.
Until someone sends a request that your application was never designed to handle.
Maybe they change a user ID in the URL. Maybe they manipulate a product price. Or maybe they submit unexpected input that your backend processes without checking.
Suddenly, your application behaves in ways you never intended.
And the interesting part?
The attacker didn't need to break your code. They simply used your application differently from how you expected.
This brings us to one of the most important security principles in software development:
Rule #1: Never Trust User Input.
1. Your Application Doesn't Know Who to Trust
When developing an application, we often assume users will interact with it through the interface we designed.
For example, imagine building a simple online shopping application.
The frontend sends this request:
{
"product_id": 101,
"quantity": 2,
"price": 50
}{
"product_id": 101,
"quantity": 2,
"price": 50
}Your backend receives the request and calculates the total:
const total = req.body.quantity * req.body.price;const total = req.body.quantity * req.body.price;Looks reasonable, right?
But there's a problem.
What happens if someone changes the request to:
{
"product_id": 101,
"quantity": 2,
"price": 0.01
}{
"product_id": 101,
"quantity": 2,
"price": 0.01
}If your backend trusts the price provided by the client, the customer might purchase a $50 product for just $0.01 per item.
Your frontend might prevent users from changing prices.
But attackers don't have to use your frontend.
They can send requests directly to your API.
The frontend is not a security boundary.
The better approach
The backend should retrieve the product's trusted price from the database and calculate the amount itself.
const product = await db.products.findById(
req.body.product_id
);
// Validate quantity and product availability first.
const total = product.price * quantity;const product = await db.products.findById(
req.body.product_id
);
// Validate quantity and product availability first.
const total = product.price * quantity;In a real checkout system, you should also validate quantity, apply server-side discounts, and verify the final amount within an appropriate transaction.
The lesson is simple:
Never let the client decide something that the server is responsible for controlling.
2. Being Logged In Doesn't Mean You Can Access Everything
Now imagine you're building a hospital management system.
A doctor logs in and opens a patient's record.
The API request looks like this:
GET /api/patients/1001GET /api/patients/1001Everything works.
But what if the doctor changes the ID?
GET /api/patients/1002GET /api/patients/1002Should the backend return the second patient's information?
Not necessarily.
Even though the doctor is authenticated, they might not be authorized to access that particular patient's record.
This is the difference between two important security concepts.
Authentication: Who are you?
Authorization: What are you allowed to access?
Developers sometimes implement authentication correctly but forget to enforce authorization on each protected operation.
The backend must check the user's permissions, resource ownership, or other applicable access rules.
In a multi-tenant application, it must also ensure that one tenant cannot access another tenant's data.
For example:
const patient = await db.patients.findFirst({
where: {
id: patientId,
tenantId: req.user.tenantId
}
});const patient = await db.patients.findFirst({
where: {
id: patientId,
tenantId: req.user.tenantId
}
});This illustrates tenant scoping, but it isn't sufficient by itself. The application must also enforce the user's patient-level permissions and any other applicable access rules.
Never assume that a valid login automatically grants access to every resource.
3. Input Validation Is Not Just Checking Empty Fields
Many developers associate validation with simple frontend checks.
For example:
if (!email || !password) {
return res.status(400).json({
message: "Required fields are missing"
});
}if (!email || !password) {
return res.status(400).json({
message: "Required fields are missing"
});
}This is useful, but input validation goes much further.
You should consider:
- Data types and expected formats.
- Maximum and minimum values.
- String lengths and array sizes.
- Allowed values.
- Business rules.
- Unexpected or nested input.
- Whether the user is authorized to perform the operation.
Imagine a money transfer API:
{
"receiver_id": 2002,
"amount": -500
}{
"receiver_id": 2002,
"amount": -500
}If the application doesn't validate the transfer amount properly, a negative value might produce unintended behavior.
A better approach is to define explicit validation rules.
if (
typeof amount !== "number" ||
!Number.isFinite(amount) ||
amount <= 0
) {
return res.status(400).json({
message: "Invalid transfer amount"
});
}if (
typeof amount !== "number" ||
!Number.isFinite(amount) ||
amount <= 0
) {
return res.status(400).json({
message: "Invalid transfer amount"
});
}For financial systems, use integer minor units or suitable decimal types rather than relying on floating-point arithmetic for money.
And remember: validation is one security layer. You still need authorization, balance checks, transaction controls, and protections against duplicate requests.
4. Your Database Doesn't Know Whether Input Is Safe
Imagine you're building a search feature.
A developer writes:
const query = `
SELECT * FROM products
WHERE name = '${search}'
`;const query = `
SELECT * FROM products
WHERE name = '${search}'
`;It works when users search for ordinary product names.
But what happens when a user supplies unexpected SQL syntax?
Because the SQL statement is constructed by combining executable SQL with untrusted input, the query may behave differently from what the developer intended.
This is the foundation of a vulnerability called SQL Injection.
The safer approach is to use parameterized queries.
const result = await db.query(
"SELECT * FROM products WHERE name = $1",
[search]
);const result = await db.query(
"SELECT * FROM products WHERE name = $1",
[search]
);This keeps user-provided values separate from the SQL command.
It is important to understand that simply removing suspicious characters is not a reliable substitute for parameterized queries.
Also, if input will be displayed in a webpage, use appropriate output encoding to help prevent cross-site scripting (XSS).
Different risks require different security controls. One validation function cannot solve every security problem.
5. Internal APIs Shouldn't Automatically Be Trusted Either
Here's something developers sometimes overlook.
Security isn't only about requests coming from a browser.
Consider a distributed application:
Mobile App
|
v
API Gateway
|
v
Order Service
|
v
Payment Service
|
v
DatabaseMobile App
|
v
API Gateway
|
v
Order Service
|
v
Payment Service
|
v
DatabaseYou might think:
"The request already passed through the API Gateway, so the Payment Service can trust it."
That's a dangerous assumption.
Each service should verify the information and permissions relevant to its responsibilities.
An internal service might receive incorrect, manipulated, stale, or unexpected data because of compromised components, integration mistakes, or application bugs.
This is related to a broader security principle called Zero Trust.
A practical interpretation for developers is:
Don't grant trust to a request simply because it comes from inside your infrastructure.
Verify identities, validate data at trust boundaries, enforce authorization, and limit permissions.
6. Five Security Habits Every Developer Should Build
You don't have to become a cybersecurity specialist to start writing more secure applications.
Develop these five habits:
1. Validate input on the backend.
Frontend validation improves the user experience, but it can be bypassed. Backend validation is essential.
2. Check authorization for every protected action.
Always verify that the authenticated user is allowed to access or modify the requested resource.
3. Never trust client-controlled business values.
Prices, permissions, account balances, discounts, and other sensitive calculations should be determined or verified by trusted server-side logic.
4. Use safe database and output-handling practices.
Parameterized queries protect SQL structure. Context-aware output encoding helps protect webpages from XSS.
5. Think about unexpected behavior.
Before finishing an API endpoint, ask yourself:
"What happens if someone sends the wrong value, an unexpected value, or a request they aren't authorized to make?"
That question alone can lead you to discover important security gaps.
7. A Different Way to Think About Software Development
When we're learning programming, we focus on making things work.
We learn how to create APIs, connect databases, build authentication systems, and deploy applications.
But as we grow into experienced engineers, we should start asking a different set of questions.
Not only:
"Does this feature work?"
But also:
"Can this feature be misused?"
Not only:
"Can the user log in?"
But also:
"What can the user access after logging in?"
Not only:
"Does my API return the correct response?"
But also:
"What happens if someone intentionally sends an unexpected request?"
This shift in thinking is what separates simply implementing functionality from engineering reliable and secure software.
Final Thoughts
One of the biggest mistakes developers can make is assuming that users will always interact with an application exactly as intended.
They won't.
Some users make mistakes. Some clients send malformed requests. Some integrations behave unexpectedly. And some attackers intentionally search for weaknesses.
As developers, we cannot control every request our applications receive.
But we can control how our applications respond.
That's why one of the most valuable rules to carry into every project is:
Never trust user input. Validate it, verify permissions, and enforce security where the data is used.
Security doesn't begin when a penetration tester checks your application.
It begins when a developer makes a design decision, writes an API endpoint, or handles a piece of untrusted data.
A good developer makes software work. A security-minded developer also thinks about how it could fail.
And that's a habit worth developing.
Further Reading
- OWASP Input Validation Cheat Sheet
- OWASP Authorization Cheat Sheet
- OWASP SQL Injection Prevention Cheat Sheet
- OWASP Cross-Site Scripting Prevention Cheat Sheet
Written by Chea Kheang
Software Engineer | System Architect | Tech Entrepreneur
Sharing practical lessons about software engineering, backend development, DevOps, and cybersecurity.