Skip to main content

Secure Coding Standards

Secure coding standards define the security controls that developers implement directly in application code. This reference provides lookup tables for validation rules, encoding requirements, cryptographic parameters, and vulnerability mitigations organised by security domain.

Input Validation

Input validation rejects data that fails to meet defined constraints before the application processes it. All input originates from untrusted sources regardless of apparent origin, including form fields, URL parameters, HTTP headers, cookies, file uploads, API payloads, and data retrieved from databases or external services.

Validation Approach

Validation uses an allowlist model that defines what constitutes valid input and rejects everything else. Blocklist approaches that attempt to filter known-bad patterns fail against novel attack variations and encoding tricks.

# Allowlist validation - correct approach
import re
def validate_username(username: str) -> bool:
"""Username must be 3-20 alphanumeric characters or underscores."""
pattern = r'^[a-zA-Z0-9_]{3,20}$'
return bool(re.match(pattern, username))
# Blocklist validation - incorrect approach
def validate_username_blocklist(username: str) -> bool:
"""Attempts to block dangerous characters - will miss edge cases."""
dangerous = ['<', '>', '"', "'", ';', '--']
return not any(char in username for char in dangerous)

Validation occurs at the boundary where data enters the application, not deeper in the processing chain. Data that passes validation at entry remains validated for that context but requires revalidation if used in a different context, such as data validated for display being subsequently used in a SQL query.

Validation Rules by Data Type

Data typeValidation ruleMaximum lengthCharacter set
UsernameAlphanumeric plus underscore20[a-zA-Z0-9_]
Email addressRFC 5322 format, verified domain254Per RFC 5322
PasswordMinimum 12 characters, no maximum128 (for hashing)Any Unicode
Phone numberE.164 format after normalisation15[0-9+]
Postal codeCountry-specific pattern10[a-zA-Z0-9 -]
URLValid scheme (https preferred), valid host2048Per RFC 3986
UUIDRFC 4122 format36[a-fA-F0-9-]
Integer IDNumeric, within expected range19 digits[0-9]
DateISO 8601 format, reasonable range10[0-9-]
Currency amountDecimal with 2 places, positive or zero15 digits[0-9.]
Free text (single line)No control characters500Printable Unicode
Free text (multi-line)No dangerous control characters10000Printable Unicode plus newline
File nameNo path separators, no null bytes255[a-zA-Z0-9._-]
JSON payloadValid JSON, schema validationContext-dependentValid JSON
HTML contentNever accept; require structured inputN/AN/A

Numeric Validation

Numeric inputs require range validation in addition to format validation. An integer that parses correctly but exceeds expected bounds causes application errors or enables integer overflow attacks.

def validate_quantity(value: str) -> int:
"""Validate and parse a quantity field."""
try:
quantity = int(value)
except ValueError:
raise ValidationError("Quantity must be a whole number")
if quantity < 1:
raise ValidationError("Quantity must be at least 1")
if quantity > 10000:
raise ValidationError("Quantity cannot exceed 10000")
return quantity
Numeric fieldMinimumMaximumPrecision
Quantity110,000Integer
Currency0.00999,999,999.992 decimal places
Percentage01002 decimal places
Year19002100Integer
Age0150Integer
Port number165535Integer
Page number110,000Integer
Items per page1100Integer

File Upload Validation

File uploads require validation of the file extension, MIME type, file content (magic bytes), and file size. Relying solely on the extension or client-provided MIME type permits attackers to upload executable files disguised as images.

CheckMethodRationale
ExtensionExtract from filename, compare to allowlistFirst-pass filter; easily bypassed alone
MIME typeRead Content-Type header, compare to allowlistClient-controlled; cannot be trusted alone
Magic bytesRead first bytes, compare to known signaturesVerifies actual file format
File sizeCheck Content-Length and actual bytes readPrevents resource exhaustion
Image dimensionsParse image header, verify reasonable boundsPrevents decompression bombs
FilenameRemove path components, sanitise charactersPrevents path traversal
import magic
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.pdf'}
ALLOWED_MIMES = {'image/jpeg', 'image/png', 'application/pdf'}
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
def validate_upload(file) -> None:
"""Validate an uploaded file."""
# Check extension
ext = Path(file.filename).suffix.lower()
if ext not in ALLOWED_EXTENSIONS:
raise ValidationError(f"File type {ext} not permitted")
# Check file size
file.seek(0, 2) # Seek to end
size = file.tell()
file.seek(0) # Reset to beginning
if size > MAX_FILE_SIZE:
raise ValidationError("File exceeds 10 MB limit")
# Check magic bytes
mime = magic.from_buffer(file.read(2048), mime=True)
file.seek(0)
if mime not in ALLOWED_MIMES:
raise ValidationError(f"File content type {mime} not permitted")

Output Encoding

Output encoding transforms data to prevent interpretation as code or markup in the output context. Data safe in one context becomes dangerous in another; a string containing <script> is harmless in a JSON response but executes as JavaScript when inserted into HTML without encoding.

Encoding by Context

Output contextEncoding methodCharacters encodedExample
HTML bodyHTML entity encoding< > & " '< becomes &lt;
HTML attributeHTML entity encoding< > & " ' `" becomes &quot;
JavaScript stringJavaScript escape\ ' " / < > and control chars' becomes \'
JavaScript in HTMLJavaScript escape then HTML encodeBoth setsDouble encoding required
URL parameterPercent encodingNon-alphanumeric except - _ . ~space becomes %20
CSS valueCSS escapeNon-alphanumeric\ prefix with hex code
JSON valueJSON string escape\ " / and control chars" becomes \"
SQL (when parameterisation impossible)Database-specific escape' \ NULVaries by database
LDAPLDAP escape\ * ( ) NUL* becomes \2a
XMLXML entity encoding< > & " '& becomes &amp;
Command lineShell escape or avoid entirelyShell metacharactersPrefer library calls

Context-Specific Examples

HTML body encoding prevents injected markup from rendering as HTML elements:

from markupsafe import escape
def render_user_comment(comment: str) -> str:
"""Render a user comment safely in HTML."""
# escape() converts < > & " ' to HTML entities
safe_comment = escape(comment)
return f'<p class="comment">{safe_comment}</p>'
# Input: <script>alert('xss')</script>
# Output: <p class="comment">&lt;script&gt;alert('xss')&lt;/script&gt;</p>

HTML attribute encoding handles the additional context of being inside a quoted attribute value:

<!-- Dangerous: unencoded user input in attribute -->
<a href="/search?q={{ user_input }}">Search</a>
<!-- Safe: URL-encoded user input -->
<a href="/search?q={{ user_input | urlencode }}">Search</a>

JavaScript context requires JavaScript escaping, and JavaScript embedded in HTML requires both JavaScript escaping and HTML encoding:

import json
def render_config_script(user_preferences: dict) -> str:
"""Render user preferences as JavaScript configuration."""
# json.dumps handles JavaScript string escaping
# The output is safe for a <script> block
json_safe = json.dumps(user_preferences)
return f'<script>const config = {json_safe};</script>'

Encoding Functions by Language

LanguageHTML encodingURL encodingJSON encoding
Pythonmarkupsafe.escape()urllib.parse.quote()json.dumps()
JavaScripttextContent property or libraryencodeURIComponent()JSON.stringify()
JavaStringEscapeUtils.escapeHtml4()URLEncoder.encode()Jackson/Gson
C#HttpUtility.HtmlEncode()Uri.EscapeDataString()JsonSerializer.Serialize()
PHPhtmlspecialchars()urlencode()json_encode()
RubyERB::Util.html_escape()CGI.escape()JSON.generate()
Gohtml.EscapeString()url.QueryEscape()json.Marshal()

SQL Injection Prevention

SQL injection occurs when attacker-controlled input becomes part of a SQL query structure, allowing execution of arbitrary SQL commands. Prevention requires separating query structure from query data through parameterised queries.

Parameterised Queries

Parameterised queries (also called prepared statements) send query structure and data separately to the database. The database parses the query structure first, then binds the data values, making it impossible for data to alter query structure.

# Vulnerable: string concatenation
def get_user_vulnerable(username: str):
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
# Input: admin'--
# Resulting query: SELECT * FROM users WHERE username = 'admin'--'
# The -- comments out the rest, bypassing any additional conditions
# Secure: parameterised query
def get_user_secure(username: str):
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))
# The database treats username as data, never as SQL

Parameter Syntax by Database

DatabasePositional parameterNamed parameter
PostgreSQL$1, $2Not supported
MySQL%s%(name)s
SQLite?:name
SQL Server@p1@name
Oracle:1:name

ORM Usage

Object-Relational Mappers (ORMs) generate parameterised queries when used correctly. Raw query methods bypass this protection and require the same care as direct SQL.

# Django ORM - safe by default
User.objects.filter(username=user_input)
# Django ORM - raw query requires parameterisation
User.objects.raw('SELECT * FROM users WHERE username = %s', [user_input])
# SQLAlchemy - safe by default
session.query(User).filter(User.username == user_input)
# SQLAlchemy - text queries require parameters
from sqlalchemy import text
session.execute(text('SELECT * FROM users WHERE username = :name'), {'name': user_input})

Dynamic Query Elements

Identifiers (table names, column names) and SQL keywords cannot be parameterised. When queries require dynamic identifiers, validate against an explicit allowlist.

ALLOWED_SORT_COLUMNS = {'username', 'created_at', 'email'}
ALLOWED_SORT_DIRECTIONS = {'ASC', 'DESC'}
def get_users_sorted(sort_column: str, sort_direction: str):
# Validate against allowlist - never use user input directly
if sort_column not in ALLOWED_SORT_COLUMNS:
raise ValidationError("Invalid sort column")
if sort_direction.upper() not in ALLOWED_SORT_DIRECTIONS:
raise ValidationError("Invalid sort direction")
# Safe to include validated identifiers
query = f"SELECT * FROM users ORDER BY {sort_column} {sort_direction}"
cursor.execute(query)

Cross-Site Scripting Prevention

Cross-site scripting (XSS) injects malicious scripts into pages viewed by other users. Prevention combines input validation, output encoding, and Content Security Policy headers.

XSS Types and Mitigations

Stored XSS persists malicious input in the database, affecting all users who view the stored content. Reflected XSS includes malicious input in the immediate response, affecting users who click crafted links. DOM-based XSS occurs when client-side JavaScript insecurely handles data.

XSS typeAttack vectorPrimary mitigationSecondary mitigation
StoredDatabase content rendered without encodingOutput encodingInput validation, CSP
ReflectedURL parameter rendered without encodingOutput encodingInput validation, CSP
DOM-basedJavaScript handling of location, document propertiesSafe DOM APIsCSP, Trusted Types

Safe DOM APIs

JavaScript DOM manipulation must use APIs that treat data as text, not as HTML.

Dangerous APISafe alternativeReason
innerHTMLtextContentinnerHTML parses as HTML
outerHTMLtextContent on parentouterHTML parses as HTML
document.write()DOM manipulation methodsWrites directly to document
eval()JSON.parse() for JSONeval executes arbitrary code
setTimeout(string)setTimeout(function)String form uses eval
setInterval(string)setInterval(function)String form uses eval
// Dangerous: innerHTML interprets HTML
element.innerHTML = userInput;
// Safe: textContent treats input as text
element.textContent = userInput;
// When HTML structure is needed, create elements programmatically
const link = document.createElement('a');
link.href = sanitisedUrl;
link.textContent = userInput; // User input only in text content
parentElement.appendChild(link);

Content Security Policy

Content Security Policy (CSP) HTTP headers instruct browsers to restrict resource loading and script execution. CSP provides defence in depth when encoding mistakes occur.

Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.org;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
DirectiveRecommended valueEffect
default-src'self'Fallback for unspecified directives
script-src'self'Blocks inline scripts and external script sources
style-src'self'Blocks inline styles (use 'unsafe-inline' if required)
img-src'self' data: https:Allows images from same origin, data URIs, HTTPS
connect-src'self' plus API domainsRestricts fetch/XHR destinations
frame-ancestors'none'Prevents framing (clickjacking protection)
base-uri'self'Prevents base tag injection
form-action'self'Restricts form submission targets

Cross-Site Request Forgery Prevention

Cross-site request forgery (CSRF) tricks authenticated users into submitting requests to applications where they have active sessions. Prevention requires verifying that requests originate from the legitimate application.

CSRF Token Implementation

CSRF tokens are random values generated server-side, embedded in forms, and validated on submission. The token proves the request originated from a page served by the application.

import secrets
def generate_csrf_token(session) -> str:
"""Generate and store a CSRF token for the session."""
token = secrets.token_urlsafe(32)
session['csrf_token'] = token
return token
def validate_csrf_token(session, submitted_token: str) -> bool:
"""Validate the submitted CSRF token against the session token."""
stored_token = session.get('csrf_token')
if not stored_token:
return False
return secrets.compare_digest(stored_token, submitted_token)
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="text" name="amount">
<button type="submit">Transfer</button>
</form>

Additional CSRF Defences

DefenceMechanismLimitations
CSRF tokenRandom token validated server-sideRequires server-side session
SameSite cookiesCookie attribute restricts cross-site sendingBrowser support varies; not all request types
Origin header checkVerify Origin or Referer header matchesHeaders can be absent in some scenarios
Custom request headerRequire X-Requested-With or similarOnly works for AJAX requests
Re-authenticationRequire password for sensitive actionsUser friction

SameSite cookie attribute configuration:

# Session cookie configuration
response.set_cookie(
'session_id',
value=session_id,
secure=True, # HTTPS only
httponly=True, # Not accessible to JavaScript
samesite='Lax' # Sent with top-level navigations, not cross-site requests
)
SameSite valueBehaviour
StrictNever sent with cross-site requests
LaxSent with top-level navigations (links) but not embedded requests
NoneSent with all requests (requires Secure attribute)

Authentication Implementation

Authentication implementation must use established libraries and frameworks. Custom authentication code introduces vulnerabilities. This section specifies requirements for authentication controls.

Password Handling

Passwords are hashed using memory-hard algorithms designed for password storage. General-purpose cryptographic hashes (SHA-256, MD5) are unsuitable because they are fast to compute, enabling rapid brute-force attacks.

AlgorithmStatusParameters
Argon2idRecommendedMemory: 64 MB, iterations: 3, parallelism: 4
bcryptAcceptableCost factor: 12 minimum
scryptAcceptableN: 2^17, r: 8, p: 1
PBKDF2-SHA256Legacy only600,000 iterations minimum
SHA-256, SHA-512ProhibitedN/A
MD5ProhibitedN/A
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # Iterations
memory_cost=65536, # 64 MB
parallelism=4,
hash_len=32,
salt_len=16
)
def hash_password(password: str) -> str:
"""Hash a password for storage."""
return ph.hash(password)
def verify_password(stored_hash: str, password: str) -> bool:
"""Verify a password against its hash."""
try:
ph.verify(stored_hash, password)
return True
except argon2.exceptions.VerifyMismatchError:
return False

Session Management

RequirementSpecification
Session ID length128 bits minimum (32 hex characters)
Session ID generationCryptographically secure random generator
Session ID transmissionCookies only; never in URLs
Session ID rotationRegenerate after authentication
Session timeout (idle)30 minutes for standard applications
Session timeout (absolute)12 hours maximum
Session invalidationServer-side invalidation on logout
import secrets
def create_session() -> str:
"""Create a new session with a secure random ID."""
session_id = secrets.token_hex(16) # 128 bits
return session_id
def post_authentication(old_session_id: str, user_id: int) -> str:
"""Regenerate session ID after successful authentication."""
# Invalidate old session
invalidate_session(old_session_id)
# Create new session
new_session_id = create_session()
associate_session_with_user(new_session_id, user_id)
return new_session_id

Multi-Factor Authentication

MFA implementation requirements:

RequirementSpecification
TOTP secret length160 bits minimum
TOTP time step30 seconds
TOTP valid windowCurrent period plus one previous period
Backup codes10 codes, single use, 8 characters each
Rate limiting5 failed attempts triggers 15-minute lockout
import pyotp
def generate_totp_secret() -> str:
"""Generate a TOTP secret for a user."""
return pyotp.random_base32(length=32) # 160 bits
def verify_totp(secret: str, code: str) -> bool:
"""Verify a TOTP code with one-period tolerance."""
totp = pyotp.TOTP(secret)
return totp.verify(code, valid_window=1)

Authorisation Implementation

Authorisation determines whether an authenticated user has permission to access a resource or perform an action. Authorisation checks occur on every request, enforced server-side.

Authorisation Check Placement

+------------------------------------------------------------------+
| REQUEST FLOW |
+------------------------------------------------------------------+
| |
| +----------+ +---------------+ +------------------+ |
| | | | | | | |
| | Client +---->+ Authentication+---->+ Authorisation | |
| | | | Middleware | | Middleware | |
| +----------+ +-------+-------+ +--------+---------+ |
| | | |
| v v |
| +-------+-------+ +--------+---------+ |
| | Is user | | Does user have | |
| | authenticated?| | permission for | |
| +-------+-------+ | this resource? | |
| | +--------+---------+ |
| | | |
| +----------+----------+ | |
| | | | |
| v v v |
| +----+----+ +-----+----+ +----+----+ |
| | No | | No | | Yes | |
| | 401 | | 403 | | Allow | |
| +---------+ +----------+ +---------+ |
| |
+------------------------------------------------------------------+

Common Authorisation Vulnerabilities

VulnerabilityDescriptionPrevention
Insecure direct object referenceAccessing resources by guessing IDsVerify ownership/permission for every resource access
Missing function-level access controlAdministrative functions accessible to regular usersCheck permissions at every endpoint
Path traversalAccessing files outside intended directoryValidate paths, use allowlists, sandbox file access
Privilege escalationUser gains higher privilegesValidate role changes server-side
Parameter tamperingModifying hidden form fields or API parametersIgnore client-supplied authorisation data

Resource-Level Authorisation

Every access to user-specific resources must verify the authenticated user has permission to access that specific resource, not merely that they are authenticated.

def get_document(document_id: int, current_user: User) -> Document:
"""Retrieve a document with ownership verification."""
document = Document.query.get(document_id)
if document is None:
raise NotFoundError("Document not found")
# Verify ownership - not just authentication
if document.owner_id != current_user.id:
# Return 404 to avoid revealing document existence
raise NotFoundError("Document not found")
return document

Cryptography Standards

Cryptographic operations use approved algorithms, key lengths, and implementations. Custom cryptographic implementations are prohibited; use established libraries.

Approved Algorithms

PurposeApproved algorithmKey/output sizeNotes
Symmetric encryptionAES-256-GCM256-bit keyAuthenticated encryption
Symmetric encryptionChaCha20-Poly1305256-bit keyAlternative to AES
Asymmetric encryptionRSA-OAEP2048-bit minimum, 4096-bit recommendedUse SHA-256 for OAEP
Digital signaturesRSA-PSS2048-bit minimum, 4096-bit recommendedUse SHA-256
Digital signaturesEd25519256-bitPreferred for new systems
Digital signaturesECDSA P-256256-bitAcceptable
Key exchangeX25519256-bitPreferred
Key exchangeECDH P-256256-bitAcceptable
Password hashingArgon2idPer parameters aboveMemory-hard
General hashingSHA-256256-bitNot for passwords
General hashingSHA-384/512384/512-bitWhen longer output needed
HMACHMAC-SHA-256256-bit key minimumMessage authentication

Prohibited Algorithms

AlgorithmReason
DES56-bit key; trivially breakable
3DESSlow; 112-bit effective security
RC4Multiple practical attacks
MD5Collision attacks
SHA-1Collision attacks demonstrated
RSA PKCS#1 v1.5Padding oracle attacks
ECB modeReveals patterns in ciphertext
CBC without HMACPadding oracle attacks

Secure Random Generation

Cryptographic random values require cryptographically secure pseudo-random number generators (CSPRNGs). Standard random number generators are predictable.

LanguageSecure random functionInsecure (never use)
Pythonsecrets modulerandom module
JavaScriptcrypto.getRandomValues()Math.random()
JavaSecureRandomRandom
C#RandomNumberGeneratorRandom
Gocrypto/randmath/rand
PHPrandom_bytes()rand(), mt_rand()
import secrets
# Generate secure random token
token = secrets.token_urlsafe(32)
# Generate secure random integer in range
random_id = secrets.randbelow(1000000)
# Secure comparison (timing-safe)
secrets.compare_digest(value_a, value_b)

Secrets in Code

Secrets include API keys, database credentials, encryption keys, tokens, and certificates. Secrets never appear in source code, version control history, logs, error messages, or client-side code.

Prohibited Practices

PracticeRiskExample
Hardcoded credentialsExposed in source, version historypassword = "admin123"
Credentials in config files (committed)Same as hardcodedconfig.py with DB_PASSWORD
Credentials in environment files (committed)Same as hardcoded.env checked into Git
Credentials in logsExposed in log aggregationlogger.info(f"Connecting with {password}")
Credentials in URLsExposed in browser history, logshttps://user:pass@host/
Credentials in client-side codeExposed to all usersAPI key in JavaScript

Secret Management Approaches

ApproachSuitabilityExample
Environment variablesDevelopment, simple deploymentsos.environ['DATABASE_URL']
Secret management serviceProductionHashiCorp Vault, AWS Secrets Manager
Kubernetes secretsKubernetes deploymentsMounted as files or env vars
CI/CD secretsBuild pipelinesGitHub Actions secrets, GitLab CI variables
import os
# Retrieve from environment
database_url = os.environ.get('DATABASE_URL')
if not database_url:
raise ConfigurationError("DATABASE_URL environment variable required")
# Never log secrets
logger.info("Connecting to database") # Correct
logger.info(f"Connecting to {database_url}") # Wrong - logs secret

Git Secret Prevention

Configure Git to prevent accidental secret commits:

Terminal window
# .gitignore - exclude secret-containing files
.env
.env.*
*.pem
*.key
secrets/
config/local.py

Pre-commit hooks detect secrets before they enter version control:

.pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks

Error Handling and Logging

Error messages displayed to users and recorded in logs must not reveal sensitive information that assists attackers.

Error Message Content

InformationDisplay to userInclude in logs
User-friendly error descriptionYesYes
Error reference codeYesYes
Stack traceNoYes (internal errors)
Database queryNoNo (may contain data)
File pathsNoYes
Internal IP addressesNoYes
CredentialsNoNo
SQL error detailsNoYes
User input that caused errorNoYes (sanitised)
import uuid
import logging
logger = logging.getLogger(__name__)
def handle_database_error(error: Exception, context: dict) -> str:
"""Handle database error securely."""
error_id = str(uuid.uuid4())[:8]
# Log full details for debugging
logger.error(
"Database error",
extra={
'error_id': error_id,
'error_type': type(error).__name__,
'error_message': str(error),
'context': context
}
)
# Return safe message to user
return f"A database error occurred. Reference: {error_id}"

Logging Security Events

Security-relevant events require logging for audit and incident response. Each log entry includes timestamp, event type, user identity, resource affected, and outcome.

EventLog levelRequired fields
Authentication successINFOUser ID, IP address, method
Authentication failureWARNINGUsername attempted, IP address, reason
Authorisation failureWARNINGUser ID, resource, action attempted
Password changeINFOUser ID
Permission changeINFOUser ID, old permissions, new permissions, changed by
Data exportINFOUser ID, data type, record count
Administrative actionINFOAdmin user ID, action, target
Input validation failureWARNINGIP address, field, reason (not the input itself)
Rate limit triggeredWARNINGIP address, endpoint, threshold
logger.info(
"Authentication successful",
extra={
'event_type': 'auth_success',
'user_id': user.id,
'ip_address': request.remote_addr,
'method': 'password'
}
)
logger.warning(
"Authentication failed",
extra={
'event_type': 'auth_failure',
'username_attempted': username, # Log username, not password
'ip_address': request.remote_addr,
'reason': 'invalid_password'
}
)

Dependency Security

Third-party dependencies introduce security vulnerabilities. Dependency management requires tracking, updating, and auditing external code.

Dependency Requirements

RequirementSpecification
Version pinningPin exact versions in production
Lock filesCommit lock files to version control
Vulnerability scanningAutomated scanning in CI pipeline
Update frequencySecurity patches within 72 hours; regular updates monthly
Licence complianceVerify compatible licences before adoption
Source verificationUse official package repositories

Vulnerability Response Times

SeverityCVSS scoreResponse time
Critical9.0-10.072 hours
High7.0-8.91 week
Medium4.0-6.91 month
Low0.1-3.9Next regular update
Terminal window
# Python - generate and audit dependencies
pip freeze > requirements.txt
pip-audit --requirement requirements.txt
# JavaScript - audit dependencies
npm audit
npm audit fix
# Ruby - audit dependencies
bundle audit check --update

Secure Defaults

Applications ship with secure default configurations. Users may weaken security deliberately but must not face insecure configurations by default.

SettingSecure defaultInsecure alternative
Cookie Secure flagEnabledDisabled
Cookie HttpOnly flagEnabledDisabled
Cookie SameSiteLax or StrictNone
HTTPSRequiredOptional
TLS version1.2 minimum1.0/1.1
Password minimum length12 charactersLess than 8
Session timeout30 minutesNo timeout
Account lockoutAfter 5 failuresNo lockout
Debug modeDisabledEnabled
Directory listingDisabledEnabled
Server version headersRemovedIncluded
Detailed error messagesDisabledEnabled
Rate limitingEnabledDisabled

See also