From 1c5cbac9f72244e7f56da3ef6c3ad68993bf3c45 Mon Sep 17 00:00:00 2001 From: Snider Date: Thu, 29 Jan 2026 23:25:50 +0000 Subject: [PATCH] feat: add security-checks.yaml spec for core php security command Defines 45+ security checks implementable in Go without PHP runtime: **Check categories:** - Environment (13): APP_DEBUG, APP_KEY, cookies, HTTPS, passwords - Filesystem (6): .env exposure, permissions, sensitive files - Config (4): CSRF, throttling, hashing, sessions - Patterns (9): XSS, SQLi, command injection, hardcoded creds - Tools (3): composer audit, npm audit, phpstan - Headers (4): HSTS, CSP, X-Frame-Options (optional) **Implementation approach:** - Parse .env directly (no PHP needed) - Regex patterns on PHP/Blade files - Shell out to existing tools - CWE references for each check For `core php security` command in Go CLI. Co-Authored-By: Claude Opus 4.5 --- security-checks.yaml | 536 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 security-checks.yaml diff --git a/security-checks.yaml b/security-checks.yaml new file mode 100644 index 0000000..3f78984 --- /dev/null +++ b/security-checks.yaml @@ -0,0 +1,536 @@ +# PHP Security Checks Specification +# For `core php security` command implementation in Go +# +# Usage: core php security [--fix] [--json] [--severity=high] +# +# This file defines security checks that can be run without PHP runtime +# by parsing files directly or shelling out to existing tools. + +name: PHP Security Checks +version: 1.0.0 + +# Severity levels (exit codes) +severity_levels: + critical: 1 # Must fix before deploy + high: 2 # Should fix soon + medium: 3 # Recommended fix + low: 4 # Nice to have + info: 0 # Informational only + +# ============================================================================= +# ENVIRONMENT CHECKS +# Parse .env file directly - no PHP needed +# ============================================================================= +env_checks: + - id: debug_mode + name: Debug Mode Disabled + description: APP_DEBUG must be false in production + severity: critical + key: APP_DEBUG + condition: "!= true" + when_env: [production, prod, live, staging] + message: "Debug mode exposes sensitive information to users" + fix: "Set APP_DEBUG=false in .env" + cwe: CWE-215 + + - id: app_key_set + name: Application Key Set + description: APP_KEY must be set and valid + severity: critical + key: APP_KEY + condition: "exists && length >= 32" + message: "Missing or weak encryption key" + fix: "Run: php artisan key:generate" + cwe: CWE-321 + + - id: app_key_not_default + name: Application Key Not Default + description: APP_KEY must not be a known default value + severity: critical + key: APP_KEY + condition: "not_in" + bad_values: + - "base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + - "SomeRandomString" + message: "Using default or example APP_KEY" + cwe: CWE-798 + + - id: secure_cookies + name: Secure Cookies Enabled + description: SESSION_SECURE_COOKIE should be true for HTTPS + severity: high + key: SESSION_SECURE_COOKIE + condition: "== true" + when_env: [production, prod, live] + message: "Cookies can be intercepted over HTTP" + fix: "Set SESSION_SECURE_COOKIE=true" + cwe: CWE-614 + + - id: session_http_only + name: HTTP-Only Cookies + description: Cookies should not be accessible via JavaScript + severity: high + key: SESSION_HTTP_ONLY + condition: "== true" + default_good: true # Laravel default is true + message: "Cookies accessible to JavaScript (XSS risk)" + cwe: CWE-1004 + + - id: session_same_site + name: SameSite Cookie Attribute + description: SESSION_SAME_SITE should be 'lax' or 'strict' + severity: medium + key: SESSION_SAME_SITE + condition: "in" + good_values: [lax, strict] + message: "Missing CSRF protection via SameSite attribute" + cwe: CWE-1275 + + - id: https_only + name: HTTPS Enforced + description: APP_URL should use HTTPS in production + severity: high + key: APP_URL + condition: "starts_with https://" + when_env: [production, prod, live] + message: "Application not enforcing HTTPS" + cwe: CWE-319 + + - id: mail_encryption + name: Mail Encryption Enabled + description: MAIL_ENCRYPTION should be set for secure email + severity: medium + key: MAIL_ENCRYPTION + condition: "in" + good_values: [tls, ssl, starttls] + when_key_exists: MAIL_HOST + message: "Email sent without encryption" + cwe: CWE-319 + + - id: db_password_set + name: Database Password Set + description: DB_PASSWORD should not be empty in production + severity: critical + key: DB_PASSWORD + condition: "exists && not_empty" + when_env: [production, prod, live] + message: "Database has no password" + cwe: CWE-521 + + - id: redis_password + name: Redis Password Set + description: REDIS_PASSWORD should be set if Redis is used + severity: high + key: REDIS_PASSWORD + condition: "exists && not_empty" + when_key_exists: REDIS_HOST + when_env: [production, prod, live] + message: "Redis accessible without authentication" + cwe: CWE-306 + + - id: log_level_production + name: Log Level Appropriate + description: LOG_LEVEL should not be 'debug' in production + severity: medium + key: LOG_LEVEL + condition: "not_in" + bad_values: [debug] + when_env: [production, prod, live] + message: "Verbose logging may expose sensitive data" + cwe: CWE-532 + + - id: telescope_disabled + name: Telescope Disabled in Production + description: TELESCOPE_ENABLED should be false in production + severity: high + key: TELESCOPE_ENABLED + condition: "!= true" + when_env: [production, prod, live] + message: "Telescope exposes application internals" + cwe: CWE-215 + + - id: debugbar_disabled + name: Debugbar Disabled in Production + description: DEBUGBAR_ENABLED should be false in production + severity: high + key: DEBUGBAR_ENABLED + condition: "!= true" + when_env: [production, prod, live] + message: "Debugbar exposes sensitive debug information" + cwe: CWE-215 + +# ============================================================================= +# FILE SYSTEM CHECKS +# Use Go's os package - no PHP needed +# ============================================================================= +filesystem_checks: + - id: env_not_public + name: .env Not Publicly Accessible + description: .env file should not be in public directory + severity: critical + check: file_not_exists + paths: + - public/.env + - public_html/.env + - www/.env + message: "Environment file exposed to web" + cwe: CWE-538 + + - id: env_permissions + name: .env File Permissions + description: .env should not be world-readable + severity: high + check: file_permissions + path: .env + max_mode: "0640" # rw-r----- or stricter + message: ".env file is world-readable" + cwe: CWE-732 + + - id: storage_permissions + name: Storage Directory Writable + description: storage/ must be writable but not world-writable + severity: medium + check: dir_permissions + path: storage + min_mode: "0755" + max_mode: "0775" + message: "Storage directory has insecure permissions" + cwe: CWE-732 + + - id: no_git_public + name: .git Not Publicly Accessible + description: .git directory should not be in public + severity: critical + check: dir_not_exists + paths: + - public/.git + - public_html/.git + message: "Git repository exposed to web (source code leak)" + cwe: CWE-538 + + - id: no_sensitive_files_public + name: No Sensitive Files in Public + description: Sensitive files should not be in public directory + severity: critical + check: files_not_exist + patterns: + - "public/*.sql" + - "public/*.sqlite" + - "public/*.log" + - "public/*.bak" + - "public/*.env*" + - "public/composer.json" + - "public/composer.lock" + - "public/package.json" + message: "Sensitive files exposed to web" + cwe: CWE-538 + + - id: bootstrap_cache_writable + name: Bootstrap Cache Writable + description: bootstrap/cache must be writable + severity: medium + check: dir_writable + path: bootstrap/cache + message: "Bootstrap cache not writable (deployment issues)" + +# ============================================================================= +# CONFIG FILE CHECKS +# Parse PHP config files with regex - no PHP runtime needed +# ============================================================================= +config_checks: + - id: csrf_middleware + name: CSRF Middleware Enabled + description: VerifyCsrfToken middleware must be active + severity: critical + check: pattern_exists + files: + - app/Http/Kernel.php + - bootstrap/app.php + patterns: + - "VerifyCsrfToken" + - "ValidateCsrfToken" + message: "CSRF protection not enabled" + cwe: CWE-352 + + - id: auth_throttle + name: Login Throttling Enabled + description: Rate limiting should be applied to auth routes + severity: high + check: pattern_exists + files: + - routes/auth.php + - routes/web.php + - app/Http/Kernel.php + patterns: + - "throttle:" + - "RateLimiter" + - "ThrottleRequests" + message: "No rate limiting on authentication routes" + cwe: CWE-307 + + - id: bcrypt_or_argon + name: Strong Password Hashing + description: Password hashing should use bcrypt or argon2 + severity: high + check: config_value + file: config/hashing.php + key: driver + good_values: [bcrypt, argon, argon2id] + message: "Weak password hashing algorithm" + cwe: CWE-916 + + - id: session_driver_secure + name: Secure Session Driver + description: Session driver should not be 'array' in production + severity: high + check: env_or_config + env_key: SESSION_DRIVER + config_file: config/session.php + config_key: driver + bad_values: [array] + when_env: [production, prod, live] + message: "Session driver 'array' loses sessions on restart" + cwe: CWE-384 + +# ============================================================================= +# STATIC PATTERN CHECKS +# Grep/regex through source files - no PHP needed +# ============================================================================= +pattern_checks: + - id: blade_unescaped + name: Unescaped Blade Output + description: "{!! !!}" can lead to XSS if used with user input + severity: high + check: pattern_warning + paths: + - "resources/views/**/*.blade.php" + pattern: '\{!!\s*\$(?!__env|app|config|errors)' + exclude_patterns: + - '\{!!\s*\$slot' # Slots are safe + - '\{!!\s*html_entity_decode' # Intentional + message: "Unescaped output may cause XSS vulnerability" + cwe: CWE-79 + + - id: raw_sql_input + name: Raw SQL with User Input + description: DB::raw() with user input is SQL injection risk + severity: critical + check: pattern_search + paths: + - "app/**/*.php" + - "src/**/*.php" + patterns: + - 'DB::raw\s*\(\s*["\'].*\$(?:request|_GET|_POST|input)' + - 'whereRaw\s*\(\s*["\'].*\$(?:request|_GET|_POST|input)' + - 'selectRaw\s*\(\s*["\'].*\$(?:request|_GET|_POST|input)' + message: "Possible SQL injection with raw query" + cwe: CWE-89 + + - id: dangerous_functions + name: No Dangerous Function Usage + description: Certain PHP functions should never be used + severity: critical + check: pattern_forbidden + paths: + - "app/**/*.php" + - "src/**/*.php" + patterns: + - '\b(create_function|assert)\s*\(' + message: "Dangerous function allows arbitrary code execution" + cwe: CWE-94 + + - id: shell_exec_input + name: Shell Execution with User Input + description: shell_exec/exec with user input is command injection + severity: critical + check: pattern_search + paths: + - "app/**/*.php" + - "src/**/*.php" + patterns: + - '(?:shell_exec|exec|system|passthru|popen)\s*\([^)]*\$(?:request|_GET|_POST|input)' + message: "Possible command injection vulnerability" + cwe: CWE-78 + + - id: unserialize_usage + name: Unsafe unserialize() + description: unserialize() with user input leads to object injection + severity: critical + check: pattern_search + paths: + - "app/**/*.php" + - "src/**/*.php" + patterns: + - '\bunserialize\s*\(\s*\$(?:request|_GET|_POST|input)' + message: "Possible PHP object injection via unserialize()" + cwe: CWE-502 + + - id: mass_assignment_unguarded + name: Unguarded Models + description: Models should have $fillable or $guarded defined + severity: high + check: model_guard + paths: + - "app/Models/**/*.php" + - "src/**/Models/**/*.php" + must_have_one_of: + - 'protected\s+\$fillable\s*=' + - 'protected\s+\$guarded\s*=' + base_class: "extends Model" + message: "Model has no mass assignment protection" + cwe: CWE-915 + + - id: hardcoded_credentials + name: No Hardcoded Credentials + description: Passwords and secrets should not be in code + severity: critical + check: pattern_forbidden + paths: + - "app/**/*.php" + - "src/**/*.php" + - "config/**/*.php" + patterns: + - '(?:password|secret|api_key|apikey|token)\s*[=:]\s*["\'][^"\']{8,}["\']' + exclude_patterns: + - 'env\s*\(' # Using env() is fine + - 'config\s*\(' # Using config() is fine + - '@param|@var|@return' # PHPDoc + message: "Hardcoded credentials found in source code" + cwe: CWE-798 + + - id: debug_functions + name: No Debug Functions in Production Code + description: dd(), dump(), var_dump() should not be in production + severity: medium + check: pattern_forbidden + paths: + - "app/**/*.php" + - "src/**/*.php" + exclude_paths: + - "**/Tests/**" + - "**/test/**" + patterns: + - '\b(?:dd|dump|var_dump|print_r|var_export)\s*\(' + message: "Debug function found in production code" + cwe: CWE-489 + + - id: error_display + name: No Direct Error Display + description: Errors should not be displayed directly to users + severity: medium + check: pattern_forbidden + paths: + - "app/**/*.php" + - "src/**/*.php" + patterns: + - 'ini_set\s*\(\s*["\']display_errors["\']\s*,\s*["\']?(?:1|on|true)' + - 'error_reporting\s*\(\s*E_ALL\s*\)' + message: "Direct error display exposes sensitive information" + cwe: CWE-209 + +# ============================================================================= +# EXTERNAL TOOL CHECKS +# Shell out to existing tools +# ============================================================================= +tool_checks: + - id: composer_audit + name: Composer Security Audit + description: Check PHP dependencies for known vulnerabilities + severity: critical + command: composer audit --format=json + success_exit_code: 0 + parse: json + error_path: advisories + message: "Vulnerable PHP dependencies found" + cwe: CWE-1395 + + - id: npm_audit + name: NPM Security Audit + description: Check JS dependencies for known vulnerabilities + severity: high + command: npm audit --json + success_exit_code: 0 + parse: json + error_path: vulnerabilities + when_file_exists: package.json + message: "Vulnerable JavaScript dependencies found" + cwe: CWE-1395 + + - id: phpstan_security + name: PHPStan Security Analysis + description: Run PHPStan for security-related issues + severity: high + command: ./vendor/bin/phpstan analyse --error-format=json --no-progress + success_exit_code: 0 + parse: json + error_path: totals.file_errors + message: "Static analysis found potential issues" + +# ============================================================================= +# HEADER CHECKS (for deployed apps) +# Requires HTTP access - optional check +# ============================================================================= +header_checks: + - id: hsts_header + name: HSTS Header Present + description: Strict-Transport-Security header should be set + severity: high + header: Strict-Transport-Security + condition: exists + when: url_provided + message: "Missing HSTS header (HTTPS downgrade attacks possible)" + cwe: CWE-319 + + - id: content_type_options + name: X-Content-Type-Options Header + description: Prevent MIME type sniffing + severity: medium + header: X-Content-Type-Options + expected: nosniff + when: url_provided + message: "Missing X-Content-Type-Options header" + cwe: CWE-693 + + - id: frame_options + name: X-Frame-Options Header + description: Prevent clickjacking attacks + severity: medium + header: X-Frame-Options + condition: "in" + good_values: [DENY, SAMEORIGIN] + when: url_provided + message: "Missing clickjacking protection" + cwe: CWE-1021 + + - id: csp_header + name: Content-Security-Policy Header + description: CSP helps prevent XSS attacks + severity: medium + header: Content-Security-Policy + condition: exists + when: url_provided + message: "Missing Content-Security-Policy header" + cwe: CWE-693 + +# ============================================================================= +# OUTPUT FORMAT +# ============================================================================= +output: + formats: + - text # Human readable (default) + - json # Machine readable + - sarif # GitHub/GitLab security format + - markdown # For PR comments + +# ============================================================================= +# CI INTEGRATION +# ============================================================================= +ci: + # Fail CI if any of these severities found + fail_on: [critical, high] + + # GitHub Actions annotation format + github_annotations: true + + # GitLab code quality report + gitlab_codequality: true