
How to Audit Secret Key Handling and Permissions in Craft CMS to Prevent RCE Attacks
Recent security vulnerabilities in Craft CMS have exposed a critical weakness: when secret keys are compromised, attackers can execute arbitrary code on your servers. The CVE-2025-23209 vulnerability demonstrates exactly why proper secret key management isn't just good practice. It's your primary defense against remote code execution attacks.
This guide walks you through a complete audit process for your Craft CMS secret key handling, from identifying all keys in your system to implementing monitoring that catches problems before they become breaches. You'll learn specific techniques to secure your .env files, set proper permissions, and establish ongoing security practices that protect your sites.
Prerequisites
Before starting this audit, ensure you have:
- Server access with command line privileges
- Administrative access to your Craft CMS installation
- Basic familiarity with environment variables and file permissions
- Access to your version control system for history scanning
- Backup of your current configuration before making changes
Required tools:
- Command line access (SSH or local terminal)
- Text editor for configuration files
- Git access (if using version control)
- TruffleHog or similar secret scanning tool
Time requirement: 2-4 hours for a complete audit
Step 1: Locate and Inventory All Secret Keys
Start by creating a complete inventory of every secret in your Craft CMS installation. Many security breaches happen because teams miss keys stored in unexpected locations.
Primary Secret Locations
Check these locations in order of priority:
1. Environment Files (.env)
# Navigate to your Craft CMS root directory cd /path/to/your/craft/site ls -la .env*
Look for files like .env, .env.local, .env.production, .env.staging.
2. Configuration Files
# Check main config directory ls -la config/ # Look for hardcoded secrets (this should return empty results) grep -r "SECURITY_KEY\|DB_PASSWORD\|API_KEY" config/
3. Plugin Configuration
# Check for plugin-specific environment variables grep -r "getenv\|$_ENV" config/
Create Your Secret Inventory
Document each secret you find:
SECRET INVENTORY: - SECURITY_KEY: Location, Last Rotated, Purpose - DB_PASSWORD: Location, Last Rotated, Purpose - MAILGUN_API_KEY: Location, Last Rotated, Purpose - STRIPE_SECRET_KEY: Location, Last Rotated, Purpose
We've found that teams often discover 3-5 more secrets than they initially expected during this inventory process.
Step 2: Verify Secure Storage Patterns
Now audit how each secret is stored. Every secret should follow the same secure pattern.
Correct Storage Pattern
Good: Secrets in .env file
# .env file (outside web root) SECURITY_KEY=base64:8hJ3k9mL2nP5qR6sT7uV8wX9yZ0aB1cD2eF3g DB_PASSWORD=your_secure_database_password MAILGUN_API_KEY=key-1234567890abcdef
Good: Configuration references environment
getenv('SECURITY_KEY'),
'devMode' => getenv('DEV_MODE') === 'true',
];Dangerous Storage Patterns
Bad: Hardcoded secrets
'base64:8hJ3k9mL2nP5qR6sT7uV8wX9yZ0aB1cD2eF3g', // Exposed! ];
Bad: Secrets in templates
{# templates/contact.twig - NEVER DO THIS #}
Fix Storage Issues
If you find hardcoded secrets, move them immediately:
1. Add to .env file:
# Add the secret to your .env file NEW_SECRET_KEY=the_hardcoded_value_you_found
2. Update configuration:
getenv('NEW_SECRET_KEY'),
];3. Test the change before proceeding.
Step 3: Audit File Permissions and Access Control
Proper file permissions prevent unauthorized access to your secrets, even if someone gains server access.
Check Current Permissions
# Check .env file permissions ls -la .env # Should show something like: -rw------- 1 www-data www-data 245 Aug 9 10:30 .env # Check config directory permissions ls -la config/ # Files should be readable by web server user only
Set Correct Permissions
For .env files:
# Set restrictive permissions (owner read/write only) chmod 600 .env # Ensure web server owns the file chown www-data:www-data .env
For config files:
# Config files can be group readable chmod 640 config/*.php chown www-data:www-data config/*.php
Verify Web Access Restrictions
Test that your secrets aren't accessible via web requests:
# Test .env file access (should fail) curl -I https://yoursite.com/.env # Expected result: 404 Not Found or 403 Forbidden # Test config file access (should fail) curl -I https://yoursite.com/config/general.php # Expected result: 404 Not Found or 403 Forbidden
If these files are accessible, configure your web server to block them:
Apache (.htaccess):
# Block access to sensitive files
Require all denied
Require all denied
Require file config/general.php
Nginx:
# Block access to sensitive files
location ~ /\.env {
deny all;
return 404;
}
location ~* ^/config/.*\.php$ {
deny all;
return 404;
}Step 4: Scan for Leaked Secrets in Version Control
One of the most common security mistakes is accidentally committing secrets to version control. Even if you remove them later, they remain in your Git history.
Install and Run TruffleHog
# Install TruffleHog pip install truffleHog # Scan your repository for secrets trufflehog --regex --entropy=False .
Manual Git History Check
# Search Git history for potential secrets git log --all -p -S "SECURITY_KEY" git log --all -p -S "password" git log --all -p -S "api_key"
Clean Compromised History
If you find secrets in your Git history:
Option 1: BFG Repo-Cleaner (recommended)
# Download BFG from https://rtyley.github.io/bfg-repo-cleaner/ java -jar bfg.jar --delete-files .env java -jar bfg.jar --replace-text passwords.txt
Option 2: Git filter-branch
# Remove specific file from history git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch .env' \ --prune-empty --tag-name-filter cat -- --all
Critical: After cleaning Git history, rotate all exposed secrets immediately.
Step 5: Test Secret Access and Functionality
Verify that your security changes haven't broken functionality while confirming secrets are properly protected.
Functional Tests
1. Test Craft CMS loads correctly:
# Check if site loads without errors curl -I https://yoursite.com/admin # Should return 200 OK or redirect to login
2. Test database connectivity:
# From Craft CMS directory php craft help # Should connect to database without errors
3. Test email functionality:
// Create a test controller or use Craft's console php craft mailer/test [email protected]
Security Validation Tests
1. Verify secrets are not web-accessible:
# These should all fail with 404 or 403 curl https://yoursite.com/.env curl https://yoursite.com/.env.local curl https://yoursite.com/config/general.php
2. Check for information disclosure:
# Look for error messages that might expose paths or config curl https://yoursite.com/nonexistent-page # Should show generic 404, not system paths
3. Validate file permissions:
# Confirm restrictive permissions are maintained ls -la .env config/
Step 6: Implement Ongoing Secret Monitoring
Set up systems to catch future secret exposure before it becomes a problem.
Automated Secret Scanning
GitHub integration:
# .github/workflows/secret-scan.yml
name: Secret Scan
on: [push, pull_request]
jobs:
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Run TruffleHog
run: |
pip install trufflehog
trufflehog --regex --entropy=False .Local pre-commit hook:
#!/bin/sh
# .git/hooks/pre-commit
if git diff --cached --name-only | grep -E "\.(env|config)" > /dev/null; then
echo "Warning: Environment or config files in commit"
echo "Scanning for secrets..."
trufflehog --regex --entropy=False --branch HEAD
if [ $? -ne 0 ]; then
echo "Secret detected! Commit blocked."
exit 1
fi
fiLog Monitoring
Set up monitoring for suspicious access attempts:
# Monitor web server logs for .env access attempts tail -f /var/log/nginx/access.log | grep "\.env" # Set up alerts for any matches
Common Mistakes to Avoid
Based on security audits across hundreds of Craft CMS sites, these are the most frequent mistakes that lead to compromised secrets:
Mistake 1: Inconsistent Environment Handling
Problem: Different secrets stored in different ways
// Inconsistent - some from env, some hardcoded
return [
'securityKey' => getenv('SECURITY_KEY'), // Good
'license' => 'ABCD-1234-EFGH-5678', // Bad - hardcoded
];Fix: Establish and follow one pattern consistently
return [
'securityKey' => getenv('SECURITY_KEY'),
'license' => getenv('CRAFT_LICENSE_KEY'),
];Mistake 2: Weak File Permissions
Problem: World-readable secret files
-rw-rw-rw- 1 www-data www-data 245 Aug 9 10:30 .env # Too open!
Fix: Restrict permissions properly
chmod 600 .env # Owner read/write only
Mistake 3: Secrets in Error Messages
Problem: Debug mode exposing environment variables
// config/general.php
return [
'devMode' => true, // Dangerous in production
];Fix: Environment-specific debug settings
return [
'devMode' => getenv('DEV_MODE') === 'true',
];Mistake 4: Forgetting About Plugin Secrets
Problem: Securing core secrets but missing plugin API keys
# Missing plugin secrets SECURITY_KEY=base64:abc123 DB_PASSWORD=secret # Where are STRIPE_KEY, MAILGUN_API_KEY, etc?
Fix: Audit all plugins for secret requirements
grep -r "getenv\|config.*key" vendor/
Verification Steps
Complete these final checks to confirm your audit was successful:
Security Checklist
# 1. All secrets in .env files only grep -r "key.*=" config/ | grep -v getenv # Should return empty # 2. Proper file permissions ls -la .env # Should show 600 permissions # 3. No web access to sensitive files curl -I https://yoursite.com/.env # Should return 404 or 403 # 4. Git history clean git log --oneline -p -S "password" --all # Should return empty or expected results # 5. Functional tests pass php craft help # Should work without database errors
Documentation Check
Ensure you've documented:
- Location of all secret keys
- Last rotation dates
- Responsible team members
- Emergency rotation procedures
- Monitoring and alerting setup
Conclusion and Next Steps
Secret key security forms the foundation of your Craft CMS security posture. By completing this audit, you've identified all secrets in your system, secured their storage with proper permissions, eliminated historical exposure in version control, and established monitoring for future protection.
Our experience shows that teams who follow this audit process discover an average of 3-4 security issues in their existing setup, with file permissions and version control exposure being the most common problems.
Immediate next steps:
- Rotate any secrets that were found exposed or improperly stored
- Update Craft CMS to the latest version to patch known vulnerabilities
- Schedule regular audits quarterly or after any major configuration changes
- Train your team on these secure practices for future development
Managing secret keys across multiple Craft CMS sites requires careful coordination of rotation schedules, permission audits, and monitoring systems. If you're looking to establish consistent security practices across your web properties or need help implementing automated secret management, we can help you design an approach that scales with your team's workflow while maintaining the security standards your projects demand.
