Application security expertise for PHP REST APIs — OWASP Top 10, JWT, OAuth 2.1, CORS, secrets management, SQL injection, XSS, CSRF, input sanitization, file upload safety, security headers, and dependency auditing. Use when auditing code, reviewing authentication/authorization, handling user input, or checking compliance with OWASP ASVS for the php-api-builder library.
Application security expertise for PHP REST APIs — OWASP Top 10, JWT, OAuth 2.1, CORS, secrets management, SQL injection, XSS, CSRF, input sanitization, file upload safety, security headers, and dependency auditing. Use when auditing code, reviewing authentication/authorization, handling user input, or checking compliance with OWASP ASVS for the php-api-builder library.
Security Auditor — OWASP & Application Security
This skill is for finding and preventing security bugs. Think adversarially: "how would I break this?"
OWASP Top 10 (2021) — what to look for
A01 Broken Access Control
Every endpoint: is authentication required? Is authorization (role/scope/ownership) checked?
Horizontal privilege escalation: can user A access user B's resource by guessing an ID? (GET /orders/123 — verify orders.user_id === current_user.id).
Default to deny. #[PublicResource] should be explicit and rare.
IDs: prefer opaque/UUID over sequential integers for publicly exposed resources when enumeration matters.
A02 Cryptographic Failures
Passwords: onlypassword_hash($pw, PASSWORD_ARGON2ID) + password_verify. Never MD5/SHA1/SHA256 directly.
Secrets (API keys, JWT secrets, DB creds): never in code. Only in .env. Never committed.
TLS everywhere in production. No HTTP in prod.
Don't log raw secrets. Use a SensitiveDataFilter (the library has one) to redact password, token, authorization, api_key.
A03 Injection
SQL: PDO with ? or :name parameters. Never string interpolation. Even for ORDER BY columns — allowlist.
Command: never pass user input to exec, shell_exec, system, passthru, popen. If truly unavoidable, use escapeshellarg() and prefer a language binding over shelling out.
LDAP/XPath: escape per-protocol.
Template injection: don't eval or include user-controlled paths.
A04 Insecure Design
Rate-limit authentication endpoints (login, signup, password reset). 5 attempts per 15 minutes per IP is a sane default.
Don't leak user enumeration: login failures say "invalid credentials", not "user not found" vs "wrong password".
Don't leak existence via response timing. Use constant-time comparison (hash_equals) for token checks.
A05 Security Misconfiguration
Production: display_errors=Off, log_errors=On.
Security headers (the library does this by default):
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains (only on HTTPS)
Validate MIME with finfo, not with the client's Content-Type.
Validate size — both in PHP and at the web server (client_max_body_size in Nginx, LimitRequestBody in Apache, post_max_size/upload_max_filesize in PHP INI).
Generate a new filename — never trust the user's. Use uniqid() or a UUID.
Strip path components from the filename. No ../. The library sanitizes.
Store outside the web root when possible. If inside, configure the server not to execute .php in that directory.
For images: re-encode (load + save) to strip EXIF and any embedded payload. Cap dimensions.
For PDFs/Office docs: scan for macros if the business context warrants (antivirus integration).
Don't serve user uploads from the same origin that sets auth cookies (stored-XSS via HTML uploads).
SQL injection — zero tolerance
Even in scaffolding code, even in admin endpoints, even in tests — always parameterized. The library's Query Builder does this correctly. The escape hatch is:
// GOODConnection::getInstance()->query('SELECT * FROM users WHERE email = ?', [$email]);
// CATASTROPHICConnection::getInstance()->query("SELECT * FROM users WHERE email = '{$email}'");