← All cheat sheets

SECURE-CODING

Authorized use only. Offensive reference for systems you own or are explicitly permitted to test. You are responsible for staying within the law.

Practical secure coding reference covering OWASP ASVS key requirements,
CWE Top 25, and language-specific security tips.

OWASP ASVS KEY REQUIREMENTS#

V1 - Architecture, Design, and Threat Modeling:
  - Define security architecture early
  - Perform threat modeling (STRIDE, PASTA)
  - Document trust boundaries and data flows
  - Apply least privilege throughout

V2 - Authentication:
  - Use multi-factor authentication for sensitive operations
  - Minimum password length: 8 characters (prefer 12+)
  - No password complexity rules (NIST 800-63B)
  - Check passwords against breached password lists
  - Implement account lockout or rate limiting
  - Use bcrypt/Argon2id/scrypt for password storage
  - Session timeout: 30 min idle, 12 hr absolute

V3 - Session Management:
  - Generate session IDs with >= 128 bits of entropy
  - Regenerate session ID after authentication
  - Invalidate sessions on logout (server-side)
  - Set cookie flags: Secure, HttpOnly, SameSite=Strict
  - Implement idle and absolute session timeouts

V4 - Access Control:
  - Enforce access control on every request (server-side)
  - Deny by default
  - Use RBAC or ABAC consistently
  - Validate that users can only access their own data
  - Log all access control failures

V5 - Validation, Sanitization, and Encoding:
  - Validate all input on the server side
  - Use allowlists over denylists
  - Output encode for the correct context (HTML, JS, URL, CSS, SQL)
  - Validate file uploads (type, size, name)

V6 - Stored Cryptography:
  - Use well-known, tested algorithms only
  - AES-256-GCM for symmetric encryption
  - RSA-2048+ or ECDSA P-256+ for asymmetric
  - Use proper key management
  - Never hardcode cryptographic keys

V7 - Error Handling and Logging:
  - Never expose stack traces to users
  - Log security events (auth, access control, input validation failures)
  - Protect log integrity
  - Include who, what, when, where in log entries
  - Never log sensitive data (passwords, tokens, PII)

CWE TOP 25 WITH EXAMPLES#

CWE-787: Out-of-bounds Write
  Vulnerable: memcpy(buf, input, strlen(input));  // no bounds check
  Fixed:      memcpy(buf, input, MIN(strlen(input), sizeof(buf)-1));

CWE-79: Cross-site Scripting (XSS)
  Vulnerable: innerHTML = userInput;
  Fixed:      textContent = userInput;  // or use encoding library

CWE-89: SQL Injection
  Vulnerable: "SELECT * FROM users WHERE name='" + name + "'"
  Fixed:      PreparedStatement with parameterized query (see below)

CWE-416: Use After Free
  Vulnerable: free(ptr); use(ptr);
  Fixed:      free(ptr); ptr = NULL;

CWE-78: OS Command Injection
  Vulnerable: os.system("ping " + userInput)
  Fixed:      subprocess.run(["ping", userInput], shell=False)

CWE-20: Improper Input Validation
  Vulnerable: Processing any input without validation
  Fixed:      Validate type, length, range, pattern before processing

CWE-125: Out-of-bounds Read
  Vulnerable: return buffer[index];  // no bounds check
  Fixed:      if (index < bufferSize) return buffer[index];

CWE-22: Path Traversal
  Vulnerable: open("/uploads/" + filename)
  Fixed:      Validate filename, use realpath(), check against base dir

CWE-352: Cross-Site Request Forgery
  Vulnerable: Form without CSRF token
  Fixed:      Include and validate anti-CSRF token per session

CWE-434: Unrestricted Upload of Dangerous File Types
  Vulnerable: Accept any uploaded file and serve it
  Fixed:      Validate type (magic bytes), rename, store outside webroot

INPUT VALIDATION PATTERNS#

Server-side validation rules:
  1. Type checking    - Is it the expected data type?
  2. Range checking   - Is it within acceptable bounds?
  3. Length checking   - Is it within acceptable length?
  4. Pattern matching  - Does it match expected format?
  5. Allowlist        - Is it in the set of allowed values?

Common validation patterns:
  Email:    ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
  Phone:    ^\+?[1-9]\d{1,14}$  (E.164)
  UUID:     ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
  URL:      Use URL parsing library, then validate scheme and host
  Integer:  Parse as integer, then check range
  Date:     Use date parsing library, then check range

Anti-patterns (DO NOT):
  - Validate only on client side
  - Use denylists (blocklists) for dangerous characters
  - Use regex for HTML/XML parsing
  - Trust Content-Type header for file type validation
  - Trust file extension alone

OUTPUT ENCODING#

HTML Context:
  & -> &amp;    < -> &lt;    > -> &gt;    " -> &quot;    ' -> &#x27;

JavaScript Context:
  Encode all non-alphanumeric characters as \xHH or \uHHHH
  Better: use JSON.stringify() for data insertion

URL Context:
  Encode using percent-encoding (encodeURIComponent in JS)

CSS Context:
  Encode non-alphanumeric as \HHHHHH (six-digit hex)

Libraries:
  Java:       OWASP Java Encoder (org.owasp.encoder)
  Python:     markupsafe.escape(), bleach
  JavaScript: DOMPurify (client), he (server)
  Go:         html/template (auto-escapes)
  PHP:        htmlspecialchars(string, ENT_QUOTES, 'UTF-8')

PARAMETERIZED QUERIES#

Java (JDBC):
  String sql = "SELECT * FROM users WHERE email = ? AND status = ?";
  PreparedStatement stmt = conn.prepareStatement(sql);
  stmt.setString(1, email);
  stmt.setString(2, status);
  ResultSet rs = stmt.executeQuery();

Python (psycopg2):
  cursor.execute("SELECT * FROM users WHERE email = %s AND status = %s",
                 (email, status))

Python (SQLAlchemy):
  result = session.query(User).filter(User.email == email).all()

JavaScript (Node.js pg):
  const result = await pool.query(
    'SELECT * FROM users WHERE email = $1 AND status = $2',
    [email, status]
  );

Go (database/sql):
  rows, err := db.Query("SELECT * FROM users WHERE email = ? AND status = ?",
                         email, status)

C# (ADO.NET):
  var cmd = new SqlCommand("SELECT * FROM users WHERE email = @email", conn);
  cmd.Parameters.AddWithValue("@email", email);

PHP (PDO):
  $stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
  $stmt->execute(['email' => $email]);

ORM best practices:
  - Use ORM query builders (they parameterize automatically)
  - Never concatenate user input into raw SQL
  - Be cautious with raw query methods even in ORMs

SECURE SESSION MANAGEMENT#

Session ID generation:
  - Use cryptographically secure random generator
  - Minimum 128 bits of entropy
  - Use framework's built-in session management

Cookie configuration:
  Set-Cookie: sessionId=<value>; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=1800

Session lifecycle:
  1. Generate new session ID on login (prevent fixation)
  2. Validate session on every request
  3. Implement idle timeout (15-30 minutes)
  4. Implement absolute timeout (8-12 hours)
  5. Invalidate session server-side on logout
  6. Regenerate session on privilege change

Token-based (JWT) considerations:
  - Use short expiration times (15-60 minutes)
  - Implement refresh token rotation
  - Store refresh tokens securely (HttpOnly cookie)
  - Validate all claims (iss, aud, exp, nbf)
  - Use strong signing algorithms (RS256, ES256)
  - Never use "none" algorithm
  - Include token in Authorization header, not URL

ERROR HANDLING#

Principles:
  - Catch specific exceptions, not generic ones
  - Log detailed errors server-side
  - Return generic messages to users
  - Never expose stack traces, SQL errors, or internal paths
  - Fail securely (deny access on error)

Bad:
  catch (Exception e) {
      response.send("Error: " + e.getMessage() + "\n" + e.getStackTrace());
  }

Good:
  catch (SQLException e) {
      logger.error("Database error in getUserProfile: {}", e.getMessage(), e);
      response.status(500).send("An internal error occurred. Reference: " + errorId);
  }

Error response format (API):
  {
    "error": {
      "code": "INTERNAL_ERROR",
      "message": "An unexpected error occurred",
      "reference": "ERR-20260319-ABC123"
    }
  }

FILE UPLOAD SECURITY#

Validation checklist:
  [ ] Validate file type by magic bytes (not just extension)
  [ ] Enforce maximum file size
  [ ] Generate new random filename (never use user-provided name)
  [ ] Store outside web root
  [ ] Set Content-Disposition: attachment on download
  [ ] Scan with antivirus
  [ ] Strip metadata (EXIF, etc.)
  [ ] Validate image dimensions (prevent pixel flood DoS)

Magic bytes check:
  JPEG: FF D8 FF
  PNG:  89 50 4E 47
  PDF:  25 50 44 46
  GIF:  47 49 46 38
  ZIP:  50 4B 03 04

Storage:
  - Store files outside document root
  - Use a separate domain for user-uploaded content
  - Serve through a handler that sets proper Content-Type
  - Never execute uploaded files
  - Consider using object storage (S3, GCS) with signed URLs

LANGUAGE-SPECIFIC TIPS#

PYTHON:
  # Use secrets module for crypto random
  import secrets
  token = secrets.token_urlsafe(32)

  # Avoid eval/exec
  # NEVER: eval(user_input)
  # Use ast.literal_eval for parsing literals safely

  # Use subprocess safely
  import subprocess
  subprocess.run(["ls", "-la", path], shell=False, check=True)
  # NEVER: os.system("ls " + path)

  # YAML safe loading
  import yaml
  data = yaml.safe_load(content)   # NOT yaml.load(content)

  # Template injection prevention
  # Use autoescape in Jinja2
  env = Environment(autoescape=True)

  # Pickle deserialization danger
  # NEVER unpickle untrusted data
  # Use JSON or MessagePack instead

  # Django security settings
  DEBUG = False
  SECURE_SSL_REDIRECT = True
  CSRF_COOKIE_SECURE = True
  SESSION_COOKIE_SECURE = True
  SESSION_COOKIE_HTTPONLY = True
  X_FRAME_OPTIONS = 'DENY'

JAVA:
  // Use try-with-resources
  try (Connection conn = dataSource.getConnection();
       PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setString(1, userInput);
      try (ResultSet rs = ps.executeQuery()) { ... }
  }

  // XML parsing - prevent XXE
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
  dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);

  // Deserialization safety
  // Avoid Java native deserialization of untrusted data
  // Use ObjectInputFilter (Java 9+)
  // Prefer JSON with Jackson (configure safely)

  // Use Security Manager or modern alternatives
  // Validate all redirect URLs against allowlist

  // Spring Security: enable CSRF, CORS properly
  // Use @Valid for input validation with Bean Validation

JAVASCRIPT (NODE.JS):
  // Avoid eval and Function constructor
  // NEVER: eval(userInput)
  // NEVER: new Function(userInput)

  // Use parameterized queries (see above)

  // Helmet.js for Express security headers
  const helmet = require('helmet');
  app.use(helmet());

  // Rate limiting
  const rateLimit = require('express-rate-limit');
  app.use(rateLimit({ windowMs: 15*60*1000, max: 100 }));

  // DOMPurify for HTML sanitization (client)
  const clean = DOMPurify.sanitize(dirty);

  // Avoid prototype pollution
  // Use Object.create(null) for dictionary objects
  // Validate JSON keys before assignment
  // Use Map instead of plain objects for user-keyed data

  // npm audit for dependency vulnerabilities
  npm audit
  npm audit fix

  // CSP headers
  app.use(helmet.contentSecurityPolicy({
      directives: {
          defaultSrc: ["'self'"],
          scriptSrc: ["'self'"],
          styleSrc: ["'self'", "'unsafe-inline'"],
          imgSrc: ["'self'", "data:"],
      }
  }));

GO:
  // html/template auto-escapes (use instead of text/template)
  tmpl := template.Must(template.ParseFiles("page.html"))
  tmpl.Execute(w, data)

  // SQL parameterization
  rows, err := db.Query("SELECT * FROM users WHERE id = $1", userID)

  // Avoid fmt.Sprintf for SQL
  // NEVER: db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = '%s'", id))

  // Use crypto/rand (not math/rand) for security
  import "crypto/rand"
  token := make([]byte, 32)
  _, err := rand.Read(token)

  // Input validation with custom validators
  // Use validator package: github.com/go-playground/validator

  // HTTP security
  // Set timeouts on http.Server
  srv := &http.Server{
      ReadTimeout:  10 * time.Second,
      WriteTimeout: 10 * time.Second,
      IdleTimeout:  120 * time.Second,
  }

  // Use filepath.Clean and filepath.Abs for path validation
  cleanPath := filepath.Clean(userPath)
  if !strings.HasPrefix(cleanPath, baseDir) {
      // path traversal attempt
  }

SECURITY HEADERS#

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0    (deprecated, rely on CSP instead)
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cache-Control: no-store (for sensitive pages)

REFERENCES#

- OWASP ASVS: https://owasp.org/www-project-application-security-verification-standard/
- CWE Top 25: https://cwe.mitre.org/top25/
- OWASP Cheat Sheet Series: https://cheatsheetseries.owasp.org/
- NIST SP 800-63B (Digital Identity): https://pages.nist.gov/800-63-3/
- SANS Secure Coding Guidelines: https://www.sans.org/