Most security breaches happen because of simple mistakes. A missing access check. An unvalidated input. A hardcoded password. In 2024, the average cost of a data breach was $4.88 million (IBM). Studies consistently show that human error is a major factor in security incidents.
The good news? You do not need to be a security expert to write secure code. You just need to know what to watch for.
This is the first article in the Security for Developers series. We will start with the OWASP Top 10 — the most widely used list of web security risks in the world.
What is OWASP?
OWASP stands for Open Worldwide Application Security Project. It is a nonprofit organization that publishes free security resources for developers.
Their most popular resource is the OWASP Top 10 — a list of the ten most critical security risks for web applications. The list is updated every few years based on real-world data from thousands of applications.
The latest version is the OWASP Top 10:2025, released in early 2026. It replaces the previous 2021 version.
The OWASP Top 10 (2025)
Let us go through each category. For each one, I will explain what it means, give a real-world example, and show how to prevent it.
A01: Broken Access Control
What it is: Users can access data or actions they should not be allowed to.
Example: A user changes the URL from /api/users/123 to /api/users/456 and sees someone else’s data. This is called an IDOR (Insecure Direct Object Reference).
Prevention:
- Always check permissions on the server side
- Never trust the client
- Deny access by default — only allow what is explicitly permitted
# Bad — no authorization check
@app.route("/api/users/<user_id>")
def get_user(user_id):
return db.get_user(user_id)
# Good — check that the logged-in user can access this data
@app.route("/api/users/<user_id>")
@login_required
def get_user(user_id):
if current_user.id != int(user_id) and not current_user.is_admin:
return {"error": "Forbidden"}, 403
return db.get_user(user_id)
A02: Security Misconfiguration
What it is: Default settings, open cloud storage, unnecessary features enabled, or missing security headers.
Example: A database is deployed with default credentials (admin / admin). An attacker finds it and downloads all user data.
Prevention:
- Change all default passwords and settings
- Disable features you do not use
- Keep all software updated
- Add security headers (
X-Content-Type-Options,X-Frame-Options,Strict-Transport-Security)
// Go — adding security headers in middleware
func SecurityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
w.Header().Set("Content-Security-Policy", "default-src 'self'")
next.ServeHTTP(w, r)
})
}
A03: Software Supply Chain Failures
This is new in 2025. It was not in the 2021 list.
What it is: Vulnerabilities in third-party libraries, packages, or build tools that you depend on.
Example: The xz-utils backdoor in 2024 — a malicious contributor added a backdoor to a widely used compression library. The Log4Shell vulnerability in 2021 affected millions of Java applications.
Prevention:
- Audit your dependencies regularly
- Use tools like
npm audit,pip audit,govulncheck, orcargo audit - Pin dependency versions in production
- Use lock files (
package-lock.json,go.sum,Cargo.lock)
# Check for vulnerabilities in your dependencies
npm audit # JavaScript
pip audit # Python
govulncheck ./... # Go
cargo audit # Rust
A04: Cryptographic Failures
What it is: Sensitive data is not protected properly — weak encryption, missing encryption, or storing passwords in plaintext.
Example: LinkedIn breach (2012) — 117 million passwords were stored with SHA-1 (no salt). Attackers cracked most of them.
Prevention:
- Use bcrypt or Argon2 for password hashing (never MD5 or SHA-1)
- Use TLS (HTTPS) for data in transit
- Encrypt sensitive data at rest
- Never hardcode encryption keys in source code
// Node.js — hashing a password with bcrypt
const bcrypt = require('bcrypt');
async function hashPassword(password) {
// Hash a password
const hash = await bcrypt.hash(password, 12);
return hash;
}
async function verifyPassword(password, hash) {
// Verify a password
const isValid = await bcrypt.compare(password, hash);
return isValid;
}
A05: Injection
What it is: Untrusted data is sent to an interpreter as part of a command or query. SQL injection, XSS, and command injection all fall here.
Example: An attacker types ' OR 1=1 -- into a login form. If the app builds SQL by concatenating strings, the attacker gets access to all accounts.
Prevention:
- Use parameterized queries (prepared statements) for SQL
- Escape output for HTML (prevents XSS)
- Validate and sanitize all user input
// Kotlin — parameterized query with JDBC
// Bad — string concatenation
val query = "SELECT * FROM users WHERE id = $userId"
// Good — parameterized query
val stmt = connection.prepareStatement("SELECT * FROM users WHERE id = ?")
stmt.setInt(1, userId)
val result = stmt.executeQuery()
We will cover SQL injection and XSS in detail in Security Tutorial #4.
A06: Insecure Design
What it is: The application has fundamental design flaws that cannot be fixed with better code. Security was not considered during the design phase.
Example: A password reset feature that asks “What is your mother’s maiden name?” — this information is often public on social media.
Prevention:
- Think about security before writing code (threat modeling)
- Use established security patterns (do not invent your own authentication)
- Review designs with security in mind
A07: Authentication Failures
What it is: Broken authentication allows attackers to log in as other users or bypass login entirely.
Example: An application allows unlimited login attempts with no rate limiting. An attacker runs a brute force attack and guesses passwords.
Prevention:
- Use strong password hashing (bcrypt, Argon2)
- Implement rate limiting on login endpoints
- Add multi-factor authentication (MFA)
- Use short-lived tokens instead of long sessions
We cover authentication in depth in Security Tutorial #2.
A08: Data Integrity Failures
What it is: Code or data is modified without proper verification. This includes insecure deserialization and untrusted software updates.
Example: An application deserializes user-supplied data without validation. An attacker sends a crafted payload that executes arbitrary code.
Prevention:
- Validate all serialized data
- Use digital signatures to verify data integrity
- Do not deserialize untrusted data
A09: Security Logging and Alerting Failures
What it is: Security events are not logged, or logs are not monitored. When a breach happens, nobody notices.
Example: An attacker tries 10,000 passwords on a login page. There is no logging, so nobody knows until the data is already stolen.
Prevention:
- Log all authentication events (login, logout, failed attempts)
- Log access control failures
- Send alerts for suspicious activity
- Store logs in a centralized, tamper-proof location
# Python — logging authentication events
import logging
logger = logging.getLogger("security")
def login(username, password):
user = db.get_user(username)
if user and verify_password(password, user.password_hash):
logger.info(f"Login success: user={username} ip={request.remote_addr}")
return create_session(user)
else:
logger.warning(f"Login failed: user={username} ip={request.remote_addr}")
return None
A10: Mishandling of Exceptional Conditions
This is new in 2025, replacing “Server-Side Request Forgery (SSRF)” from the 2021 list.
What it is: The application does not handle error conditions properly. Uncaught exceptions can leak sensitive information or leave the system in an insecure state.
Example: An unhandled exception shows a full stack trace to the user, including database connection strings and internal paths.
Prevention:
- Never show stack traces or internal errors to users
- Use generic error messages for external responses
- Handle all error cases explicitly
- Set up global exception handlers
// Go — generic error response in production
func handleError(w http.ResponseWriter, err error) {
// Log the real error for debugging
log.Printf("Internal error: %v", err)
// Send a generic message to the user
http.Error(w, "Something went wrong. Please try again.",
http.StatusInternalServerError)
}
What Changed from 2021 to 2025
The 2025 list has two new categories:
| 2021 | 2025 | What Changed |
|---|---|---|
| A03: Injection | A05: Injection | Moved down — frameworks now auto-escape more |
| A06: Vulnerable Components | A03: Supply Chain Failures | Renamed and expanded — Log4Shell and xz-utils made this critical |
| A10: SSRF | A10: Exceptional Conditions | Replaced — error handling failures cause more breaches than SSRF |
The biggest change is A03: Software Supply Chain Failures. After the SolarWinds attack, the Log4Shell vulnerability, and the xz-utils backdoor, the security community recognized that your dependencies are a major attack surface.
Quick Self-Assessment
Ask these questions about your current project:
| Question | If No, Read |
|---|---|
| Do you check permissions on every API endpoint? | A01: Broken Access Control |
| Are all default passwords changed? | A02: Security Misconfiguration |
| Do you audit your dependencies? | A03: Supply Chain Failures |
| Are passwords hashed with bcrypt or Argon2? | A04: Cryptographic Failures |
| Do you use parameterized queries? | A05: Injection |
| Did you think about security during design? | A06: Insecure Design |
| Do you have rate limiting on login? | A07: Authentication Failures |
| Do you validate serialized data? | A08: Data Integrity Failures |
| Do you log security events? | A09: Logging Failures |
| Do you handle all errors properly? | A10: Exceptional Conditions |
If you answered “no” to any of these, this series will help you fix it.
Prevention Checklist
| Risk | Key Prevention | Priority |
|---|---|---|
| Broken Access Control | Server-side authorization checks | Critical |
| Security Misconfiguration | Change defaults, add security headers | Critical |
| Supply Chain Failures | Audit dependencies, use lock files | High |
| Cryptographic Failures | bcrypt/Argon2, TLS everywhere | Critical |
| Injection | Parameterized queries, output escaping | Critical |
| Insecure Design | Threat modeling before coding | High |
| Authentication Failures | Rate limiting, MFA, strong hashing | Critical |
| Data Integrity Failures | Validate serialized data, use signatures | High |
| Logging Failures | Log security events, set up alerts | Medium |
| Exceptional Conditions | Handle all errors, hide internals | Medium |
Related Articles
- Security Tutorial #2: Authentication — Passwords, Hashing, JWT
- Security Tutorial #3: Authorization — RBAC, OAuth 2.0, OpenID Connect
- Security Tutorial #4: SQL Injection and XSS — How to Prevent Them
- Security Tutorial #5: HTTPS and TLS — How Encryption Works
What’s Next?
In the next tutorial, we will dive into authentication — how to store passwords safely, hash them correctly, and use JWT tokens for secure API access.