537 lines
16 KiB
YAML
537 lines
16 KiB
YAML
|
|
# 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
|