Skip to content

Secure Coding Practices

Section Overview

Foundational security principles and practical techniques for writing secure code that protects user data, maintains system integrity, and defends against common attack vectors.

← Back to Security Overview


Defense-in-Depth Principle

Core Principle: Security must be implemented in multiple layers throughout the application stack, with no single point of failure.

Understanding Defense-in-Depth

Defense-in-depth is a military strategy adapted for cybersecurity. Just as a castle has multiple defensive layers (moat, walls, gates, guards), your application should have multiple security controls. If an attacker breaches one layer, subsequent layers provide continued protection.

Why This Matters

A single security control is never sufficient. Multiple complementary layers ensure that if one fails, others continue to protect your system and data.


Key Guidelines

  • Apply security controls at all layers: Network, infrastructure, application, and data
  • Assume breach mentality: Design as if attackers will penetrate outer defenses
  • Implement complementary controls: Combine preventive and detective measures
  • Avoid single points of failure: No single control should be critical to security

Practical Implementation

When securing a feature, consider security at each architectural layer:

Network Layer
  • Use TLS/SSL for all communications (enforce HTTPS)
  • Implement rate limiting to prevent brute force attacks
  • Configure firewalls to restrict unnecessary access
  • Use Web Application Firewalls (WAF) for additional filtering
Application Layer
  • Validate and sanitize all inputs server-side
  • Use parameterized queries to prevent SQL injection
  • Implement proper session management
  • Apply principle of least privilege for user permissions
Data Layer
  • Encrypt sensitive data at rest using industry-standard algorithms
  • Implement column-level encryption for highly sensitive fields
  • Use database access controls and separate credentials per service
  • Enable database audit logging
Monitoring Layer
  • Log all security-relevant events (authentication, authorization failures, data access)
  • Set up real-time alerts for suspicious patterns
  • Implement anomaly detection for unusual behavior
  • Maintain audit trails for compliance and forensics

Real-World Application

Consider a user registration feature. Defense-in-depth means:

  1. Client-side validation provides immediate feedback (user experience layer)
  2. Server-side validation enforces actual security rules (never trust the client)
  3. Password hashing protects credentials even if the database is compromised
  4. Parameterized queries prevent SQL injection
  5. Rate limiting prevents automated registration abuse
  6. Email verification adds another authentication factor
  7. Audit logging tracks registration attempts for security monitoring

Implementation Examples

# Python: Demonstrates multiple security layers working together
def register_user(request_data):
    # Layer 1: Input validation - reject malformed data early
    validation_errors = validate_registration_data(request_data)
    if validation_errors:
        return {"status": "error", "errors": validation_errors}

    # Layer 2: Password strength enforcement
    if not password_meets_requirements(request_data["password"]):
        return {"status": "error", "message": "Password too weak"}

    # Layer 3: Secure password storage using adaptive hashing
    hashed_password = bcrypt.hashpw(
        request_data["password"].encode("utf-8"), 
        bcrypt.gensalt(rounds=12)
    )

    # Layer 4: SQL injection prevention via parameterized queries
    try:
        with db_connection() as conn:
            with conn.cursor() as cur:
                cur.execute(
                    "INSERT INTO users (username, email, password_hash) VALUES (%s, %s, %s)",
                    (request_data["username"], request_data["email"], hashed_password)
                )
    except IntegrityError:
        return {"status": "error", "message": "User already exists"}

    # Layer 5: Security event logging for detection and forensics
    security_logger.info(
        "New user registered", 
        extra={"username": request_data["username"], "email": request_data["email"]}
    )

    return {"status": "success", "message": "Registration successful"}
// JavaScript: Demonstrates multiple security layers working together
async function registerUser(userData) {
  try {
    // Layer 1: Input validation - reject malformed data early
    const validationErrors = validateRegistrationData(userData);
    if (validationErrors.length > 0) {
      return { status: 'error', errors: validationErrors };
    }

    // Layer 2: Password strength enforcement
    if (!passwordMeetsRequirements(userData.password)) {
      return { status: 'error', message: 'Password too weak' };
    }

    // Layer 3: Secure password storage using adaptive hashing
    const salt = await bcrypt.genSalt(12);
    const passwordHash = await bcrypt.hash(userData.password, salt);

    // Layer 4: SQL injection prevention via parameterized queries
    const result = await db.query(
      'INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)',
      [userData.username, userData.email, passwordHash]
    );

    // Layer 5: Security event logging for detection and forensics
    securityLogger.info('New user registered', {
      username: userData.username,
      email: userData.email,
      ipAddress: userData.ipAddress
    });

    return { status: 'success', message: 'Registration successful' };
  } catch (error) {
    if (error.code === 'ER_DUP_ENTRY') {
      return { status: 'error', message: 'User already exists' };
    }
    errorLogger.error('Registration error', { error: error.message });
    return { status: 'error', message: 'Registration failed' };
  }
}
// Java: Demonstrates multiple security layers working together
@Service
public class RegistrationService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private SecurityAuditLogger securityLogger;

    public RegistrationResult registerUser(RegistrationRequest request) {
        // Layer 1: Input validation - reject malformed data early
        ValidationResult validation = validateRegistrationData(request);
        if (!validation.isValid()) {
            return RegistrationResult.error(validation.getErrors());
        }

        // Layer 2: Password strength enforcement
        if (!passwordMeetsRequirements(request.getPassword())) {
            return RegistrationResult.error("Password too weak");
        }

        // Layer 3: Secure password storage using adaptive hashing
        String hashedPassword = passwordEncoder.encode(request.getPassword());

        // Layer 4: Create user with secure defaults
        User user = new User();
        user.setUsername(request.getUsername());
        user.setEmail(request.getEmail());
        user.setPasswordHash(hashedPassword);
        user.setEnabled(false);  // Require email verification

        // Layer 5: SQL injection prevention (JPA uses parameterized queries)
        try {
            userRepository.save(user);
        } catch (DataIntegrityViolationException e) {
            return RegistrationResult.error("User already exists");
        }

        // Layer 6: Security event logging for detection and forensics
        securityLogger.logSecurityEvent(
            SecurityEventType.USER_REGISTRATION,
            request.getUsername(),
            request.getClientIp()
        );

        return RegistrationResult.success("Registration successful");
    }
}

Common Pitfalls

Common Mistakes

  • Over-reliance on perimeter security: Assuming internal systems are safe
  • Security through obscurity: Hiding implementation details instead of proper controls
  • Incomplete coverage: Protecting some layers while neglecting others
  • Neglecting monitoring: Having controls but no way to detect when they fail

Input Validation and Sanitization

Core Principle: All input from external sources must be validated for correctness and sanitized to neutralize potential malicious content.

Why Input Validation Matters

Attackers commonly exploit applications by submitting unexpected, malicious, or malformed input. Input validation is your first line of defense against injection attacks, data corruption, and system crashes. The fundamental rule: never trust user input.

The Golden Rule

Never trust user input. Validate everything, from every source, at every trust boundary.


Understanding Trust Boundaries

A trust boundary exists wherever data moves between systems or components with different security contexts:

  • User browser to web server
  • Web application to database
  • External API to your service
  • File uploads to your storage
  • Configuration files to application runtime

At each boundary, validate that incoming data meets your expectations.


Validation Strategy

Whitelist vs Blacklist Approach
  • Preferred: Whitelist validation - Define what is allowed and reject everything else
  • Avoid: Blacklist validation - Define what is forbidden (attackers find bypasses)
Validation Layers
  1. Syntactic Validation: Format, length, type, character set
  2. Semantic Validation: Logical correctness (end date after start date)
  3. Business Rule Validation: Domain-specific constraints
  4. Contextual Validation: Based on user role, application state

Practical Validation Guidelines

For All Inputs:

  • Validate data type, format, length, and range
  • Reject invalid input immediately - don't try to "fix" it
  • Validate on the server side, even if client-side validation exists
  • Re-validate at each trust boundary

For Specific Input Types:

Input Type Validation Requirements Security Concern
Email addresses RFC-compliant format, length limits Injection via email headers
Usernames Alphanumeric + specific characters, length limits Account enumeration, injection
Passwords Minimum complexity, length requirements Brute force attacks
URLs Protocol whitelist (http/https), domain validation Open redirect, SSRF
File uploads Extension, MIME type, size, content scanning Malware, path traversal
Dates Format validation, reasonable range Logic errors, overflow
Numeric values Range checking, type enforcement Integer overflow, logic errors

Sanitization vs Validation

Validation: Checking if input meets requirements (accept or reject)

Sanitization: Modifying input to remove or neutralize harmful elements

When to Use Each

Use validation when possible. Sanitize only when you must accept varied input formats (e.g., user-generated content with limited HTML).


Implementation Examples

# Python: Comprehensive validation pattern
import re
import html

def validate_user_profile(data):
    """Validate user profile data against defined rules"""
    errors = []

    # Required field validation
    if not data.get("username"):
        errors.append("Username is required")
    elif not re.match(r'^[a-zA-Z0-9_]{3,30}$', data.get("username", "")):
        errors.append("Username: 3-30 characters, letters/numbers/underscore only")

    # Email validation with specific pattern
    if not data.get("email"):
        errors.append("Email is required")
    elif not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', 
                      data.get("email", "")):
        errors.append("Invalid email format")

    # Optional field with constraints
    if data.get("website"):
        if not re.match(r'^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$', 
                       data.get("website")):
            errors.append("Website must be valid URL (http/https)")

    # Length constraints on text fields
    if data.get("bio") and len(data.get("bio")) > 500:
        errors.append("Bio cannot exceed 500 characters")

    return errors

def sanitize_user_profile(data):
    """Sanitize data after validation passes"""
    sanitized = {}

    if "username" in data:
        sanitized["username"] = data["username"].strip()

    if "email" in data:
        sanitized["email"] = data["email"].strip().lower()

    if "bio" in data:
        # HTML escape to prevent XSS
        sanitized["bio"] = html.escape(data["bio"].strip())

    if "website" in data:
        website = data["website"].strip()
        if website and re.match(r'^https?://', website):
            sanitized["website"] = website

    return sanitized

def process_profile_update(raw_data):
    """Orchestrate validation and sanitization"""
    # First validate
    errors = validate_user_profile(raw_data)
    if errors:
        return {"status": "error", "errors": errors}

    # Then sanitize
    sanitized_data = sanitize_user_profile(raw_data)

    # Finally, save using parameterized queries (not shown)
    return {"status": "success", "profile": sanitized_data}
// JavaScript: Comprehensive validation pattern
class UserProfileValidator {
  static validate(data) {
    const errors = [];

    // Required field validation
    if (!data.username) {
      errors.push('Username is required');
    } else if (!/^[a-zA-Z0-9_]{3,30}$/.test(data.username)) {
      errors.push('Username: 3-30 characters, letters/numbers/underscore only');
    }

    // Email validation with specific pattern
    if (!data.email) {
      errors.push('Email is required');
    } else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(data.email)) {
      errors.push('Invalid email format');
    }

    // Optional field with constraints
    if (data.website && !/^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/.*)?$/.test(data.website)) {
      errors.push('Website must be valid URL (http/https)');
    }

    // Length constraints on text fields
    if (data.bio && data.bio.length > 500) {
      errors.push('Bio cannot exceed 500 characters');
    }

    return errors;
  }

  static sanitize(data) {
    const sanitized = {};

    if (data.username) {
      sanitized.username = data.username.trim();
    }

    if (data.email) {
      sanitized.email = data.email.trim().toLowerCase();
    }

    if (data.bio) {
      // Use DOMPurify to sanitize HTML content
      sanitized.bio = DOMPurify.sanitize(data.bio.trim(), {
        ALLOWED_TAGS: [],
        ALLOWED_ATTR: []
      });
    }

    if (data.website) {
      const website = data.website.trim();
      if (website && /^https?:\/\//.test(website)) {
        sanitized.website = website;
      }
    }

    return sanitized;
  }
}

async function processProfileUpdate(rawData) {
  // First validate
  const errors = UserProfileValidator.validate(rawData);
  if (errors.length > 0) {
    return { status: 'error', errors };
  }

  // Then sanitize
  const sanitizedData = UserProfileValidator.sanitize(rawData);

  try {
    // Finally, save using parameterized queries
    await db.query(
      'UPDATE user_profiles SET bio = ?, website = ? WHERE username = ?',
      [sanitizedData.bio, sanitizedData.website, sanitizedData.username]
    );

    return { status: 'success', profile: sanitizedData };
  } catch (error) {
    console.error('Profile update error:', error);
    return { status: 'error', message: 'Failed to update profile' };
  }
}
// Java: Comprehensive validation pattern
@Service
public class UserProfileService {

    @Autowired
    private UserProfileRepository repository;

    public class ValidationResult {
        private final List<String> errors = new ArrayList<>();

        public void addError(String error) {
            errors.add(error);
        }

        public boolean isValid() {
            return errors.isEmpty();
        }

        public List<String> getErrors() {
            return Collections.unmodifiableList(errors);
        }
    }

    public ValidationResult validateUserProfile(UserProfileDto dto) {
        ValidationResult result = new ValidationResult();

        // Required field validation
        if (StringUtils.isEmpty(dto.getUsername())) {
            result.addError("Username is required");
        } else if (!Pattern.matches("^[a-zA-Z0-9_]{3,30}$", dto.getUsername())) {
            result.addError("Username: 3-30 characters, letters/numbers/underscore only");
        }

        // Email validation with specific pattern
        if (StringUtils.isEmpty(dto.getEmail())) {
            result.addError("Email is required");
        } else if (!Pattern.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", 
                                    dto.getEmail())) {
            result.addError("Invalid email format");
        }

        // Optional field with constraints
        if (StringUtils.hasText(dto.getWebsite()) && 
            !Pattern.matches("^https?://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(/.*)?$", 
                           dto.getWebsite())) {
            result.addError("Website must be valid URL (http/https)");
        }

        // Length constraints on text fields
        if (StringUtils.hasText(dto.getBio()) && dto.getBio().length() > 500) {
            result.addError("Bio cannot exceed 500 characters");
        }

        return result;
    }

    public UserProfile sanitizeAndCreateUserProfile(UserProfileDto dto) {
        UserProfile profile = new UserProfile();

        if (dto.getUsername() != null) {
            profile.setUsername(dto.getUsername().trim());
        }

        if (dto.getEmail() != null) {
            profile.setEmail(dto.getEmail().trim().toLowerCase());
        }

        if (dto.getBio() != null) {
            // HTML escape bio to prevent script injection
            String sanitizedBio = StringEscapeUtils.escapeHtml4(dto.getBio().trim());
            profile.setBio(sanitizedBio);
        }

        if (dto.getWebsite() != null) {
            String website = dto.getWebsite().trim();
            if (website.matches("^https?://.*")) {
                profile.setWebsite(website);
            }
        }

        return profile;
    }

    public ApiResponse processProfileUpdate(UserProfileDto dto) {
        // First validate
        ValidationResult validationResult = validateUserProfile(dto);
        if (!validationResult.isValid()) {
            return ApiResponse.error("Validation failed", validationResult.getErrors());
        }

        // Then sanitize
        UserProfile profile = sanitizeAndCreateUserProfile(dto);

        try {
            // Finally, save using JPA (parameterized queries automatic)
            repository.save(profile);
            return ApiResponse.success("Profile updated successfully", profile);
        } catch (Exception e) {
            log.error("Failed to update profile", e);
            return ApiResponse.error("Failed to update profile");
        }
    }
}

Critical Rules

Never Break These Rules

  1. Always validate on the server: Client-side validation is for UX, not security
  2. Reject, don't sanitize: When input is invalid, reject it rather than trying to clean it
  3. Use parameterized queries: Never concatenate user input into SQL queries
  4. Escape output context-appropriately: Different contexts need different escaping
  5. Set maximum lengths: Prevent resource exhaustion from oversized inputs
  6. Log validation failures: Track patterns that might indicate attacks

Common Validation Mistakes

  • Validating only on the client side
  • Using blacklists instead of whitelists
  • Insufficiently restrictive regular expressions
  • Forgetting to validate after deserialization
  • Not re-validating at each trust boundary
  • Over-sanitizing instead of properly validating

Output Encoding and Escaping

Core Principle: All output rendered to users must be properly encoded or escaped to prevent injection attacks, particularly Cross-Site Scripting (XSS).

Understanding Output Encoding

Output encoding transforms data so it's treated as data, not code. Even if malicious content enters your system (through validation failures or compromised databases), proper output encoding prevents it from executing in users' browsers.

The Golden Rule

Encode all untrusted data before rendering it to users, regardless of where it came from.


Why Output Encoding Matters

Consider this scenario: A user's profile bio contains <script>alert('XSS')</script>. If you render this directly into HTML, the browser executes the script. Output encoding converts it to &lt;script&gt;alert('XSS')&lt;/script&gt;, which displays as text.


Context-Specific Encoding

Different output contexts require different encoding methods:

Context Encoding Required Purpose Example
HTML Body HTML entity encoding Prevent tag injection <div>{{userBio}}</div>
HTML Attribute HTML attribute encoding Prevent attribute breakout <div title="{{userData}}">
JavaScript JavaScript/JSON encoding Prevent script injection var data = {{jsonData}};
URL URL/percent encoding Prevent URL manipulation <a href="{{userWebsite}}">
CSS CSS encoding Prevent style injection style="color: {{userColor}}"

Implementation Guidelines

1. Use Framework Protection

Modern frameworks provide automatic escaping. Understand and leverage these features:

  • Python (Flask/Jinja2): Auto-escapes by default
  • JavaScript (React): JSX escapes by default
  • Java (Thymeleaf/Spring): Auto-escapes text expressions

2. Know When to Override

Sometimes you need to render actual HTML (rich text editors, markdown). In these cases:

  • Sanitize the HTML using trusted libraries (DOMPurify, Bleach, jsoup)
  • Whitelist only necessary tags and attributes
  • Remove all JavaScript event handlers
  • Strip dangerous protocols (javascript:, data:)

3. Apply Defense-in-Depth

Combine output encoding with other controls:

  • Content Security Policy (CSP) headers
  • HttpOnly and Secure flags on cookies
  • X-Content-Type-Options: nosniff header
  • X-Frame-Options to prevent clickjacking

Implementation Examples

# Python (Flask): Context-aware output encoding
from flask import Flask, render_template_string, escape
import html
import json
import urllib.parse

app = Flask(__name__)

@app.route('/profile/<username>')
def profile(username):
    # Simulated user data (could contain malicious content)
    user = {
        'username': username,
        'bio': '<script>alert("XSS")</script>Normal bio text',
        'website': 'https://example.com',
        'favorite_color': 'red'
    }

    # Context 1: HTML body - HTML encoding
    html_safe_bio = html.escape(user['bio'])

    # Context 2: JavaScript - JSON encoding
    js_safe_data = json.dumps(user['bio'])

    # Context 3: URL - URL encoding
    url_safe_website = urllib.parse.quote(user['website'], safe=':/')

    # Context 4: CSS - escape special characters
    css_safe_color = user['favorite_color'].replace('"', '\\"').replace("'", "\\'")

    template = '''
    <!DOCTYPE html>
    <html>
    <head>
        <title>Profile: {{ username }}</title>
        <style>
            .user-badge { color: "{{ css_color }}"; }
        </style>
    </head>
    <body>
        <h1>{{ username }}'s Profile</h1>
        <!-- Jinja2 auto-escapes this -->
        <div class="bio">{{ bio }}</div>

        <!-- Manually escaped for demonstration -->
        <div class="bio-manual">{{ html_bio | safe }}</div>

        <a href="{{ website }}">Visit Website</a>

        <script>
            // JavaScript context requires JSON encoding
            const userBio = {{ js_bio | safe }};
            console.log('Bio length:', userBio.length);
        </script>
    </body>
    </html>
    '''

    return render_template_string(
        template,
        username=username,
        bio=user['bio'],  # Jinja2 auto-escapes
        html_bio=html_safe_bio,
        js_bio=js_safe_data,
        website=url_safe_website,
        css_color=css_safe_color
    )

# Add security headers
@app.after_request
def add_security_headers(response):
    response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self'"
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    return response
// JavaScript (Express with EJS): Context-aware output encoding
const express = require('express');
const he = require('he');  // HTML entity encoder
const sanitizeHtml = require('sanitize-html');

const app = express();
app.set('view engine', 'ejs');

// Security headers middleware
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'");
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  next();
});

app.get('/profile/:username', (req, res) => {
  // Simulated user data (could contain malicious content)
  const user = {
    username: req.params.username,
    bio: '<script>alert("XSS")</script>Normal bio text',
    website: 'https://example.com',
    favoriteColor: 'red'
  };

  // Context 1: HTML body - HTML encoding
  const htmlSafeBio = he.encode(user.bio);

  // Context 2: JavaScript - JSON encoding
  const jsSafeData = JSON.stringify(user.bio);

  // Context 3: URL - URL encoding
  const urlSafeWebsite = encodeURIComponent(user.website);

  // Context 4: Sanitized HTML (allow limited tags)
  const sanitizedBio = sanitizeHtml(user.bio, {
    allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p'],
    allowedAttributes: {
      'a': ['href']
    },
    allowedSchemes: ['http', 'https']
  });

  res.render('profile', {
    username: user.username,
    htmlSafeBio,
    jsSafeData,
    urlSafeWebsite,
    sanitizedBio
  });
});

// EJS template structure:
/*
<!DOCTYPE html>
<html>
<head>
    <title>Profile: <%= username %></title>
</head>
<body>
    <h1><%= username %>'s Profile</h1>

    <!-- EJS auto-escapes with <%= %> -->
    <div class="bio"><%= htmlSafeBio %></div>

    <!-- Sanitized HTML with <%- %> (unescaped) -->
    <div class="rich-bio"><%- sanitizedBio %></div>

    <a href="<%= urlSafeWebsite %>">Visit Website</a>

    <script>
        const userBio = <%- jsSafeData %>;
        console.log('Bio length:', userBio.length);
    </script>
</body>
</html>
*/
// Java (Spring with Thymeleaf): Context-aware output encoding
@Controller
public class ProfileController {

    @Autowired
    private UserProfileService userProfileService;

    @GetMapping("/profile/{username}")
    public String profile(@PathVariable String username, Model model) throws Exception {

        // Simulated user data (could contain malicious content)
        UserProfile user = new UserProfile();
        user.setUsername(username);
        user.setBio("<script>alert('XSS')</script>Normal bio text");
        user.setWebsite("https://example.com");
        user.setFavoriteColor("red");

        // Thymeleaf handles HTML encoding automatically for th:text
        model.addAttribute("user", user);

        // For JavaScript context, prepare JSON
        ObjectMapper mapper = new ObjectMapper();
        String userBioJson = mapper.writeValueAsString(user.getBio());
        model.addAttribute("userBioJson", userBioJson);

        // For URL context
        String encodedWebsite = URLEncoder.encode(
            user.getWebsite(), 
            StandardCharsets.UTF_8.toString()
        );
        model.addAttribute("encodedWebsite", encodedWebsite);

        // For rich text, sanitize using jsoup
        String sanitizedBio = Jsoup.clean(user.getBio(), Safelist.basic());
        model.addAttribute("sanitizedBio", sanitizedBio);

        return "profile";
    }
}

// Thymeleaf template (profile.html):
/*
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:text="'Profile: ' + ${user.username}">Profile</title>
</head>
<body>
    <h1 th:text="${user.username} + '''s Profile'">Profile</h1>

    <!-- Thymeleaf th:text automatically HTML-escapes -->
    <div class="bio" th:text="${user.bio}">Bio</div>

    <!-- For pre-sanitized HTML, use th:utext (unescaped) -->
    <div class="rich-bio" th:utext="${sanitizedBio}">Bio</div>

    <a th:href="${encodedWebsite}" target="_blank">Visit Website</a>

    <script th:inline="javascript">
        /*<![CDATA[*/
        // Thymeleaf properly escapes JavaScript context
        const userBio = /*[[${userBioJson}]]*/ '';
        console.log('Bio length:', userBio.length);
        /*]]>*/
    </script>
</body>
</html>
*/

// Security headers configuration
@Configuration
public class WebSecurityConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new SecurityHeadersInterceptor());
            }
        };
    }

    public class SecurityHeadersInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, 
                                HttpServletResponse response, 
                                Object handler) {
            response.setHeader("Content-Security-Policy", 
                             "default-src 'self'; script-src 'self'");
            response.setHeader("X-Content-Type-Options", "nosniff");
            response.setHeader("X-Frame-Options", "DENY");
            return true;
        }
    }
}

Critical Rules for Output Encoding

Never Break These Rules

  1. Default to safe: Use auto-escaping templates; only disable when absolutely necessary
  2. Know your context: HTML, JavaScript, URL, and CSS all require different encoding
  3. Encode at render time: Encode as late as possible, right before output
  4. Don't double-encode: Encoding already-encoded data creates display issues
  5. Combine with CSP: Use Content Security Policy as an additional layer
  6. Never trust stored data: Even database data should be encoded on output

Common Mistakes

  • Disabling auto-escaping without understanding the risks
  • Using the same encoding method for all contexts
  • Encoding too early in the processing pipeline
  • Trusting data from databases without encoding
  • Not setting appropriate security headers
  • Forgetting to encode data in AJAX responses

Secure Storage and Handling of Sensitive Data

Core Principle: Sensitive data must be encrypted both at rest and in transit, with access strictly controlled on a need-to-know basis.

Understanding Sensitive Data

Sensitive data includes any information that could cause harm if disclosed, modified, or destroyed:

Personally Identifiable Information (PII)

  • Names, addresses, phone numbers
  • Social security numbers, national IDs
  • Biometric data, health records
  • Financial information, credit card numbers

Authentication Credentials

  • Passwords, password hashes
  • API keys, tokens, secrets
  • Encryption keys, certificates

Business Critical Data

  • Proprietary algorithms, trade secrets
  • Financial records, contracts
  • Customer lists, pricing information

Data Classification

Establish clear data classification levels:

Classification Examples Required Protection
Public Marketing materials, public docs Basic integrity controls
Internal Employee directories, policies Access controls, audit logs
Confidential Customer data, financial records Encryption, strict access controls
Restricted Passwords, SSNs, health records Strong encryption, MFA, audit logs

Encryption Requirements

Data at Rest
  • Use AES-256 or equivalent for file/database encryption
  • Implement column-level encryption for highly sensitive fields
  • Never store encryption keys with encrypted data
  • Use Hardware Security Modules (HSMs) for key storage when possible
Data in Transit
  • Use TLS 1.2 or higher for all communications
  • Enforce HTTPS across entire application
  • Use certificate pinning for mobile apps
  • Disable weak cipher suites
Password Storage
  • Never store passwords in plain text or using reversible encryption
  • Use adaptive hashing algorithms: bcrypt, scrypt, or Argon2
  • Set appropriate work factors (bcrypt rounds: 12+)
  • Implement password rotation and history

Key Management Best Practices

Key Management Essentials

  1. Never hardcode secrets: Use environment variables or secret management services
  2. Separate keys by environment: Different keys for dev, staging, production
  3. Implement key rotation: Regular scheduled rotation of encryption keys
  4. Use key derivation: Derive application keys from master keys
  5. Audit key access: Log all key usage and access attempts

Implementation Examples

# Python: Secure handling of sensitive data
import os
import bcrypt
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64

class SecretManager:
    """Secure secret management"""

    @staticmethod
    def get_secret(key, default=None):
        """Retrieve secret from environment or vault"""
        # Priority 1: Environment variables
        value = os.environ.get(key)
        if value:
            return value

        # Priority 2: Secret management service (AWS Secrets Manager, HashiCorp Vault)
        # This is a placeholder - implement actual vault integration
        try:
            from vault_client import get_secret
            return get_secret(key)
        except ImportError:
            return default

class PasswordManager:
    """Secure password hashing and verification"""

    @staticmethod
    def hash_password(password):
        """Hash password using bcrypt"""
        salt = bcrypt.gensalt(rounds=12)
        return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')

    @staticmethod
    def verify_password(stored_hash, provided_password):
        """Verify password against stored hash"""
        return bcrypt.checkpw(
            provided_password.encode('utf-8'),
            stored_hash.encode('utf-8')
        )

class DataEncryptor:
    """Field-level encryption for sensitive data"""

    def __init__(self):
        # Get encryption key from secure storage
        encryption_key = SecretManager.get_secret("DATA_ENCRYPTION_KEY")
        if not encryption_key:
            raise ValueError("Encryption key not configured")

        # Derive a proper Fernet key
        salt = SecretManager.get_secret("ENCRYPTION_SALT").encode()
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
        )
        key = base64.urlsafe_b64encode(kdf.derive(encryption_key.encode()))
        self.fernet = Fernet(key)

    def encrypt(self, plaintext):
        """Encrypt sensitive data"""
        if plaintext is None:
            return None
        return self.fernet.encrypt(plaintext.encode('utf-8')).decode('utf-8')

    def decrypt(self, ciphertext):
        """Decrypt sensitive data"""
        if ciphertext is None:
            return None
        return self.fernet.decrypt(ciphertext.encode('utf-8')).decode('utf-8')

    def encrypt_fields(self, data, sensitive_fields):
        """Encrypt specific fields in a dictionary"""
        result = data.copy()
        for field in sensitive_fields:
            if field in result and result[field]:
                result[field] = self.encrypt(str(result[field]))
        return result

    def decrypt_fields(self, data, sensitive_fields):
        """Decrypt specific fields in a dictionary"""
        result = data.copy()
        for field in sensitive_fields:
            if field in result and result[field]:
                result[field] = self.decrypt(result[field])
        return result

# Usage example
class UserDataService:
    """Service with secure data handling"""

    SENSITIVE_FIELDS = ['ssn', 'credit_card', 'address']

    def __init__(self):
        self.encryptor = DataEncryptor()

    def save_user_data(self, user_id, user_data):
        """Save user data with encrypted sensitive fields"""
        # Encrypt sensitive fields
        encrypted_data = self.encryptor.encrypt_fields(
            user_data, 
            self.SENSITIVE_FIELDS
        )

        # Save to database using parameterized queries
        db.execute(
            "INSERT INTO users (id, name, email, ssn, credit_card) VALUES (%s, %s, %s, %s, %s)",
            (user_id, encrypted_data['name'], encrypted_data['email'], 
             encrypted_data['ssn'], encrypted_data['credit_card'])
        )

        # Audit log (without sensitive data)
        audit_log.info(f"User data saved for user_id: {user_id}")

    def get_user_data(self, user_id, requesting_user_id):
        """Retrieve and decrypt user data with access control"""
        # Check access permission
        if not self._can_access(requesting_user_id, user_id):
            raise PermissionError("Unauthorized access")

        # Retrieve encrypted data
        encrypted_data = db.fetch_one(
            "SELECT * FROM users WHERE id = %s", (user_id,)
        )

        # Decrypt sensitive fields
        decrypted_data = self.encryptor.decrypt_fields(
            encrypted_data,
            self.SENSITIVE_FIELDS
        )

        # Audit log
        audit_log.info(
            f"User data accessed: user_id={user_id}, accessed_by={requesting_user_id}"
        )

        return decrypted_data

    def _can_access(self, requesting_user_id, target_user_id):
        """Check if user has permission to access data"""
        # Self-access allowed
        if requesting_user_id == target_user_id:
            return True

        # Check role-based permissions
        roles = db.fetch_user_roles(requesting_user_id)
        return 'admin' in roles or 'support' in roles
// JavaScript: Secure handling of sensitive data
const crypto = require('crypto');
const bcrypt = require('bcrypt');

class SecretManager {
  /**
   * Secure secret management
   */
  static getSecret(key, defaultValue = null) {
    // Priority 1: Environment variables
    const value = process.env[key];
    if (value) {
      return value;
    }

    // Priority 2: Secret management service
    // Implement vault integration here
    try {
      const { getSecret } = require('./vaultClient');
      return getSecret(key);
    } catch (err) {
      return defaultValue;
    }
  }
}

class PasswordManager {
  /**
   * Secure password hashing and verification
   */
  static async hashPassword(password) {
    const salt = await bcrypt.genSalt(12);
    return bcrypt.hash(password, salt);
  }

  static async verifyPassword(storedHash, providedPassword) {
    return bcrypt.compare(providedPassword, storedHash);
  }
}

class DataEncryptor {
  /**
   * Field-level encryption for sensitive data
   */
  constructor() {
    // Get encryption key from secure storage
    this.encryptionKey = SecretManager.getSecret('DATA_ENCRYPTION_KEY');
    if (!this.encryptionKey) {
      throw new Error('Encryption key not configured');
    }

    // Convert to Buffer
    this.key = Buffer.from(this.encryptionKey, 'hex');
    this.algorithm = 'aes-256-gcm';
    this.ivLength = 16;
  }

  encrypt(plaintext) {
    if (!plaintext) return null;

    const text = typeof plaintext === 'string' ? plaintext : JSON.stringify(plaintext);

    // Generate random IV
    const iv = crypto.randomBytes(this.ivLength);

    // Create cipher
    const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);

    // Encrypt
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');

    // Get auth tag
    const authTag = cipher.getAuthTag();

    // Combine IV, auth tag, and encrypted data
    return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
  }

  decrypt(ciphertext) {
    if (!ciphertext) return null;

    // Split components
    const [ivHex, authTagHex, encrypted] = ciphertext.split(':');

    const iv = Buffer.from(ivHex, 'hex');
    const authTag = Buffer.from(authTagHex, 'hex');

    // Create decipher
    const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
    decipher.setAuthTag(authTag);

    // Decrypt
    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
  }

  encryptFields(data, sensitiveFields) {
    const result = { ...data };
    for (const field of sensitiveFields) {
      if (result[field]) {
        result[field] = this.encrypt(result[field]);
      }
    }
    return result;
  }

  decryptFields(data, sensitiveFields) {
    const result = { ...data };
    for (const field of sensitiveFields) {
      if (result[field]) {
        result[field] = this.decrypt(result[field]);
      }
    }
    return result;
  }
}

// Usage example
class UserDataService {
  constructor(db, auditLog) {
    this.db = db;
    this.auditLog = auditLog;
    this.encryptor = new DataEncryptor();
    this.sensitiveFields = ['ssn', 'creditCard', 'address'];
  }

  async saveUserData(userId, userData) {
    // Encrypt sensitive fields
    const encryptedData = this.encryptor.encryptFields(
      userData,
      this.sensitiveFields
    );

    // Save to database using parameterized queries
    await this.db.query(
      'INSERT INTO users (id, name, email, ssn, credit_card) VALUES (?, ?, ?, ?, ?)',
      [userId, encryptedData.name, encryptedData.email, 
       encryptedData.ssn, encryptedData.creditCard]
    );

    // Audit log (without sensitive data)
    await this.auditLog.info(`User data saved for user_id: ${userId}`);
  }

  async getUserData(userId, requestingUserId) {
    // Check access permission
    if (!await this._canAccess(requestingUserId, userId)) {
      throw new Error('Unauthorized access');
    }

    // Retrieve encrypted data
    const encryptedData = await this.db.query(
      'SELECT * FROM users WHERE id = ?',
      [userId]
    );

    // Decrypt sensitive fields
    const decryptedData = this.encryptor.decryptFields(
      encryptedData[0],
      this.sensitiveFields
    );

    // Audit log
    await this.auditLog.info(
      `User data accessed: user_id=${userId}, accessed_by=${requestingUserId}`
    );

    return decryptedData;
  }

  async _canAccess(requestingUserId, targetUserId) {
    // Self-access allowed
    if (requestingUserId === targetUserId) {
      return true;
    }

    // Check role-based permissions
    const roles = await this.db.fetchUserRoles(requestingUserId);
    return roles.includes('admin') || roles.includes('support');
  }
}
// Java: Secure handling of sensitive data
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.*;

@Service
public class SensitiveDataHandler {

    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int GCM_IV_LENGTH = 12;
    private static final int GCM_TAG_LENGTH = 16;

    @Autowired
    private SecretManager secretManager;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    /**
     * Password hashing and verification
     */
    public String hashPassword(String password) {
        return passwordEncoder.encode(password);
    }

    public boolean verifyPassword(String storedHash, String providedPassword) {
        return passwordEncoder.matches(providedPassword, storedHash);
    }

    /**
     * Field-level encryption for sensitive data
     */
    public String encryptData(String plaintext) throws Exception {
        if (plaintext == null) {
            return null;
        }

        // Get encryption key from secure storage
        byte[] key = secretManager.getSecretAsBytes("DATA_ENCRYPTION_KEY");
        SecretKey secretKey = new SecretKeySpec(key, "AES");

        // Generate random IV
        byte[] iv = new byte[GCM_IV_LENGTH];
        new SecureRandom().nextBytes(iv);

        // Create GCM parameters
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);

        // Initialize cipher for encryption
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);

        // Encrypt
        byte[] encryptedData = cipher.doFinal(
            plaintext.getBytes(StandardCharsets.UTF_8)
        );

        // Combine IV and encrypted data
        ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encryptedData.length);
        byteBuffer.put(iv);
        byteBuffer.put(encryptedData);

        // Encode as Base64 for storage
        return Base64.getEncoder().encodeToString(byteBuffer.array());
    }

    public String decryptData(String encryptedData) throws Exception {
        if (encryptedData == null) {
            return null;
        }

        // Decode from Base64
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);

        // Extract IV and cipher text
        ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedBytes);
        byte[] iv = new byte[GCM_IV_LENGTH];
        byteBuffer.get(iv);

        byte[] cipherText = new byte[byteBuffer.remaining()];
        byteBuffer.get(cipherText);

        // Get encryption key
        byte[] key = secretManager.getSecretAsBytes("DATA_ENCRYPTION_KEY");
        SecretKey secretKey = new SecretKeySpec(key, "AES");

        // Create GCM parameters
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);

        // Initialize cipher for decryption
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);

        // Decrypt
        byte[] decryptedData = cipher.doFinal(cipherText);

        return new String(decryptedData, StandardCharsets.UTF_8);
    }

    public Map<String, Object> encryptFields(Map<String, Object> data, 
                                            Set<String> sensitiveFields) 
                                            throws Exception {
        Map<String, Object> result = new HashMap<>(data);

        for (String field : sensitiveFields) {
            if (result.containsKey(field) && result.get(field) != null) {
                String fieldValue = String.valueOf(result.get(field));
                result.put(field, encryptData(fieldValue));
            }
        }

        return result;
    }

    public Map<String, Object> decryptFields(Map<String, Object> data, 
                                            Set<String> sensitiveFields) 
                                            throws Exception {
        Map<String, Object> result = new HashMap<>(data);

        for (String field : sensitiveFields) {
            if (result.containsKey(field) && result.get(field) != null) {
                String fieldValue = String.valueOf(result.get(field));
                result.put(field, decryptData(fieldValue));
            }
        }

        return result;
    }
}

// Usage example
@Service
public class UserDataService {

    private static final Set<String> SENSITIVE_FIELDS = Set.of(
        "ssn", "creditCard", "address"
    );

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private SensitiveDataHandler sensitiveDataHandler;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private AuditLogger auditLogger;

    public UserData saveUserData(String userId, UserData userData, String operatorId) 
                                throws Exception {
        // Check permissions
        if (!permissionService.canModifyUser(operatorId, userId)) {
            throw new AccessDeniedException("Unauthorized access");
        }

        // Convert to map for encryption
        Map<String, Object> dataMap = userData.toMap();

        // Encrypt sensitive fields
        Map<String, Object> encryptedMap = sensitiveDataHandler.encryptFields(
            dataMap, 
            SENSITIVE_FIELDS
        );

        // Convert back to entity
        UserData encryptedData = UserData.fromMap(encryptedMap);

        // Save to database using parameterized queries (JPA handles this)
        UserData saved = userRepository.save(encryptedData);

        // Audit log (without sensitive data)
        auditLogger.log(AuditEvent.builder()
            .action("SAVE_USER_DATA")
            .resource("USER")
            .resourceId(userId)
            .operator(operatorId)
            .build());

        return saved;
    }

    public UserData getUserData(String userId, String requestingUserId) 
                               throws Exception {
        // Check permissions
        if (!permissionService.canViewUser(requestingUserId, userId)) {
            throw new AccessDeniedException("Unauthorized access");
        }

        // Retrieve encrypted data
        UserData encryptedData = userRepository.findById(userId)
            .orElseThrow(() -> new NotFoundException("User not found"));

        // Convert to map for decryption
        Map<String, Object> encryptedMap = encryptedData.toMap();

        // Decrypt sensitive fields
        Map<String, Object> decryptedMap = sensitiveDataHandler.decryptFields(
            encryptedMap,
            SENSITIVE_FIELDS
        );

        // Convert back to entity
        UserData decryptedData = UserData.fromMap(decryptedMap);

        // Audit log
        auditLogger.log(AuditEvent.builder()
            .action("VIEW_USER_DATA")
            .resource("USER")
            .resourceId(userId)
            .operator(requestingUserId)
            .build());

        return decryptedData;
    }
}

Access Control and Data Minimization

Principle of Least Privilege
  • Grant minimum access necessary for job function
  • Implement role-based access control (RBAC)
  • Use attribute-based access control (ABAC) for complex scenarios
  • Regularly review and revoke unnecessary permissions
Data Minimization
  • Collect only data that is absolutely necessary
  • Set retention policies and delete data when no longer needed
  • Anonymize or pseudonymize data when possible
  • Aggregate data instead of storing individual records
Audit and Compliance
  • Log all access to sensitive data
  • Implement real-time monitoring and alerting
  • Conduct regular access reviews
  • Maintain audit trails for compliance requirements

Common Mistakes

Avoid These Pitfalls

  • Storing passwords in plain text or using weak hashing
  • Hardcoding encryption keys in source code
  • Using the same encryption key across all environments
  • Not rotating encryption keys regularly
  • Logging sensitive data in application logs
  • Transmitting sensitive data over unencrypted connections
  • Failing to implement proper access controls

Protecting Against Common Security Vulnerabilities

Core Principle: Implement specific defenses against well-known attack vectors, focusing on the OWASP Top 10 and other common security threats.

Overview

This section addresses the most prevalent security vulnerabilities that continue to compromise applications. Each vulnerability includes concrete prevention strategies and implementation patterns across Python, JavaScript, and Java.

Key Guidelines

  • Prevent SQL injection with parameterized queries
  • Defend against XSS with proper output encoding
  • Mitigate CSRF attacks with anti-forgery tokens
  • Implement proper session management
  • Protect against server-side request forgery (SSRF)
  • Prevent insecure deserialization
  • Enforce proper access controls
  • Secure API endpoints appropriately

1. SQL Injection Prevention

Core Prevention Strategy: Use parameterized queries (prepared statements) for all database operations.

The Rule

Never concatenate user input directly into SQL queries. Parameterized queries ensure user input is treated as data, not executable code.


Implementation Examples
# Python: SQL Injection Prevention

# VULNERABLE - DO NOT USE
def get_user_unsafe(username):
    query = f"SELECT * FROM users WHERE username = '{username}'"
    cursor.execute(query)  # User input becomes part of SQL structure
    return cursor.fetchone()

# SECURE - Parameterized query
def get_user_safe(username):
    query = "SELECT * FROM users WHERE username = %s"
    cursor.execute(query, (username,))  # Input treated as data only
    return cursor.fetchone()

# SECURE - ORM (SQLAlchemy)
def get_user_orm(username):
    return User.query.filter_by(username=username).first()

# SECURE - Multiple parameters
def authenticate_user(username, email):
    query = """
        SELECT id, username, password_hash 
        FROM users 
        WHERE username = %s AND email = %s AND active = %s
    """
    cursor.execute(query, (username, email, True))
    return cursor.fetchone()
// JavaScript: SQL Injection Prevention

// VULNERABLE - DO NOT USE
async function getUserUnsafe(username) {
  const query = `SELECT * FROM users WHERE username = '${username}'`;
  return await db.query(query);
}

// SECURE - Parameterized query
async function getUserSafe(username) {
  const query = 'SELECT * FROM users WHERE username = ?';
  return await db.query(query, [username]);
}

// SECURE - ORM (Sequelize)
async function getUserOrm(username) {
  return await User.findOne({ where: { username } });
}

// SECURE - Multiple parameters
async function authenticateUser(username, email) {
  const query = `
    SELECT id, username, password_hash 
    FROM users 
    WHERE username = ? AND email = ? AND active = ?
  `;
  return await db.query(query, [username, email, true]);
}
// Java: SQL Injection Prevention

// VULNERABLE - DO NOT USE
public User getUserUnsafe(String username) throws SQLException {
    String query = "SELECT * FROM users WHERE username = '" + username + "'";
    Statement stmt = connection.createStatement();
    ResultSet rs = stmt.executeQuery(query);
    return mapToUser(rs);
}

// SECURE - Prepared statement
public User getUserSafe(String username) throws SQLException {
    String query = "SELECT * FROM users WHERE username = ?";
    try (PreparedStatement pstmt = connection.prepareStatement(query)) {
        pstmt.setString(1, username);
        ResultSet rs = pstmt.executeQuery();
        return mapToUser(rs);
    }
}

// SECURE - JPA
public User getUserJpa(String username) {
    return entityManager.createQuery(
        "SELECT u FROM User u WHERE u.username = :username", User.class)
        .setParameter("username", username)
        .getSingleResult();
}

// SECURE - Spring Data JPA
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);

    @Query("SELECT u FROM User u WHERE u.username = :username AND u.active = :active")
    User findActiveUser(@Param("username") String username, @Param("active") boolean active);
}

Additional Protection Measures

Defense-in-Depth for SQL

  • Least privilege database accounts - Applications should use accounts with minimal necessary permissions
  • Input validation - Validate format and type before queries (secondary defense)
  • ORM usage - Leverage frameworks that handle parameterization automatically
  • Error handling - Never expose database errors to users; log server-side only
  • Database activity monitoring - Alert on unusual query patterns or suspicious characters

2. Cross-Site Scripting (XSS) Prevention

Core Prevention Strategy: Implement context-aware output encoding and use Content Security Policy (CSP).

The Rule

Encode all untrusted data before rendering. Different contexts (HTML, JavaScript, URL, CSS) require different encoding methods.


Context-Specific Encoding
Context Example Required Encoding
HTML Body <div>{{data}}</div> HTML entity encoding
HTML Attribute <div title="{{data}}"> HTML attribute encoding
JavaScript var x = "{{data}}" JavaScript/JSON encoding
URL <a href="{{data}}"> URL encoding
CSS style="color: {{data}}" CSS encoding

Implementation Examples
# Python: XSS Prevention

# Flask/Jinja2 - automatic escaping enabled by default
from flask import Flask, render_template_string
from markupsafe import escape

app = Flask(__name__)

@app.route('/profile/<username>')
def show_profile(username):
    user = get_user(username)

    template = '''
    <h1>{{ user.username }}</h1>     <!-- Auto-escaped -->
    <div>{{ user.bio }}</div>         <!-- Auto-escaped -->

    <!-- DANGEROUS - avoid unless content pre-sanitized -->
    <!-- <div>{{ user.bio|safe }}</div> -->
    '''

    return render_template_string(template, user=user)

# Django templates - automatic escaping by default
"""
<!-- templates/profile.html -->
<h1>{{ user.username }}</h1>  <!-- Auto-escaped -->
<div>{{ user.bio }}</div>      <!-- Auto-escaped -->
"""

# When rich text is required, sanitize first
import bleach

ALLOWED_TAGS = ['p', 'br', 'strong', 'em', 'a']
ALLOWED_ATTRS = {'a': ['href', 'title']}

def sanitize_html(content):
    return bleach.clean(content, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS)
// JavaScript: XSS Prevention

// React - automatic escaping by default
function UserProfile({ user }) {
  return (
    <div>
      <h1>{user.username}</h1>  {/* Auto-escaped */}
      <p>{user.bio}</p>          {/* Auto-escaped */}

      {/* DANGEROUS - avoid unless content pre-sanitized */}
      {/* <div dangerouslySetInnerHTML={{ __html: user.bio }} /> */}
    </div>
  );
}

// Express with EJS
app.get('/profile/:username', (req, res) => {
  const user = getUserData(req.params.username);
  res.render('profile', { user });
});

/* profile.ejs:
<h1><%= user.username %></h1>  <!-- Escaped -->
<div><%= user.bio %></div>     <!-- Escaped -->
*/

// When rich text is required, use DOMPurify
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

function sanitizeHtml(content) {
  return DOMPurify.sanitize(content, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'],
    ALLOWED_ATTR: ['href', 'title']
  });
}
// Java: XSS Prevention with Spring/Thymeleaf

@Controller
public class ProfileController {

    @GetMapping("/profile/{username}")
    public String showProfile(@PathVariable String username, Model model) {
        User user = userService.getUser(username);
        model.addAttribute("user", user);
        return "profile";
    }
}

/* Thymeleaf template (profile.html):
<h1 th:text="${user.username}">Username</h1>  <!-- Auto-escaped -->
<div th:text="${user.bio}">Bio</div>          <!-- Auto-escaped -->

<!-- DANGEROUS - avoid unless content pre-sanitized -->
<!-- <div th:utext="${user.bio}">Bio</div> -->
*/

// When rich text is required, use jsoup
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;

public String sanitizeHtml(String content) {
    return Jsoup.clean(content, Safelist.basic());
}

// Or configure custom whitelist
public String sanitizeHtmlCustom(String content) {
    Safelist whitelist = new Safelist()
        .addTags("p", "br", "strong", "em", "a")
        .addAttributes("a", "href", "title");
    return Jsoup.clean(content, whitelist);
}

Content Security Policy (CSP)

Implement CSP headers as defense-in-depth:

# Python (Flask)
@app.after_request
def add_security_headers(response):
    response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self'"
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    return response
// JavaScript (Express with Helmet)
const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'"],
    imgSrc: ["'self'", "data:"],
    connectSrc: ["'self'"],
    fontSrc: ["'self'"],
    objectSrc: ["'none'"],
    mediaSrc: ["'self'"],
    frameSrc: ["'none'"],
  },
}));
// Java (Spring)
@Configuration
public class WebSecurityConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new HandlerInterceptor() {
                    @Override
                    public boolean preHandle(HttpServletRequest request, 
                                           HttpServletResponse response, 
                                           Object handler) {
                        response.setHeader("Content-Security-Policy", 
                            "default-src 'self'; script-src 'self'");
                        response.setHeader("X-Content-Type-Options", "nosniff");
                        response.setHeader("X-Frame-Options", "DENY");
                        return true;
                    }
                });
            }
        };
    }
}

Additional Protection Measures

Defense-in-Depth for XSS

  • Use framework auto-escaping - Leverage built-in protections in React, Vue, Angular, Django, Thymeleaf
  • Set cookie flags - HttpOnly and Secure flags prevent JavaScript cookie access
  • Validate input - Reject unexpected patterns (secondary defense)
  • Sanitize rich text - Use DOMPurify, Bleach, or jsoup for user-generated HTML
  • Subresource integrity - Use SRI for third-party scripts

3. Cross-Site Request Forgery (CSRF) Protection

Core Prevention Strategy: Implement anti-CSRF tokens and validate them on state-changing operations.

The Rule

All state-changing requests (POST, PUT, DELETE) must include a unique, unpredictable token that attackers cannot forge.


Implementation Examples
# Python (Django) - CSRF protection built-in

# settings.py includes CSRF middleware by default
MIDDLEWARE = [
    'django.middleware.csrf.CsrfViewMiddleware',
    # ...
]

# Template usage
"""
<form method="post" action="/update-profile/">
  {% csrf_token %}
  <input type="text" name="bio" />
  <button type="submit">Update</button>
</form>
"""

# View (protection automatic with middleware)
from django.views.decorators.csrf import csrf_protect

@csrf_protect
def update_profile(request):
    if request.method == 'POST':
        user = request.user
        user.bio = request.POST.get('bio')
        user.save()
        return redirect('profile')
    return render(request, 'profile_form.html')

# For AJAX requests
"""
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('/api/update', {
    method: 'POST',
    headers: {
        'X-CSRFToken': csrftoken,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
});
</script>
"""
// JavaScript (Express) - CSRF protection with csurf

const express = require('express');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');

const app = express();

app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));

// Setup CSRF protection
const csrfProtection = csrf({ cookie: true });

// GET request - provide token
app.get('/profile', csrfProtection, (req, res) => {
  res.render('profile', { csrfToken: req.csrfToken() });
});

// POST request - validate token
app.post('/update-profile', csrfProtection, (req, res) => {
  // Token validated automatically
  updateUserProfile(req.body);
  res.redirect('/profile');
});

// Error handling
app.use((err, req, res, next) => {
  if (err.code === 'EBADCSRFTOKEN') {
    return res.status(403).send('Invalid CSRF token');
  }
  next(err);
});

// HTML form
/*
<form action="/update-profile" method="POST">
  <input type="hidden" name="_csrf" value="{{csrfToken}}">
  <input type="text" name="bio" />
  <button type="submit">Update</button>
</form>
*/

// AJAX with CSRF token
/*
<script>
const token = document.querySelector('input[name="_csrf"]').value;
fetch('/api/update', {
    method: 'POST',
    headers: {
        'CSRF-Token': token,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
});
</script>
*/
// Java (Spring) - CSRF protection enabled by default

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and()
            .authorizeRequests()
                .anyRequest().authenticated();
    }
}

// Thymeleaf template - token included automatically
/*
<form th:action="@{/update-profile}" method="post">
  <!-- CSRF token auto-included by Thymeleaf -->
  <input type="text" name="bio" th:value="${user.bio}" />
  <button type="submit">Update</button>
</form>
*/

// Controller
@Controller
public class ProfileController {

    @PostMapping("/update-profile")
    public String updateProfile(@RequestParam String bio, Principal principal) {
        // CSRF token validated automatically by Spring Security
        userService.updateBio(principal.getName(), bio);
        return "redirect:/profile";
    }
}

// For REST APIs (stateless, no sessions)
@RestController
@RequestMapping("/api")
public class ApiController {

    // Disable CSRF for stateless API, use other mechanisms
    // Configure in WebSecurityConfig:
    /*
    http
        .csrf()
            .ignoringAntMatchers("/api/**")
    */

    // Instead, require custom headers (protected by CORS)
    @PostMapping("/update")
    public ResponseEntity<?> update(@RequestHeader("X-Requested-With") String header) {
        if (!"XMLHttpRequest".equals(header)) {
            return ResponseEntity.status(403).body("Invalid request");
        }
        // Process request
        return ResponseEntity.ok().build();
    }
}

Additional Protection Measures

Defense-in-Depth for CSRF

  • SameSite cookie attribute - Set to Strict or Lax to prevent cross-site cookie submission
  • Origin/Referer validation - Check headers match expected domain (secondary defense)
  • Custom request headers - For SPAs, require headers that browsers can't set cross-origin
  • Double submit cookie pattern - Alternative to server-side token storage for stateless apps

4. Server-Side Request Forgery (SSRF) Protection

Core Prevention Strategy: Validate, sanitize, and restrict URLs in server-side requests.

The Rule

Never make server-side HTTP requests to user-supplied URLs without strict validation.


Implementation Examples
# Python: SSRF Prevention

import requests
import ipaddress
import socket
from urllib.parse import urlparse

# Whitelist of allowed domains
ALLOWED_DOMAINS = ['api.example.com', 'api.trusted-partner.com']

def is_url_safe(url):
    """Validate URL before making server-side request"""
    try:
        parsed = urlparse(url)

        # Only allow specific schemes
        if parsed.scheme not in ['http', 'https']:
            return False

        hostname = parsed.netloc.split(':')[0]

        # Resolve to IP address
        ip = socket.gethostbyname(hostname)
        ip_addr = ipaddress.ip_address(ip)

        # Block private/internal IPs
        if (ip_addr.is_private or ip_addr.is_loopback or 
            ip_addr.is_link_local or ip_addr.is_multicast):
            return False

        # Check against whitelist
        if hostname not in ALLOWED_DOMAINS:
            return False

        return True
    except Exception:
        return False

def fetch_url_safely(url):
    """Make HTTP request only to validated URLs"""
    if not is_url_safe(url):
        raise ValueError("URL failed security validation")

    response = requests.get(
        url,
        timeout=5,
        allow_redirects=False  # Handle redirects manually
    )

    # Validate redirect location if present
    if 300 <= response.status_code < 400:
        location = response.headers.get('Location')
        if not is_url_safe(location):
            raise ValueError("Redirect URL failed security validation")

    return response
// JavaScript: SSRF Prevention

const fetch = require('node-fetch');
const { URL } = require('url');
const dns = require('dns').promises;
const ipaddr = require('ipaddr.js');

// Whitelist of allowed domains
const ALLOWED_DOMAINS = ['api.example.com', 'api.trusted-partner.com'];

async function isUrlSafe(urlString) {
  try {
    const parsedUrl = new URL(urlString);

    // Only allow specific schemes
    if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
      return false;
    }

    const hostname = parsedUrl.hostname;

    // Resolve to IP address
    const { address } = await dns.lookup(hostname);
    const addr = ipaddr.parse(address);

    // Block private/internal IPs
    if (addr.range() !== 'unicast') {
      return false;
    }

    // Check against whitelist
    if (!ALLOWED_DOMAINS.includes(hostname)) {
      return false;
    }

    return true;
  } catch (error) {
    return false;
  }
}

async function fetchUrlSafely(urlString) {
  if (!await isUrlSafe(urlString)) {
    throw new Error('URL failed security validation');
  }

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 5000);

  try {
    const response = await fetch(urlString, {
      signal: controller.signal,
      redirect: 'manual'  // Handle redirects manually
    });

    // Validate redirect location if present
    if (response.status >= 300 && response.status < 400) {
      const location = response.headers.get('Location');
      if (!await isUrlSafe(location)) {
        throw new Error('Redirect URL failed security validation');
      }
    }

    return response;
  } finally {
    clearTimeout(timeoutId);
  }
}
// Java: SSRF Prevention

@Service
public class SafeUrlFetcher {

    private static final List<String> ALLOWED_DOMAINS = 
        Arrays.asList("api.example.com", "api.trusted-partner.com");

    private static final int TIMEOUT_MS = 5000;

    public boolean isUrlSafe(String urlString) {
        try {
            URL url = new URL(urlString);

            // Only allow specific schemes
            if (!url.getProtocol().equals("http") && 
                !url.getProtocol().equals("https")) {
                return false;
            }

            String hostname = url.getHost();

            // Resolve to IP address
            InetAddress address = InetAddress.getByName(hostname);

            // Block private/internal IPs
            if (address.isLoopbackAddress() || 
                address.isSiteLocalAddress() || 
                address.isLinkLocalAddress() || 
                address.isMulticastAddress()) {
                return false;
            }

            // Check against whitelist
            if (!ALLOWED_DOMAINS.contains(hostname)) {
                return false;
            }

            return true;
        } catch (Exception e) {
            logger.error("URL validation error", e);
            return false;
        }
    }

    public String fetchUrlSafely(String urlString) throws IOException {
        if (!isUrlSafe(urlString)) {
            throw new SecurityException("URL failed security validation");
        }

        URL url = new URL(urlString);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setConnectTimeout(TIMEOUT_MS);
        connection.setReadTimeout(TIMEOUT_MS);
        connection.setInstanceFollowRedirects(false);  // Handle redirects manually

        try {
            int status = connection.getResponseCode();

            // Validate redirect location if present
            if (status >= 300 && status < 400) {
                String location = connection.getHeaderField("Location");
                if (!isUrlSafe(location)) {
                    throw new SecurityException("Redirect URL failed security validation");
                }
            }

            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(connection.getInputStream()))) {
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                return response.toString();
            }
        } finally {
            connection.disconnect();
        }
    }
}

Additional Protection Measures

Defense-in-Depth for SSRF

  • Network segmentation - Isolate services making external requests
  • Outbound firewall rules - Restrict server egress to necessary destinations
  • URL scheme blacklist - Block file://, gopher://, ftp://, etc.
  • Disable URL redirects - Or validate redirect destinations
  • Monitor outbound requests - Alert on unusual destinations or patterns

5. Insecure Deserialization Prevention

Core Prevention Strategy: Avoid deserializing untrusted data; prefer data-only formats like JSON.

The Rule

Never deserialize data from untrusted sources using formats that support object instantiation (pickle, Java serialization, etc.).


Implementation Examples
# Python: Safe Deserialization

# VULNERABLE - DO NOT USE
import pickle

def load_data_unsafe(serialized_data):
    # Never use pickle with untrusted data
    return pickle.loads(serialized_data)  # Can execute arbitrary code

# SECURE - Use JSON for data serialization
import json

def load_data_safe(serialized_data):
    # JSON doesn't contain executable code
    return json.loads(serialized_data)

# For complex objects, use explicit validation
from dataclasses import dataclass
from typing import Optional

@dataclass
class UserData:
    username: str
    email: str
    age: Optional[int] = None

def load_user_data(json_string):
    data = json.loads(json_string)

    # Validate and construct object explicitly
    if not isinstance(data.get('username'), str):
        raise ValueError("Invalid username type")
    if not isinstance(data.get('email'), str):
        raise ValueError("Invalid email type")

    return UserData(
        username=data['username'],
        email=data['email'],
        age=data.get('age')
    )
// JavaScript: Safe Deserialization

// VULNERABLE - DO NOT USE
function parseDataUnsafe(jsonString) {
  // Never use eval to parse data
  return eval('(' + jsonString + ')');  // Can execute arbitrary code
}

// SECURE - Use JSON.parse
function parseDataSafe(jsonString) {
  try {
    return JSON.parse(jsonString);  // Safe data-only parsing
  } catch (error) {
    throw new Error('Invalid JSON data');
  }
}

// With validation using reviver function
function parseWithValidation(jsonString) {
  return JSON.parse(jsonString, (key, value) => {
    // Validate specific fields
    if (key === 'userId' && typeof value !== 'number') {
      throw new Error('Invalid userId type');
    }

    if (key === 'email' && typeof value !== 'string') {
      throw new Error('Invalid email type');
    }

    return value;
  });
}

// For complex validation, use schema validation
const Ajv = require('ajv');
const ajv = new Ajv();

const userSchema = {
  type: 'object',
  properties: {
    username: { type: 'string', minLength: 3, maxLength: 30 },
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0, maximum: 150 }
  },
  required: ['username', 'email'],
  additionalProperties: false
};

function parseAndValidate(jsonString) {
  const data = JSON.parse(jsonString);
  const validate = ajv.compile(userSchema);

  if (!validate(data)) {
    throw new Error('Data validation failed: ' + JSON.stringify(validate.errors));
  }

  return data;
}
// Java: Safe Deserialization

// VULNERABLE - DO NOT USE
private Object deserializeUnsafe(byte[] serializedData) {
    try {
        ObjectInputStream ois = new ObjectInputStream(
            new ByteArrayInputStream(serializedData));
        return ois.readObject();  // Can instantiate arbitrary classes
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

// SECURE - Use JSON with Jackson
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;

private User deserializeSafe(String jsonData) {
    ObjectMapper mapper = new ObjectMapper();

    // Disable features that can lead to security issues
    mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);

    // Disable polymorphic type handling
    mapper.activateDefaultTyping(
        mapper.getPolymorphicTypeValidator(),
        ObjectMapper.DefaultTyping.NONE);

    try {
        // Explicitly specify expected type
        return mapper.readValue(jsonData, User.class);
    } catch (JsonProcessingException e) {
        throw new RuntimeException("Deserialization failed", e);
    }
}

// With Bean Validation
import javax.validation.Valid;
import javax.validation.Validator;

@Service
public class DataService {

    private final ObjectMapper objectMapper;
    private final Validator validator;

    public <T> T deserializeAndValidate(String jsonData, Class<T> type) {
        try {
            T object = objectMapper.readValue(jsonData, type);

            // Validate using Bean Validation annotations
            Set<ConstraintViolation<T>> violations = validator.validate(object);
            if (!violations.isEmpty()) {
                throw new ValidationException("Validation failed: " + violations);
            }

            return object;
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Deserialization failed", e);
        }
    }
}

// Entity with validation annotations
public class User {
    @NotBlank
    @Size(min = 3, max = 30)
    private String username;

    @Email
    @NotBlank
    private String email;

    @Min(0)
    @Max(150)
    private Integer age;

    // getters and setters
}

Additional Protection Measures

Defense-in-Depth for Deserialization

  • Prefer JSON/XML - Use data-only formats that don't support code execution
  • Implement integrity checks - Use HMAC to verify data hasn't been tampered with
  • Schema validation - Validate structure and types after deserialization
  • Whitelist classes - If using Java serialization, implement ObjectInputFilter
  • Monitor deserialization - Alert on unexpected errors or patterns

6. Broken Access Control Prevention

Core Prevention Strategy: Implement centralized, role-based access control with proper authorization checks on every request.

The Rule

Verify user permissions for every resource access and operation. Never rely on client-side controls or URL obfuscation.


Implementation Examples
# Python (Django): Access Control Implementation

from enum import Enum, auto
from functools import wraps
from django.core.exceptions import PermissionDenied

# Define permissions centrally
class Permission(Enum):
    VIEW_PROFILE = auto()
    EDIT_PROFILE = auto()
    MANAGE_USERS = auto()
    EXPORT_DATA = auto()

# Map roles to permissions
ROLE_PERMISSIONS = {
    'user': [Permission.VIEW_PROFILE, Permission.EDIT_PROFILE],
    'moderator': [Permission.VIEW_PROFILE, Permission.EDIT_PROFILE, Permission.EXPORT_DATA],
    'admin': [Permission.VIEW_PROFILE, Permission.EDIT_PROFILE, 
              Permission.MANAGE_USERS, Permission.EXPORT_DATA]
}

# Authorization decorator
def requires_permission(permission):
    def decorator(view_func):
        @wraps(view_func)
        def wrapped_view(request, *args, **kwargs):
            roles = request.user.get_roles()

            has_permission = any(
                permission in ROLE_PERMISSIONS.get(role, [])
                for role in roles
            )

            if not has_permission:
                raise PermissionDenied("Insufficient permissions")

            return view_func(request, *args, **kwargs)
        return wrapped_view
    return decorator

# Usage
@requires_permission(Permission.MANAGE_USERS)
def manage_users(request):
    users = User.objects.all()
    return render(request, 'admin/manage_users.html', {'users': users})

# Resource-level authorization
@requires_permission(Permission.EDIT_PROFILE)
def edit_post(request, post_id):
    post = get_object_or_404(Post, id=post_id)

    # Check ownership or admin rights
    is_owner = post.author_id == request.user.id
    is_admin = 'admin' in request.user.get_roles()

    if not (is_owner or is_admin):
        raise PermissionDenied("Not authorized to edit this post")

    # Process edit
    if request.method == 'POST':
        post.content = request.POST['content']
        post.save()
        return redirect('view_post', post_id=post.id)

    return render(request, 'edit_post.html', {'post': post})
// JavaScript (Express): Access Control Implementation

// Define permissions centrally
const Permission = {
  VIEW_PROFILE: 'view_profile',
  EDIT_PROFILE: 'edit_profile',
  MANAGE_USERS: 'manage_users',
  EXPORT_DATA: 'export_data'
};

// Map roles to permissions
const ROLE_PERMISSIONS = {
  'user': [Permission.VIEW_PROFILE, Permission.EDIT_PROFILE],
  'moderator': [Permission.VIEW_PROFILE, Permission.EDIT_PROFILE, Permission.EXPORT_DATA],
  'admin': [Permission.VIEW_PROFILE, Permission.EDIT_PROFILE, 
           Permission.MANAGE_USERS, Permission.EXPORT_DATA]
};

// Permission middleware
function requirePermission(permission) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    const roles = req.user.roles || ['user'];
    const hasPermission = roles.some(role => 
      ROLE_PERMISSIONS[role]?.includes(permission)
    );

    if (!hasPermission) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }

    next();
  };
}

// Usage
app.get('/admin/users', 
  requirePermission(Permission.MANAGE_USERS),
  async (req, res) => {
    const users = await User.find({});
    res.json(users);
  }
);

// Resource-level authorization
app.put('/api/posts/:postId',
  requirePermission(Permission.EDIT_POSTS),
  async (req, res) => {
    try {
      const post = await Post.findById(req.params.postId);

      if (!post) {
        return res.status(404).json({ error: 'Post not found' });
      }

      // Check ownership or admin rights
      const isOwner = post.authorId.toString() === req.user.id.toString();
      const isAdmin = req.user.roles.includes('admin');

      if (!isOwner && !isAdmin) {
        return res.status(403).json({ error: 'Not authorized to edit this post' });
      }

      // Process update
      Object.assign(post, req.body);
      await post.save();

      res.json(post);
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  }
);
// Java (Spring): Access Control Implementation

// Define permissions as enum
public enum Permission {
    VIEW_PROFILE,
    EDIT_PROFILE,
    MANAGE_USERS,
    EXPORT_DATA
}

// Map roles to permissions
@Configuration
public class SecurityPermissionsConfig {

    @Bean
    public Map<String, Set<Permission>> rolePermissions() {
        Map<String, Set<Permission>> permissions = new HashMap<>();

        permissions.put("ROLE_USER", EnumSet.of(
            Permission.VIEW_PROFILE, 
            Permission.EDIT_PROFILE
        ));

        permissions.put("ROLE_MODERATOR", EnumSet.of(
            Permission.VIEW_PROFILE, 
            Permission.EDIT_PROFILE,
            Permission.EXPORT_DATA
        ));

        permissions.put("ROLE_ADMIN", EnumSet.of(
            Permission.VIEW_PROFILE, 
            Permission.EDIT_PROFILE,
            Permission.MANAGE_USERS,
            Permission.EXPORT_DATA
        ));

        return permissions;
    }
}

// Permission evaluator
@Component
public class PermissionEvaluator {

    private final Map<String, Set<Permission>> rolePermissions;

    public PermissionEvaluator(Map<String, Set<Permission>> rolePermissions) {
        this.rolePermissions = rolePermissions;
    }

    public boolean hasPermission(Authentication auth, Permission permission) {
        return auth.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .anyMatch(role -> {
                Set<Permission> permissions = rolePermissions.get(role);
                return permissions != null && permissions.contains(permission);
            });
    }
}

// Controller with authorization
@RestController
@RequestMapping("/api/admin")
public class AdminController {

    private final UserService userService;
    private final PermissionEvaluator permissionEvaluator;

    @GetMapping("/users")
    @PreAuthorize("@permissionEvaluator.hasPermission(authentication, T(com.example.Permission).MANAGE_USERS)")
    public ResponseEntity<List<User>> getAllUsers() {
        return ResponseEntity.ok(userService.findAllUsers());
    }

    // Resource-level authorization
    @PutMapping("/posts/{postId}")
    @PreAuthorize("@permissionEvaluator.hasPermission(authentication, T(com.example.Permission).EDIT_POSTS)")
    public ResponseEntity<Post> updatePost(@PathVariable Long postId, 
                                          @RequestBody Post updatedPost) {
        Post existingPost = postService.findById(postId);

        if (existingPost == null) {
            return ResponseEntity.notFound().build();
        }

        // Check ownership or admin rights
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        boolean isAdmin = auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
        boolean isOwner = existingPost.getAuthorId().equals(getCurrentUserId());

        if (!isAdmin && !isOwner) {
            throw new AccessDeniedException("Not authorized to edit this post");
        }

        // Process update
        Post saved = postService.update(postId, updatedPost);
        return ResponseEntity.ok(saved);
    }

    private Long getCurrentUserId() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return ((UserPrincipal) auth.getPrincipal()).getId();
    }
}

Additional Protection Measures

Defense-in-Depth for Access Control

  • Deny by default - Require explicit permission grants rather than blacklisting
  • Centralize authorization logic - Avoid scattered permission checks throughout code
  • Enforce server-side - Never rely on client-side UI restrictions alone
  • URL-based access control - Apply framework-level protection to routes/endpoints
  • Record-level security - Implement row-level security for multi-tenant applications
  • Audit access attempts - Log both successful and failed authorization checks

7. Secure Session Management

Core Prevention Strategy: Implement secure session creation, storage, and invalidation with proper configuration.

The Rule

Generate cryptographically random session IDs, use secure storage, set appropriate flags, and regenerate on privilege changes.


Configuration Examples
# Python (Django): Secure Session Configuration

# settings.py
SESSION_COOKIE_SECURE = True        # Only send over HTTPS
SESSION_COOKIE_HTTPONLY = True      # Prevent JavaScript access
SESSION_COOKIE_SAMESITE = 'Strict'  # CSRF protection
SESSION_COOKIE_AGE = 3600           # 1 hour timeout
SESSION_EXPIRE_AT_BROWSER_CLOSE = True

# Use Redis for session storage (production)
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://redis:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'PASSWORD': os.environ.get('REDIS_PASSWORD'),
        },
    }
}

# Session timeout middleware
from datetime import datetime, timedelta
from django.contrib.auth import logout

class SessionTimeoutMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if request.user.is_authenticated:
            last_activity = request.session.get('last_activity')

            if last_activity:
                last_time = datetime.fromisoformat(last_activity)
                if datetime.now() - last_time > timedelta(minutes=30):
                    logout(request)
                    return redirect(settings.LOGIN_URL + '?expired=1')

            request.session['last_activity'] = datetime.now().isoformat()

        return self.get_response(request)

# Regenerate session on login
def login_view(request):
    if request.method == 'POST':
        user = authenticate(
            username=request.POST['username'],
            password=request.POST['password']
        )
        if user:
            # Regenerate session ID to prevent fixation
            request.session.create()
            auth_login(request, user)
            return redirect('dashboard')

    return render(request, 'login.html')
// JavaScript (Express): Secure Session Configuration

const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');

const app = express();

// Redis client for session storage
const redisClient = redis.createClient({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT,
  password: process.env.REDIS_PASSWORD
});

// Session configuration
app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  name: 'sessionId',  // Don't use default name
  cookie: {
    httpOnly: true,      // Prevent JavaScript access
    secure: true,        // Only transmit over HTTPS
    sameSite: 'strict',  // CSRF protection
    maxAge: 3600000,     // 1 hour
  },
  resave: false,
  saveUninitialized: false,
  rolling: true,  // Reset expiration on each request
}));

// Session timeout middleware
app.use((req, res, next) => {
  if (req.session && req.session.userId) {
    const now = Date.now();
    const lastActive = req.session.lastActive || now;

    // 30 minute inactivity timeout
    if (now - lastActive > 30 * 60 * 1000) {
      return req.session.destroy(err => {
        if (err) next(err);
        else res.redirect('/login?expired=true');
      });
    }

    req.session.lastActive = now;
  }
  next();
});

// Regenerate session on login
app.post('/login', (req, res, next) => {
  authenticate(req.body).then(user => {
    if (!user) {
      return res.status(401).send('Invalid credentials');
    }

    // Regenerate to prevent fixation
    req.session.regenerate(err => {
      if (err) return next(err);

      req.session.userId = user.id;
      req.session.userRole = user.role;
      req.session.lastActive = Date.now();

      res.redirect('/dashboard');
    });
  }).catch(next);
});

// Secure logout
app.get('/logout', (req, res) => {
  req.session.destroy(err => {
    if (err) console.error('Session destroy error:', err);
    res.clearCookie('sessionId');
    res.redirect('/login');
  });
});
// Java (Spring): Secure Session Configuration

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .sessionManagement()
                // Prevent session fixation
                .sessionFixation().changeSessionId()
                // Limit concurrent sessions
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .and()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .invalidSessionUrl("/login?expired")
                .and()
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll();
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

// application.properties
/*
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=strict
server.servlet.session.timeout=1h
server.servlet.session.cookie.name=SESSIONID

# Redis session storage (production)
spring.session.store-type=redis
spring.redis.host=redis
spring.redis.port=6379
spring.redis.password=${REDIS_PASSWORD}
*/

// Session timeout filter
@Component
public class SessionTimeoutFilter implements Filter {

    private static final long TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpSession session = httpRequest.getSession(false);

        if (session != null) {
            Long lastAccess = (Long) session.getAttribute("lastAccessTime");
            long now = System.currentTimeMillis();

            if (lastAccess != null && (now - lastAccess > TIMEOUT_MS)) {
                session.invalidate();
                ((HttpServletResponse) response).sendRedirect("/login?expired=true");
                return;
            }

            session.setAttribute("lastAccessTime", now);
        }

        chain.doFilter(request, response);
    }
}

Additional Protection Measures

Defense-in-Depth for Sessions

  • Regenerate on authentication - Create new session ID after login
  • Secure storage - Use server-side storage (Redis, database), not client-side
  • Timeout policies - Implement both absolute and idle timeouts
  • Revocation mechanism - Support immediate session termination
  • Session binding - Optionally tie sessions to IP/user-agent (with care)
  • Monitor anomalies - Alert on multiple sessions, unusual locations

8. API Security Best Practices

Core Prevention Strategy: Implement authentication, authorization, rate limiting, and input validation for all API endpoints.

The Rule

APIs require the same security rigor as web interfaces, plus additional controls for programmatic access.


Key API Security Measures
Measure Purpose
Authentication Verify identity using OAuth 2.0, JWT, or API keys
Authorization Enforce granular permissions on every endpoint
Rate limiting Prevent abuse with request throttling
Input validation Validate all parameters, headers, and body content
Output filtering Return only necessary data, avoid oversharing
HTTPS only Require TLS for all API communication
CORS configuration Restrict cross-origin access appropriately
API versioning Manage changes without breaking clients

Summary

This section covered eight critical vulnerability categories with concrete prevention strategies. Key takeaways:

Key Principles

  1. SQL Injection - Always use parameterized queries
  2. XSS - Encode output appropriately for context
  3. CSRF - Implement anti-CSRF tokens for state-changing operations
  4. SSRF - Validate and whitelist URLs before server-side requests
  5. Insecure Deserialization - Prefer JSON, avoid pickle/Java serialization
  6. Broken Access Control - Centralize authorization, check every request
  7. Session Management - Use secure configuration, regenerate on privilege changes
  8. API Security - Combine authentication, authorization, rate limiting, and validation

Each vulnerability requires defense-in-depth: combine multiple controls and never rely on a single security mechanism.



Next Steps


Last updated: December 2025