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.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
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}'");