Dr J’s Binding Protocol — Document 17

Security Protocol

Comprehensive web application security covering OWASP Top 10 (2025), HTTP hardening, authentication, input validation, supply chain integrity, bot mitigation, DNS security, monitoring, server hardening, privacy compliance, and pre-launch verification.

← Back to Build Hub

Table of Contents

  1. OWASP Top 10 (2025)
  2. HTTP Security Headers
  3. Transport & Encryption
  4. Input Validation & Injection Prevention
  5. Authentication & Session Security
  6. Password Policy & Hashing
  7. CSRF, Rate Limiting & Bot Mitigation
  8. Payment & PCI DSS
  9. API & Webhook Security
  10. Supply Chain & Dependency Security
  11. DNS Security
  12. Server & Runtime Hardening
  13. WAF & Firewall Patterns
  14. Monitoring, Logging & Incident Response
  15. Privacy & Compliance (GDPR, CCPA)
  16. Emerging Threats (2025–2026)
  17. Pre-Launch Security Checklist
  18. Penetration Testing Protocol
  19. File Integrity & HTML Monitoring
  20. Admin Authentication Tiers

1. OWASP Top 10 (2025)

The OWASP Foundation released the eighth edition of its Top 10 in 2025, based on analysis of over 175,000 CVEs and 589 CWEs. Two new categories were added. This is the threat model every site must be built against.

#CategoryWhat It Means for Us
A01 Broken Access Control Now includes SSRF. Admin endpoints must verify auth. Client portal must enforce client isolation. No direct object references without ownership check.
A02 Security Misconfiguration Jumped from #5. Default credentials, debug mode left on, directory listings enabled, missing security headers, verbose error messages. Every tested app had at least one.
A03 Software Supply Chain Failures NEW. Replaces "Vulnerable Components." npm/CDN dependency attacks, typosquatting, compromised maintainer accounts. See Section 10.
A04 Cryptographic Failures Weak hashing, plaintext storage, missing HTTPS, insecure random number generation. All passwords must use Argon2id or bcrypt.
A05 Injection SQL injection, XSS, command injection. All database queries MUST use prepared statements. All output MUST be encoded. See Section 4.
A06 Insecure Design Flaws in business logic, missing threat modeling, no rate limiting on critical flows. Security must be designed in, not bolted on.
A07 Authentication Failures Credential stuffing, weak passwords, missing MFA, session fixation. See Sections 5 and 6.
A08 Software or Data Integrity Failures Unsigned updates, CI/CD pipeline compromise, deserialization attacks. Use SRI on all external scripts.
A09 Security Logging & Alerting Failures Renamed to emphasize alerting. Without alerts that trigger action, logs are useless. See Section 14.
A10 Mishandling of Exceptional Conditions NEW. Covers 24 CWEs: improper error handling, failing open instead of closed, logical errors under abnormal input. Errors must fail secure.
Every section in this document maps to at least one OWASP Top 10 risk. This is not a theoretical exercise. These are the actual attack vectors used against real websites every day. 70% of breaches in hosting environments stem from misconfigurations or unpatched systems (Verizon 2025 DBIR).

2. HTTP Security Headers

Security headers instruct the browser to enforce protections on behalf of your site. Missing headers leave the browser in its most permissive (least secure) mode.

Required Headers

HeaderValuePurpose
Strict-Transport-Security max-age=63072000; includeSubDomains; preload Force HTTPS for 2 years. Prevents SSL stripping attacks. Submit to hstspreload.org for browser preload list.
Content-Security-Policy See nonce-based policy below Prevents XSS, clickjacking, data injection. Only 21.9% adoption globally — massive competitive advantage.
X-Content-Type-Options nosniff Prevents MIME-type sniffing. Stops browsers from interpreting files as a different content type.
X-Frame-Options DENY Prevents clickjacking (legacy fallback). Modern browsers use CSP frame-ancestors instead, but keep this for older browsers.
Referrer-Policy strict-origin-when-cross-origin Prevents leaking internal paths, query params, and session tokens in the Referer header on cross-origin requests.
Permissions-Policy geolocation=(), camera=(), microphone=(), payment=(), usb=() Restricts browser APIs your site doesn't use. Replaces deprecated Feature-Policy. Only 3.7% adoption.
Cross-Origin-Opener-Policy same-origin Isolates your browsing context from cross-origin windows. Prevents Spectre-style side-channel attacks.
Cross-Origin-Resource-Policy same-site Prevents other sites from loading your resources (images, scripts, fonts) cross-origin.

Headers to Remove

HeaderActionWhy
Server Remove or set to non-informative value Reveals server software and version (e.g., nginx/1.24). Helps attackers target known vulnerabilities.
X-Powered-By Remove entirely Reveals backend technology (e.g., PHP/8.3). No legitimate purpose.
X-XSS-Protection Set to 0 or omit Browser XSS auditors have been removed from all modern browsers. This header can actually introduce vulnerabilities.
Expect-CT Remove (deprecated) Certificate Transparency is now enforced by default. Header has no effect.

Content Security Policy (Strict, Nonce-Based)

The recommended CSP approach for 2025–2026 uses nonces instead of domain whitelists. This prevents XSS even if an attacker finds an injection point.

/* Production CSP — nonce-based strict policy */ Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https://res.cloudinary.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.stripe.com; frame-src https://js.stripe.com; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; upgrade-insecure-requests;

Static Sites (Our Pattern)

For sites that use inline scripts and no server-side nonce generation, use hash-based CSP or a relaxed policy with tight connect-src:

/* Static site CSP — domain-whitelist approach */ Content-Security-Policy: default-src 'self'; script-src 'self' https://js.stripe.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.stripe.com; frame-src https://js.stripe.com; object-src 'none'; base-uri 'self'; form-action 'self';
'unsafe-inline' for styles is acceptable. Inline styles are not a meaningful XSS vector because CSS cannot execute JavaScript. The risk is in inline scripts, not inline styles. Avoid 'unsafe-inline' in script-src at all costs.

Nginx Configuration

# Add to server block or location block in 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=(), payment=(), usb=()" always; add_header Cross-Origin-Opener-Policy "same-origin" always; add_header Cross-Origin-Resource-Policy "same-site" always; # Remove information leakage server_tokens off; more_clear_headers Server; fastcgi_hide_header X-Powered-By;

3. Transport & Encryption

HTTPS Everywhere

All traffic must be served over HTTPS. HTTP requests redirect with 301 Moved Permanently to the HTTPS equivalent. No exceptions.

Certificate Management

Secure Cookies

/* Cookie attributes — every cookie must have ALL of these */ Set-Cookie: session_id=abc123; HttpOnly; /* Not accessible via JavaScript — prevents XSS cookie theft */ Secure; /* Only sent over HTTPS — prevents network sniffing */ SameSite=Lax; /* Prevents CSRF in most scenarios */ Path=/; Max-Age=86400; /* Explicit expiry — 24 hours */

SameSite Explained

ValueBehaviorUse When
Strict Cookie never sent on cross-site requests Admin sessions, sensitive operations
Lax Cookie sent on top-level navigations only (clicking a link) Default for most auth cookies. Best balance of security and usability.
None; Secure Cookie sent on all cross-site requests Only for true cross-origin needs (third-party embeds). Requires Secure flag.

Cookie Prefixes

Use cookie prefixes for additional browser-enforced security:

Same-domain API is always preferred. Deploying your API at api.example.com (same parent domain as example.com) allows SameSite=Lax cookies. Cross-origin APIs require SameSite=None, which is less secure.

4. Input Validation & Injection Prevention

Every piece of data that enters your application from outside is hostile until proven otherwise. Validate at system boundaries. Sanitize before output.

The Four-Stage Pipeline

Every form submission, API request, and webhook passes through these stages in order:

1. VALIDATE Check required fields are present Check email format (filter_var + FILTER_VALIDATE_EMAIL) Check length limits (prevent abuse) Verify CSRF token matches session Check rate limit (per-IP, time-window) Check honeypot field is empty 2. SANITIZE htmlspecialchars() on all text inputs Strip tags where HTML is not expected trim() whitespace 3. STORE Insert into database via prepared statement Record: timestamp, IP, user agent, form data 4. NOTIFY Send HTML email to site owner On DevHub: send to MailHog (devhub-mailhog:1025) On Production: use sendmail (Exim auto-DKIM-signs)

SQL Injection Prevention

SQL injection is OWASP A05 and has been a top web vulnerability for over 20 years. The fix is simple and absolute: never concatenate user input into SQL strings.

// CORRECT — Parameterized query (PDO prepared statement) $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? AND active = 1"); $stmt->execute([$_POST['email']]); // WRONG — String concatenation (VULNERABLE) $sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'"; // Attacker sends: ' OR 1=1 -- and gets every user

XSS Prevention

Cross-site scripting (OWASP A05) injects malicious JavaScript into your pages. Two types:

// Encode ALL user-supplied data before rendering in HTML echo htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8'); // For JavaScript context, use json_encode echo 'var data = ' . json_encode($user_input) . ';';

Input Validation Reference

Field TypeValidationSanitization
Name Required, 2–100 characters htmlspecialchars(), trim()
Email Required, filter_var(FILTER_VALIDATE_EMAIL) filter_var(FILTER_SANITIZE_EMAIL)
Phone Optional, 7–20 characters, digits/spaces/dashes/parens Strip non-numeric for storage
Message / Textarea Required, 10–5000 characters htmlspecialchars(), trim(), preserve newlines
Select / Radio Value must be in allowed list (whitelist check) Cast to expected type
File Upload Whitelist extensions, check MIME type server-side, max size Rename with random filename, store outside webroot
Never trust client-side validation alone. HTML5 required and pattern attributes improve UX but are trivially bypassed with curl or browser dev tools. Every validation check must be duplicated server-side. Client-side is for convenience; server-side is for security.

5. Authentication & Session Security

Session-Based Auth (Our Pattern)

All our sites use PHP session-based authentication with httpOnly cookies. The client never stores credentials or tokens — the browser handles the session cookie automatically.

FileRole
api/auth.phpLogin (POST), register (POST), logout (POST). Returns session cookie.
api/auth-session.phpSession validation (GET). Returns user data if authenticated, 401 if not.
js/auth.jsClient-side auth state. Checks session on page load, manages login/logout UI.

Session Flow

1. User submits login form → POST /api/auth.php {action:"login", email, password} 2. Server verifies credentials → password_verify() 3. Server creates PHP session → session_start(), $_SESSION['user_id'] = ... 4. Server returns {ok:true, user:{...}} + Set-Cookie: PHPSESSID=... 5. Client stores nothing → session cookie is httpOnly, handled by browser 6. On page load → auth.js calls GET /api/auth-session.php 7. Server checks $_SESSION → returns user data or 401 8. auth.js updates UI based on response

Session Configuration (PHP)

// Set BEFORE session_start() ini_set('session.cookie_httponly', 1); // No JavaScript access ini_set('session.cookie_secure', 1); // HTTPS only ini_set('session.cookie_samesite', 'Lax'); // CSRF protection ini_set('session.use_strict_mode', 1); // Reject uninitialized session IDs ini_set('session.gc_maxlifetime', 3600); // 1 hour server-side expiry session_start(); // Regenerate session ID after login to prevent session fixation session_regenerate_id(true);

Multi-Factor Authentication (MFA)

MFA adds a second verification layer. The hierarchy of MFA methods, from strongest to weakest:

MethodSecurity LevelStatus
Passkeys / WebAuthn / FIDO2 Highest — phishing-resistant Recommended standard. Native in all major OSes and browsers.
Hardware security keys (YubiKey) Highest — phishing-resistant Best for shared devices and high-security admin accounts.
TOTP (authenticator apps) Medium — phishable Acceptable fallback. Google Authenticator, Authy, etc.
SMS OTP Low — SIM-swap, SS7 attacks Actively deprecated. Philippines, US (USPTO, FINRA), UAE banning by 2026.
Email OTP Low — account takeover risk Actively deprecated. Same attack surface as password reset.

Recovery Codes

Admin accounts must use MFA. At minimum, TOTP-based 2FA for any account that can modify products, orders, users, or site configuration. For admin portals with financial access, require passkeys or hardware keys.

6. Password Policy & Hashing

NIST SP 800-63B Revision 4 (final July 2025) overhauled password guidance. The old complexity rules are gone. Length and breach checking are what matter.

NIST 800-63B Rev 4 Requirements

Old GuidanceNew Guidance (2025)
8-char minimum with complexity rules 8-char minimum, 15+ recommended, up to 64 allowed
Uppercase + number + special required No composition rules. Length over complexity.
Forced 90-day rotation No mandatory rotation unless compromise suspected
Security questions allowed Security questions banned. They are not secrets.
Optional breach checking Mandatory breach checking against known compromised databases (e.g., HaveIBeenPwned API)
Any hashing algorithm Memory-hard functions required (Argon2id, bcrypt)

Hashing Algorithms

Argon2id — Gold Standard (New Projects)

// PHP 7.2+ — use Argon2id for new projects $hash = password_hash($password, PASSWORD_ARGON2ID, [ 'memory_cost' => 65536, // 64 MB — makes GPU/ASIC attacks economically infeasible 'time_cost' => 4, // 4 iterations 'threads' => 2 // 2 parallel threads ]); // Verify — same function works for both Argon2id and bcrypt if (password_verify($input, $stored_hash)) { /* authenticated */ }

bcrypt — Still Acceptable (Existing Projects)

// bcrypt at cost 12+ remains secure for existing deployments $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]); // No urgent need to migrate from bcrypt to Argon2id // 72-byte input limit is the only meaningful limitation
Use password_hash() and password_verify() — always. These PHP functions handle salt generation, algorithm selection, and future-proof upgrades automatically. Never use md5(), sha1(), sha256(), or any raw hash function for passwords.

Login Security Rules

7. CSRF, Rate Limiting & Bot Mitigation

CSRF Token Implementation

Every form must include a CSRF token. The token is generated server-side, embedded as a hidden field, and verified on submission.

// Generate token (on form page load) if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } // Embed in form <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>"> // Verify on submission (constant-time comparison) if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { http_response_code(403); exit('Invalid CSRF token'); }
Use hash_equals(), not == or ===. String comparison operators are vulnerable to timing attacks — an attacker can determine how many characters match by measuring response time. hash_equals() runs in constant time.

Rate Limiting

EndpointLimitWindow
Login / Register5 requests60 seconds per IP
Contact / Quote forms5 submissions60 seconds per IP
Checkout10 requests60 seconds per IP
Shipping rate lookup20 requests60 seconds per IP
API endpoints (general)60 requests60 seconds per API key
Password reset3 requests60 seconds per IP

Honeypot Spam Prevention

<!-- Hidden from real users via CSS, bots auto-fill it --> <div style="position:absolute;left:-9999px;" aria-hidden="true"> <label for="website_url">Leave this empty</label> <input type="text" name="website_url" id="website_url" tabindex="-1" autocomplete="off"> </div> // Server-side check if (!empty($_POST['website_url'])) { // Bot detected — silently reject http_response_code(200); // Return 200 so bot thinks it worked exit; }
Return HTTP 200 for honeypot rejections. If you return an error code, sophisticated bots learn to leave the field empty. A silent 200 wastes fewer cycles on retry loops.

Bot Detection Layers

Modern bot mitigation uses multiple signals, not just CAPTCHAs:

LayerTechniqueCatches
1. Honeypot Hidden form field (our standard) Basic automated bots. Free, zero UX impact.
2. Rate limiting Per-IP request throttling Brute force, credential stuffing, spam floods.
3. Behavioral Mouse movement, keystroke timing, scroll patterns Headless browsers. Bots move in straight lines; humans don't.
4. Reputation IP reputation, ASN/hosting detection, TOR exit nodes Known bot networks, VPN/proxy abuse, data center IPs.
5. Challenge Cloudflare Turnstile (preferred) or hCaptcha Sophisticated bots. Use only when layers 1–4 are insufficient.
Cloudflare Turnstile is preferred over reCAPTCHA. No Google cookies (GDPR-friendly), invisible/passive UX, free tier available. reCAPTCHA v3 has better ML detection but creates privacy concerns. Turnstile catches 33% of sophisticated bots — layer it with honeypot + rate limiting for comprehensive coverage.

8. Payment & PCI DSS

Cardinal Rule

Never store, process, or transmit raw credit card data. No card numbers, CVVs, expiration dates, or magnetic stripe data should ever touch your server. Use Stripe Checkout (hosted payment page) or Stripe Elements (embedded fields) so card data goes directly to Stripe, never to you.

PCI Compliance Levels

LevelWhat It MeansOur Target
SAQ-A All payment processing is fully outsourced. No card data on your server. This is us (Stripe Checkout)
SAQ-A-EP Embedded payment fields (Stripe Elements). Card data doesn't touch server, but your page hosts the form. Acceptable alternative
SAQ-D You handle card data directly. Full PCI audit required. Never needed for our sites

Stripe Webhook Security

// ALWAYS verify webhook signatures — prevents forged events $payload = file_get_contents('php://input'); $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE']; $endpoint_secret = getenv('STRIPE_WEBHOOK_SECRET'); try { $event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); } catch (\Exception $e) { http_response_code(400); exit('Invalid signature'); } // Idempotency: check if order already exists for this session $session_id = $event->data->object->id; $existing = $pdo->prepare("SELECT id FROM orders WHERE stripe_session_id = ?"); $existing->execute([$session_id]); if ($existing->fetch()) { exit('Already processed'); }

9. API & Webhook Security

Admin Endpoint Protection

Every admin API route must verify authentication before processing. No unprotected admin endpoints — ever.

// Admin API — verify Bearer token $auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; if (!preg_match('/^Bearer\s+(.+)$/i', $auth_header, $m)) { http_response_code(401); exit(json_encode(['error' => 'Unauthorized'])); } if (!hash_equals(getenv('ADMIN_TOKEN'), $m[1])) { http_response_code(403); exit(json_encode(['error' => 'Forbidden'])); }

CORS Configuration

// Set CORS origin to production domain — NEVER use wildcard '*' $allowed_origin = getenv('CORS_ORIGIN') ?: 'https://example.com'; header("Access-Control-Allow-Origin: $allowed_origin"); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization'); header('Access-Control-Allow-Credentials: true'); // Handle preflight if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }

JWT Best Practices

If using JWTs (for API-to-API communication, not browser auth where sessions are preferred):

Error Handling (OWASP A10)

// CORRECT — Generic error to client, detailed error to logs try { // ... database operation ... } catch (\Exception $e) { error_log('Order creation failed: ' . $e->getMessage()); http_response_code(500); exit(json_encode(['error' => 'Something went wrong. Please try again.'])); } // WRONG — Leaks internals to attacker exit(json_encode(['error' => $e->getMessage()])); // "SQLSTATE[42S02]: Base table or view not found: Table 'prod_db.orders'..."

10. Supply Chain & Dependency Security

Software supply chain attacks are now OWASP Top 10 #3 (A03:2025). In 2025, compromised npm packages via phished maintainer accounts affected thousands of projects. This is not theoretical.

Subresource Integrity (SRI)

Every script or stylesheet loaded from a CDN must include an integrity attribute. If the CDN is compromised and the file changes, the browser refuses to execute it.

<!-- CORRECT — SRI hash verifies file integrity --> <script src="https://cdn.example.com/lib.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w" crossorigin="anonymous"></script> <!-- WRONG — No integrity check, CDN compromise = your site compromised --> <script src="https://cdn.example.com/lib.js"></script>

npm Dependency Security

PHP Dependency Security

Secrets Management

API keys, tokens, database passwords, and webhook secrets live in .env files only. Never in source code, never in git, never in client-side JavaScript.
Add .env to .gitignore in every project. Search the codebase before every deploy: grep -r "sk_live\|sk_test\|whsec_\|password" --include="*.php" --include="*.js" .
Production .env lives in the shared/ directory on the deployment server. It persists across deploys and is never included in the build artifact.
Rotate secrets immediately if exposed. If a key appears in a git commit (even if reverted), consider it compromised. Generate new keys, revoke old ones.

11. DNS Security

DNS is the foundation of your site's identity. If DNS is compromised, attackers can redirect traffic, intercept email, and issue fraudulent SSL certificates.

Required DNS Records

Record TypePurposeStatus
CAA Restricts which Certificate Authorities can issue SSL certs for your domain Required. Set to 0 issue "letsencrypt.org"
SPF (TXT) Specifies which mail servers can send email for your domain Auto-created by dpanel
DKIM (TXT) Cryptographically signs outbound email to prove it's from you Auto-created by dpanel
DMARC (TXT) Tells receiving servers what to do with email that fails SPF/DKIM Auto-created by dpanel

DNSSEC

DNSSEC cryptographically signs DNS records, preventing spoofing and cache poisoning. Without DNSSEC, CAA records are vulnerable to DNS spoofing — an attacker could forge DNS responses to bypass your CAA restrictions and obtain fraudulent certificates.

Email Authentication Chain

All three records (SPF + DKIM + DMARC) must be present and aligned. Missing any one creates a gap that phishers exploit:

DNS Monitoring

12. Server & Runtime Hardening

SSH Configuration

# /etc/ssh/sshd_config — production settings PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes Protocol 2 MaxAuthTries 3 ClientAliveInterval 300 ClientAliveCountMax 2 X11Forwarding no AllowTcpForwarding no # Use Ed25519 keys (preferred) or RSA 4096-bit minimum # chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys

PHP Production Settings

; php.ini — production security settings expose_php = Off ; Don't reveal PHP version in headers display_errors = Off ; Never show errors to users log_errors = On ; Log errors to file error_log = /var/log/php/error.log allow_url_fopen = Off ; Prevent remote file inclusion allow_url_include = Off ; Prevent remote code inclusion enable_dl = Off ; Disable dynamic loading of extensions open_basedir = /var/www/html:/tmp ; Restrict filesystem access disable_functions = exec,passthru,shell_exec,system,proc_open,popen session.cookie_httponly = 1 session.cookie_secure = 1 session.use_strict_mode = 1 session.cookie_samesite = Lax

File Permissions

PathPermissionsNotes
Web root directories755Owner rwx, group/others rx
Web files (HTML, CSS, JS, PHP)644Owner rw, group/others r
Config files (.env, wp-config.php)400Owner read-only. Never 644 or 777.
Upload directories755Never 777. Store uploads outside webroot if possible.
Log directories750Web server group read
SSL private keys600Root/service owner only

Nginx Security

# Block access to sensitive files and directories location ~ /\. { deny all; } # .env, .git, .htaccess location ~ \.(sql|bak|log|conf)$ { deny all; } # Database dumps, logs, configs location ~ ^/(api|migrations)/.+\.php$ { # Allow API PHP files fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; include fastcgi_params; } location ~ \.php$ { deny all; } # Block all other direct PHP access # Prevent directory listing autoindex off; # Limit request body size (prevents large file upload attacks) client_max_body_size 10m; # Rate limit login endpoints limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; location = /api/auth.php { limit_req zone=login burst=3 nodelay; fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; include fastcgi_params; }

Firewall Rules

13. WAF & Firewall Patterns

A Web Application Firewall inspects HTTP traffic and blocks malicious requests before they reach your application code.

OWASP Core Rule Set (CRS)

The OWASP CRS is the standard WAF rule set, used with ModSecurity or Cloudflare. It blocks:

Attack TypeWhat CRS Catches
SQL InjectionUNION-based, blind, time-based, parameterized patterns
XSSReflected, stored, DOM-based script injection patterns
File Inclusion (LFI/RFI)Path traversal (../), remote file includes
Remote Code ExecutionCommand injection, shell metacharacters
Scanner DetectionKnown vulnerability scanner signatures (Nikto, sqlmap)
Protocol ViolationsMalformed HTTP, invalid methods, oversized headers
Data LeakageBlocks responses containing error messages, stack traces, sensitive patterns

Paranoia Levels

Custom Rules to Add

# Block access to sensitive paths location = /.env { return 403; } location = /.git { return 403; } location = /wp-config.php { return 403; } location = /phpinfo.php { return 403; } location = /composer.json { return 403; } location = /package.json { return 403; } # Block empty or generic bot User-Agents if ($http_user_agent = "") { return 403; } if ($http_user_agent ~* "(Havij|sqlmap|nikto|w3af|Nessus)") { return 403; } # Block abnormally large request headers large_client_header_buffers 4 8k;
Run in detection mode first. Deploy WAF rules in DetectionOnly mode for 1–2 weeks to identify false positives before switching to blocking mode. Review logs and whitelist legitimate requests that trigger rules.

14. Monitoring, Logging & Incident Response

OWASP A09:2025 was renamed "Security Logging and Alerting Failures" to emphasize: logging without alerting is useless.

What to Log

EventLog DataAlert On
Authentication events Timestamp, IP, user ID, success/failure, method 5+ failed logins in 60 seconds from same IP
Authorization failures Timestamp, IP, user ID, requested resource, reason Any attempt to access admin endpoints without valid auth
Input validation failures Timestamp, IP, endpoint, rejected input (sanitized) Injection patterns detected (SQL, XSS, command)
Admin actions Timestamp, admin user, action, affected records New admin account created, bulk delete, config change
Payment events Timestamp, order ID, amount, status, Stripe event type Failed webhook verification, unusual refund patterns
Application errors Timestamp, error type, stack trace, request context Error rate exceeds baseline (5xx spike)

Logging Rules

Incident Response Plan

Detect: Automated monitoring alerts on anomalous behavior (login failures, error spikes, unusual traffic patterns). Response time goal: under 5 minutes.
Contain: Isolate the affected system. Block the attacker's IP. Disable compromised accounts. Do not destroy evidence.
Assess: Determine the scope: what was accessed, what was modified, what data was exposed. Check logs, database audit trails, and file modification times.
Notify: GDPR requires supervisory authority notification within 72 hours. If high risk to individuals, notify them directly. PCI DSS requires immediate card brand notification.
Remediate: Patch the vulnerability. Rotate all potentially compromised credentials. Deploy monitoring for the attack vector. Update this protocol if a new pattern was discovered.
Review: Post-incident review within 72 hours. Document what happened, how it was detected, response timeline, root cause, and what will prevent recurrence.

15. Privacy & Compliance (GDPR, CCPA)

GDPR (EU/EEA)

The General Data Protection Regulation applies to any business serving EU/EEA residents, regardless of where the business is located. Maximum penalty: 4% of global annual revenue or €20M, whichever is higher.

Cookie Consent

If your site uses only essential cookies (session, CSRF, cart) with no analytics or advertising tracking, no cookie banner is needed. Essential cookies are GDPR-exempt. Our sites typically fall into this category.

Data Subject Rights

CCPA / CPRA (California)

Applies if your business has annual gross revenue over $25M, processes data of 100,000+ California residents, or derives 50%+ revenue from selling personal information.

E-Commerce Privacy Checklist

16. Emerging Threats (2025–2026)

New attack vectors that didn't exist (or weren't practical) two years ago.

AI-Powered Attacks

Prompt Injection

If your site integrates AI features (chatbots, search, content generation), prompt injection is the #1 risk (OWASP LLM Top 10).

Supply Chain Escalation

Supply chain attacks elevated to OWASP A03:2025. Key 2025 incidents:

Browser Security Evolution

Review this section quarterly. The threat landscape evolves faster than any document can capture. Subscribe to OWASP newsletters, CISA advisories, and your dependency ecosystem's security announcements (npm, Packagist).

17. Pre-Launch Security Checklist

Every item must pass before deploying to production. No exceptions. A single missing item is a launch blocker.

Security Measure Why It Matters
HTTPS everywhere with HSTSEncrypts all data in transit. Required by Stripe, expected by customers, rewarded by Google.
All security headers configured (see Section 2)CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, COOP.
CSRF tokens on all formsPrevents attackers from submitting forms on behalf of other users.
Parameterized SQL queries (PDO prepared statements)Prevents SQL injection. Never concatenate user input into SQL.
Input sanitization (htmlspecialchars)Prevents XSS — malicious JavaScript injected via form fields.
Rate limiting on all sensitive endpointsPrevents brute-force, spam floods, and checkout abuse.
Honeypot on all public formsCatches automated bots without degrading UX. Returns 200 on detection.
Webhook signature verificationPrevents fake webhook events that could create fraudulent orders.
No card data on serverStripe Checkout handles it. No card data = PCI SAQ-A (simplest compliance).
Email validation (filter_var)Prevents malformed addresses, header injection, bounce storms.
No secrets in source codeAPI keys and passwords in .env only. grep -r "sk_live\|sk_test\|whsec_" returns zero matches.
Generic error messagesNo stack traces, SQL errors, or file paths in responses. Detailed errors in logs only.
Admin endpoints require authEvery admin API route verifies Bearer token or session auth.
SRI on all external scriptsPrevents CDN compromise from injecting malicious code.
CORS origin set to production domainNot wildcard, not DevHub. Prevents cross-origin API abuse.
.env file not publicly accessibleBlocked by nginx location ~ /\. rule.
Debug/display_errors disabledNo PHP errors, stack traces, or debug output visible to users.
All DevHub URLs purged from sourceSearch for devhub, preview.ecom. Any remaining cause broken calls and Google indexing.
CAA record restricts certificate issuanceOnly Let's Encrypt can issue certs for your domain.
SPF, DKIM, DMARC records verifiedPrevents email spoofing and phishing using your domain.
Cookie attributes set (HttpOnly, Secure, SameSite)Prevents session hijacking via XSS and network sniffing.
Session regenerated after loginPrevents session fixation attacks.
Server/X-Powered-By headers removedPrevents server fingerprinting.
Sensitive file paths blocked in nginx.env, .git, .sql, composer.json, package.json all return 403.
24 items. All must pass. No "we'll fix it later." Every check addresses a real-world attack vector that is automated and constant. SQL injection, XSS, CSRF, and webhook spoofing are not theoretical.

18. Penetration Testing Protocol

Before every production deployment, manually verify security measures work. These are the exact tests to run.

Input & Injection Tests

Submit every form with empty required fields. Bypass browser validation (curl or dev tools). Server must reject the submission with a proper error response.
Submit <script>alert('xss')</script> in every text field. Output must be escaped, not executed. Check the database too — stored XSS is worse than reflected.
Submit SQL injection payloads in text fields. Try ' OR 1=1 -- and '; DROP TABLE users; --. Application must handle gracefully (prepared statements prevent this by design).

Auth & Access Control Tests

Call every admin API endpoint without the Authorization header. Each must return 401 Unauthorized. No data leakage in the error response.
Access client-specific resources with a different client's session. Portal must enforce client isolation — Client A must never see Client B's orders, agreements, or portal data.
Attempt to access protected pages without a session. Portal, account, and order pages must redirect to login, not show partial content or empty shells.

Payment & Webhook Tests

Send a webhook request without a valid Stripe signature. Endpoint must return 400 and must not create an order. Verify in the database that no record was created.
Send the same webhook event twice. Idempotency guard must prevent duplicate orders. Second request should return success but create no new records.

Information Leakage Tests

Search the entire codebase for hardcoded keys. Run grep -r "sk_live\|sk_test\|whsec_\|password.*=.*['\"]" --include="*.php" --include="*.js" . — zero matches expected.
Trigger an intentional server error. The customer-facing response must be generic. Detailed error must appear only in server logs. Check response body for stack traces, SQL, file paths.
Request sensitive file paths directly. /.env, /.git/config, /composer.json, /api/config.php must all return 403 or 404 — never the file contents.
Check response headers. Server and X-Powered-By must be absent or non-informative. All security headers from Section 2 must be present.

Automated Scan

After manual tests pass, run automated tools:

Document every test result. Keep a security test log per site per deployment. If a test fails, fix the issue and re-run the entire checklist — a fix in one area can introduce a regression in another.

19. File Integrity & HTML Monitoring

Automated detection of unauthorized changes to production site files and rendered HTML content. This catches file tampering, database injection, defacement, SEO spam injection, and malware insertion — whether the attacker modifies files directly or manipulates database content that renders into pages.

Architecture: Origin-Only Scanning

Integrity scans run exclusively against the origin server (prod-hetzner), not CDN edge copies. The origin holds the master files — if the origin is clean, edge copies are clean. This keeps the monitoring footprint near-zero and avoids polluting CDN caches or inflating bandwidth.

Layer 1: Deploy-Time File Manifest

At deploy time, generate a SHA-256 checksum of every file in the release. This manifest represents the known-good state and is stored in the deployment’s shared directory.

Layer 2: Rendered HTML Snapshot Comparison

File integrity alone misses database-level attacks. If an attacker injects a <script> tag into a product description via SQL injection, the PHP files are untouched but the rendered HTML is compromised. HTML snapshot monitoring catches this.

Implementation: THR Hub Integration

The THR Hub already monitors multiple sites (health checks, scoresheets, sync status). File integrity becomes another monitoring dimension:

What This Detects

Attack TypeFile ManifestHTML Snapshot
Web shell uploaded
PHP file modified (backdoor)
JavaScript injection (file-level)
SQL injection → malicious content in DB
SEO spam links injected via DB
Site defacement
Hidden redirect added
Product price manipulation (DB)

Scan Frequency Guidelines

The two layers are complementary, not redundant. File manifests catch filesystem attacks instantly (5-minute window). HTML snapshots catch database-level attacks that bypass the filesystem entirely. Together they provide comprehensive tamper detection with minimal overhead.

20. Admin Authentication Tiers

Three distinct authentication tiers enforce the principle of least privilege across the platform. Each tier has progressively stricter security requirements based on the scope of access granted.

Tier 1: Master Admin (Hub)

Full cross-site access. User management, system settings, deployment controls, all site data.

Tier 2: Site Admin (Per-Site Dashboards)

Scoped to a single site. Manages products, orders, members, and site-specific settings. Cannot access other sites or system-level controls.

Tier 3: Client Portal User

End-user access. Views their own orders, products, and account information. No administrative capabilities.

Session Timeout Protocol: Sliding Window

All session timeouts use a sliding window (inactivity-based) model, not absolute expiration. The timer resets on every authenticated action:

Implementation: store last_activity timestamp on the session record. On every authenticated request, compare NOW() - last_activity against the tier’s timeout threshold. If expired, destroy the session and return 401. If valid, update last_activity to now.

Never rely on client-side timeout. JavaScript timers can be manipulated. The server must be the sole authority on session validity. Client-side timeout UX (redirect to login, warning modals) is a convenience — the server enforces the real boundary.

Two-Factor Authentication: TOTP

Time-based One-Time Password (RFC 6238) is the recommended 2FA method. Compatible with Google Authenticator, Authy, 1Password, and all standard TOTP apps.

2FA Method Comparison

MethodSecurityUX FrictionCostRecommendation
TOTP (Authenticator app)StrongLowFreePrimary — use this
Email OTPModerateMediumFreeEmergency fallback only
WebAuthn / PasskeysStrongest (phishing-proof)LowestFreeFuture state — add when browser support matures
SMS OTPWeak (SIM swap attacks)MediumPer-messageDo not use

Migration Path: Replacing Static Tokens

The current PC and THR admin dashboards use a static X-Admin-Token header. This must be replaced:

Current PatternTarget Pattern
Static X-Admin-Token headerSession cookie + bcrypt login
Same token for all usersPer-user credentials with role assignment
Token never expiresSliding-window inactivity timeout
No audit trailEvery admin action logged with user, IP, timestamp
No 2FATOTP mandatory for Tier 1, optional for Tier 2
No revocationSessions can be killed individually or globally
The portal auth system already implements most of Tier 1 and Tier 3. The primary work is replacing the static token pattern in site admin dashboards (Tier 2) and adding TOTP to the master admin login flow. This is an incremental upgrade, not a rewrite.