How to Audit Secret Key Handling and Permissions in Craft CMS to Prevent RCE Attacks

How to Audit Secret Key Handling and Permissions in Craft CMS to Prevent RCE Attacks

Alex Rollin
Alex Rollin
August 6, 2025
Last updated : February 15, 2026
August 6, 2025

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
fi

Log 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.

Share this article