Security & Hardening
warning
Most WordPress vulnerabilities come from custom code. Treat child themes like production codebases with reviews and rollback.
Security & Hardening Explained
| Security area | What to do | Example |
|---|---|---|
| Output escaping | Escape anything dynamic | esc_html(), esc_url() |
| Input sanitization | Sanitize user input | sanitize_text_field() |
| Nonces | Protect state-changing actions | wp_nonce_field() |
| Secrets hygiene | Never commit keys | No .env in theme repo |
| Logs | Use server + WP logs | /usr/local/lsws/logs/error.log |
Why It Matters
- Child themes often add shortcodes and hooks that output content.
- That output is a common XSS vector if unescaped.
- You also need safe rollback and audit trails.
How It Works
Secure WordPress code follows a simple rule: sanitize input, escape output, and validate permissions before doing anything destructive.
Practical Walkthrough
Step 1: Audit Your Child Theme for Risky Patterns
audit-child-theme-security.sh
cd /var/www/html/wp-content/themes/generatepress-child
grep -R "echo \$\|\$_GET\|\$_POST\|eval\(" -n . | head -n 80 || true
Step 2: Check File Permissions
check-child-theme-perms.sh
cd /var/www/html
stat wp-content/themes/generatepress-child
ls -lah wp-content/themes/generatepress-child | head -n 40
Step 3: Know Where Logs Are
check-common-log-locations.sh
ls -lah /usr/local/lsws/logs 2>/dev/null | head -n 40 || true
ls -lah wp-content 2>/dev/null | head -n 40 || true
Step 4: Add a Basic WP Hardening Constant (Optional)
In wp-config.php (server-managed):
wp-config.php
define( 'DISALLOW_FILE_EDIT', true );
Optional (if your stack allows it):
wp-config.php
define( 'DISALLOW_FILE_MODS', false );
Practical Examples
Example 1: A Safe Shortcode
wp-content/themes/generatepress-child/functions.php
<?php
add_shortcode( 'safe_notice', function( $atts ) {
$atts = shortcode_atts( array( 'text' => '' ), $atts );
return '<div class="notice">' . esc_html( (string) $atts['text'] ) . '</div>';
} );
Example 2: Avoid Secrets in Theme Code
Bad:
- API keys in
functions.php - private URLs in templates
Good:
- Use environment variables (server-level)
- Use WP config constants (not committed)
Example 3: Nonce Pattern (Admin/Form Work)
If you add forms or admin actions, use nonces and capability checks.
Example 4: Escape URLs
wp-content/themes/generatepress-child/inc/hooks.php
<?php
add_action( 'generate_footer', function() {
echo '<a href="' . esc_url( home_url( '/' ) ) . '">Home</a>';
} );
Best Practices
| Practice | Why |
|---|---|
| Escape output by default | Prevents XSS |
| Sanitize all input | Prevents injection |
| Review code changes | Reduces risk |
| Keep secrets out of repos | Prevents leaks |
| Patch quickly | Reduce exposure time |
| Keep a staging environment | Test updates safely |
| Disable file editing in admin | Reduces attack surface |
| Review third-party scripts | Common compromise vector |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Unexpected markup injection | Unescaped input | Audit shortcodes and hook callbacks |
| White screen after change | Fatal error | Check error logs and roll back |
| Security plugin flags theme | Suspicious patterns | Remove risky functions and refactor |
| Random redirects | Compromised plugin/theme | Disable plugins on staging and audit logs |
Hands-On
- Search your child theme for
$_GETand$_POSTusage. - Replace one unsafe echo with
esc_html(). - Add a short note in README about secrets policy.
- Add
DISALLOW_FILE_EDITon staging and confirm wp-admin cannot edit files. - Add one new shortcode and ensure it escapes output.
Quick Reference
security-cheatsheet.sh
cd /var/www/html/wp-content/themes/generatepress-child
grep -R "\$_GET\|\$_POST\|eval\(" -n . | head
What's Next
- Next: Multisite & Reusability
- Related: Versioning & Deployment