# 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
