SE Notes
Writing code that is resilient to security attacks and vulnerabilities.
Secure coding is the practice of writing software that is resilient to exploitation—code that behaves correctly not only for expected inputs but also when confronted with deliberately malicious inputs designed to subvert its intended behavior. Most security vulnerabilities are not sophisticated design flaws but simple coding mistakes: failing to validate input, constructing queries from untrusted data, exposing sensitive information in error messages, or trusting client-side validation. Secure coding practices prevent these mistakes by establishing defensive habits that become second nature through training and discipline.
Input Validation
The most fundamental secure coding practice is treating all input as potentially hostile. Every piece of data that enters your application from external sources—user forms, API calls, file uploads, environment variables, database reads—must be validated before use.
Validation strategies:
Key principles: Validate on the server (never trust client-side validation alone), use allow-lists rather than deny-lists (specify what is acceptable rather than trying to enumerate everything that is not), validate data type, length, range, and format, and reject invalid input rather than trying to fix it.
Output Encoding
When displaying user-supplied data, encode it appropriately for the output context to prevent injection attacks:
# HTML context - encode HTML special characters
from markupsafe import escape
user_comment = escape(raw_comment) # <script> becomes <script>
# URL context - encode URL special characters
from urllib.parse import quote
redirect_url = f"/search?q={quote(user_query)}"
# JavaScript context - JSON-encode data embedded in scripts
import json
js_data = json.dumps(user_data) # Properly escapes quotes and special charsDifferent contexts require different encoding: HTML entities for HTML content, URL encoding for URLs, JavaScript escaping for inline scripts, and CSS escaping for style attributes. Using the wrong encoding for the context provides no protection.
Parameterized Queries
Never construct database queries by concatenating user input. Always use parameterized queries (prepared statements) that separate query structure from data:
// VULNERABLE - SQL injection possible
String query = "SELECT * FROM users WHERE email = '" + userEmail + "'";
// SECURE - parameterized query
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE email = ?"
);
stmt.setString(1, userEmail);
ResultSet rs = stmt.executeQuery();This principle extends beyond SQL: parameterize LDAP queries, XML queries, OS commands, and any other interpreter interaction.
Secure Authentication Implementation
import bcrypt
import secrets
# Password hashing - use a slow, salted algorithm
def hash_password(password):
return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# Secure session token generation
def generate_session_token():
return secrets.token_urlsafe(32) # 256 bits of cryptographic randomness
# Timing-safe comparison prevents timing attacks
import hmac
def verify_token(provided_token, stored_token):
return hmac.compare_digest(provided_token, stored_token)Error Handling and Information Leakage
Error messages should help legitimate users without helping attackers:
# INSECURE - reveals system details to attackers
except DatabaseError as e:
return f"Database error: {e}" # Exposes table names, query structure
# SECURE - generic message to users, detailed logging for developers
except DatabaseError as e:
logger.error(f"Database error in user lookup: {e}", exc_info=True)
return "An error occurred. Please try again or contact support."
# INSECURE login response - enables account enumeration
"No account found with that email" # Attacker learns which emails exist
# SECURE login response - same message regardless of reason
"Invalid email or password" # Attacker cannot distinguish the failure causePrinciple of Least Privilege in Code
# INSECURE - using root/admin database connection for all operations
db = connect(user='root', password='admin_pass')
# SECURE - separate connections with minimal permissions
read_db = connect(user='app_reader', password=read_pass) # SELECT only
write_db = connect(user='app_writer', password=write_pass) # INSERT, UPDATE only
admin_db = connect(user='app_admin', password=admin_pass) # Used only for migrationsApply this principle to: file system access (read-only where possible), API keys (scope to minimum needed operations), service accounts (separate accounts per service with limited permissions), and network access (services communicate only with required peers).
Secure Dependency Management
Third-party libraries are a major attack surface:
- Audit dependencies before adding them (check maintainer reputation, community size, security track record)
- Pin exact versions in production to prevent supply-chain attacks via compromised updates
- Regularly scan for known vulnerabilities using tools like Snyk, npm audit, or pip-audit
- Remove unused dependencies to reduce attack surface
- Verify package integrity through checksums or signatures
Secrets Management
Never hardcode secrets in source code:
Use dedicated secrets management tools (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) for production systems. Rotate secrets regularly. Never commit secrets to version control—use .gitignore and pre-commit hooks to prevent accidental exposure.
Security Code Review Checklist
When reviewing code, systematically check for: input validation on all external data, parameterized queries for all database interactions, output encoding appropriate to context, authentication and authorization on every endpoint, secure session management, appropriate error handling without information leakage, secure cryptographic choices, proper secrets management, and logging of security-relevant events. Making this checklist part of every code review catches vulnerabilities before they reach production.
Building Security Culture
Secure coding is not a one-time training event but an ongoing discipline. Regular security training, security champions within development teams, threat modeling for new features, security-focused code review checklists, and celebrating vulnerability discoveries (rather than punishing them) all contribute to a culture where security is everyone's responsibility rather than an afterthought delegated to a separate team.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Secure Coding Practices — Software Engineering.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Software Engineering topic.
Search Terms
software-engineering, software engineering, software, engineering, security, secure, coding, practices
Related Software Engineering Topics