Web Security Overview
Web security isn't a feature you bolt on at the end—it's a mindset that shapes how you write every line of code. This section covers the practical security knowledge every developer needs: understanding common vulnerabilities, configuring protective HTTP headers, and managing secrets without shooting yourself in the foot.
Why Security Matters for Developers
A single vulnerability can expose user data, destroy trust, and create legal liability. The 2023 Verizon Data Breach Report found that 74% of breaches involved the human element—misconfigurations, credential theft, or social engineering. As a developer, you're on the front lines.
Security isn't solely the responsibility of a dedicated security team. Modern development practices push security left into the development process itself. If you're writing code that handles user input, authentication, or sensitive data, you're doing security work whether you realize it or not.
Defense in Depth
Defense in depth means layering multiple security controls so that if one fails, others still protect the system. Think of it like a medieval castle: walls, moats, gates, guards, and a keep—each layer makes the attacker's job harder.
Application Layers
┌─────────────────────────────────────────────┐
│ Frontend (Browser) │
│ - Input validation │
│ - CSP headers │
│ - Secure cookie handling │
└─────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────┐
│ API Gateway / CDN │
│ - Rate limiting │
│ - WAF rules │
│ - DDoS protection │
└─────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────┐
│ Backend Server │
│ - Authentication / Authorization │
│ - Input sanitization │
│ - Parameterized queries │
│ - Logging and monitoring │
└─────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────┐
│ Database │
│ - Principle of least privilege │
│ - Encryption at rest │
│ - Network isolation │
└─────────────────────────────────────────────┘
Practical Example
Consider protecting against SQL injection:
- Frontend: Validate that an email field looks like an email before submission
- API Gateway: Rate limit requests to prevent automated attacks
- Backend: Use parameterized queries—never string concatenation
- Database: Run with a user that only has
SELECT,INSERT,UPDATEon specific tables—notDROPorTRUNCATE
If your frontend validation has a bug, the parameterized query still prevents injection. If someone bypasses your API entirely, the database permissions limit damage.
// Each layer adds protection
// Layer 1: Frontend validation (easily bypassed, but catches honest mistakes)
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// Layer 2: Backend - parameterized query (critical protection)
async function findUser(email: string) {
// NEVER do this:
// const query = `SELECT * FROM users WHERE email = '${email}'`;
// Always do this:
const result = await db.execute({
sql: 'SELECT * FROM users WHERE email = ?',
args: [email]
});
return result.rows[0];
}
// Layer 3: Database permissions (limits blast radius)
// GRANT SELECT, INSERT, UPDATE ON users TO app_user;
// -- No DELETE, DROP, or TRUNCATE permissions
Shift Left Security
"Shift left" means moving security checks earlier in the development lifecycle. Instead of finding vulnerabilities in production (expensive, embarrassing), you catch them during development (cheap, private).
The Cost Multiplier
Fixing a vulnerability at each stage costs roughly:
- Design phase: 1x (cheapest)
- Development: 5x
- Testing: 10x
- Production: 30x or more
A SQL injection caught in code review costs you 15 minutes. The same vulnerability discovered after a breach costs investigation time, incident response, customer notification, potential fines, and reputation damage.
Practical Shift-Left Practices
1. IDE Plugins and Linters
Install security linters that catch issues as you type:
# ESLint security plugin
pnpm add -D eslint-plugin-security
# For detecting secrets in code
pnpm add -D secretlint
// .eslintrc.js
module.exports = {
plugins: ['security'],
extends: ['plugin:security/recommended'],
rules: {
'security/detect-object-injection': 'warn',
'security/detect-non-literal-regexp': 'warn',
'security/detect-unsafe-regex': 'error',
}
};
2. Pre-commit Hooks
Block commits that contain secrets or obvious vulnerabilities:
# Install git-secrets
brew install git-secrets
# Configure for AWS patterns
git secrets --install
git secrets --register-aws
# .pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
3. CI/CD Security Scanning
Integrate security scanning into your pipeline:
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'CRITICAL,HIGH'
4. Dependency Auditing
Regularly check for vulnerable dependencies:
# npm/pnpm built-in audit
pnpm audit
# More aggressive fix
pnpm audit --fix
# For continuous monitoring
# Configure Dependabot or Snyk in your repo
The Human Factor
Technical controls only go so far. Most breaches involve human error or social engineering.
Developer Fatigue is a Security Risk
Tired developers make mistakes:
- Copy-pasting code from Stack Overflow without reviewing it
- Disabling security checks "temporarily" to debug
- Reusing credentials across environments
- Ignoring security warnings because there are too many
Mitigation:
- Automate security checks so developers don't have to remember
- Keep security warnings actionable—fix false positives so real issues stand out
- Make the secure path the easy path (secure defaults, simple APIs)
Social Engineering
Attackers target developers because they have privileged access:
- Phishing: Fake password reset emails, "urgent" security alerts
- Pretexting: Someone claiming to be from IT support needing your credentials
- Supply Chain Attacks: Malicious packages with typosquatted names (
lodashvs1odash)
# Before installing a package, verify it
npm info <package-name>
# Check download counts, last publish date, and maintainer
# A package with 12 downloads and no README is suspicious
Insider Threats
Not everyone with access has good intentions. Defense:
- Principle of least privilege: Only grant permissions needed for the job
- Audit logs: Track who accessed what and when
- Code review: Two sets of eyes on every change
- Offboarding procedures: Revoke access immediately when someone leaves
Security vs. Compliance
Compliance (SOC 2, HIPAA, PCI-DSS, GDPR) and security overlap but aren't the same thing.
| Compliance | Security |
|---|---|
| "We have a password policy" | "Our passwords are actually strong" |
| "We do annual security training" | "Our developers write secure code daily" |
| "We have an incident response plan" | "We can actually execute that plan under pressure" |
| "We encrypt data at rest" | "We actually manage our encryption keys properly" |
Compliance is the floor, not the ceiling. You can be compliant and still get breached if you only check boxes without understanding why those controls exist.
Practical Advice
- Use compliance requirements as a starting point, then ask "what are we actually trying to protect?"
- Document not just what you do, but why—this helps with both audits and actual security
- Automate compliance evidence collection so it doesn't become a checkbox exercise
Security Principles to Remember
1. Never Trust User Input
Everything from the client is suspect—form data, headers, cookies, URL parameters. Validate and sanitize on the server.
// User input includes things you might not expect
const suspiciousInputs = {
formFields: 'obvious',
urlParams: 'obvious',
cookies: 'often forgotten',
headers: 'definitely forgotten - User-Agent, Referer, etc.',
fileUploads: 'filename, MIME type, and content are all untrusted',
jsonBody: 'even field names can be malicious',
};
2. Fail Securely
When something goes wrong, fail in a way that doesn't expose information or leave the system in a vulnerable state.
// Bad: reveals internal structure
try {
await db.query(sql);
} catch (error) {
return res.status(500).json({ error: error.message });
// Might expose: "relation 'users' does not exist" or SQL syntax
}
// Good: generic error, detailed logging
try {
await db.query(sql);
} catch (error) {
logger.error('Database query failed', { error, sql: sql.substring(0, 100) });
return res.status(500).json({ error: 'An internal error occurred' });
}
3. Principle of Least Privilege
Grant the minimum permissions needed. Your web app shouldn't connect to the database as root.
-- Create a limited user for the application
CREATE USER app_readonly WITH PASSWORD 'secure_password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;
-- Create a separate user for migrations
CREATE USER app_migrations WITH PASSWORD 'different_password';
GRANT ALL ON ALL TABLES IN SCHEMA public TO app_migrations;
4. Defense Against Your Future Self
Write code assuming someone (including you in 6 months) will misuse it:
// Dangerous: easy to misuse
function query(sql: string) {
return db.execute(sql);
}
// Safer: forces parameterized queries
function query(sql: string, params: unknown[]) {
return db.execute({ sql, args: params });
}
// Even safer: use a query builder that makes injection impossible
const users = await db
.select()
.from(usersTable)
.where(eq(usersTable.email, email));
What's in This Section
This security section covers the practical knowledge you need:
- Common Vulnerabilities (OWASP Top 10): XSS, CSRF, and injection attacks—what they are, how they work, and how to prevent them
- HTTP Security Headers: CORS, CSP, HSTS, and other headers that protect your users
- Secrets Management: Keeping API keys, passwords, and tokens secure across environments
See Also
- Docker Deployment Guide - Securing containerized applications
- PostgreSQL - Database security configuration
- OWASP Top 10 - The canonical list of web vulnerabilities
- OWASP Cheat Sheet Series - Practical security guidance