claude-plugin-wordpress-manager 1.5.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +97 -0
  3. package/README.md +27 -13
  4. package/agents/wp-accessibility-auditor.md +206 -0
  5. package/agents/wp-content-strategist.md +18 -0
  6. package/agents/wp-deployment-engineer.md +34 -2
  7. package/agents/wp-performance-optimizer.md +12 -0
  8. package/agents/wp-security-auditor.md +20 -0
  9. package/agents/wp-security-hardener.md +266 -0
  10. package/agents/wp-site-manager.md +14 -0
  11. package/agents/wp-test-engineer.md +207 -0
  12. package/docs/guides/INDEX.md +46 -0
  13. package/docs/guides/wp-blog.md +590 -0
  14. package/docs/guides/wp-design-system.md +976 -0
  15. package/docs/guides/wp-ecommerce.md +786 -0
  16. package/docs/guides/wp-landing-page.md +762 -0
  17. package/docs/guides/wp-portfolio.md +713 -0
  18. package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
  19. package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
  20. package/package.json +2 -2
  21. package/skills/wordpress-router/references/decision-tree.md +12 -2
  22. package/skills/wp-accessibility/SKILL.md +170 -0
  23. package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
  24. package/skills/wp-accessibility/references/a11y-testing.md +222 -0
  25. package/skills/wp-accessibility/references/block-a11y.md +247 -0
  26. package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
  27. package/skills/wp-accessibility/references/media-a11y.md +254 -0
  28. package/skills/wp-accessibility/references/theme-a11y.md +309 -0
  29. package/skills/wp-audit/SKILL.md +4 -0
  30. package/skills/wp-block-development/SKILL.md +5 -0
  31. package/skills/wp-block-themes/SKILL.md +4 -0
  32. package/skills/wp-e2e-testing/SKILL.md +186 -0
  33. package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
  34. package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
  35. package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
  36. package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
  37. package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
  38. package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
  39. package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
  40. package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
  41. package/skills/wp-headless/SKILL.md +168 -0
  42. package/skills/wp-headless/references/api-layer-choice.md +160 -0
  43. package/skills/wp-headless/references/cors-config.md +245 -0
  44. package/skills/wp-headless/references/frontend-integration.md +331 -0
  45. package/skills/wp-headless/references/headless-auth.md +286 -0
  46. package/skills/wp-headless/references/webhooks.md +277 -0
  47. package/skills/wp-headless/references/wpgraphql.md +331 -0
  48. package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
  49. package/skills/wp-i18n/SKILL.md +170 -0
  50. package/skills/wp-i18n/references/js-i18n.md +201 -0
  51. package/skills/wp-i18n/references/multilingual-setup.md +219 -0
  52. package/skills/wp-i18n/references/php-i18n.md +196 -0
  53. package/skills/wp-i18n/references/rtl-support.md +206 -0
  54. package/skills/wp-i18n/references/translation-workflow.md +178 -0
  55. package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
  56. package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
  57. package/skills/wp-interactivity-api/SKILL.md +4 -0
  58. package/skills/wp-plugin-development/SKILL.md +6 -0
  59. package/skills/wp-rest-api/SKILL.md +4 -0
  60. package/skills/wp-security/SKILL.md +179 -0
  61. package/skills/wp-security/references/api-restriction.md +147 -0
  62. package/skills/wp-security/references/authentication-hardening.md +105 -0
  63. package/skills/wp-security/references/filesystem-hardening.md +105 -0
  64. package/skills/wp-security/references/http-headers.md +105 -0
  65. package/skills/wp-security/references/incident-response.md +144 -0
  66. package/skills/wp-security/references/user-capabilities.md +115 -0
  67. package/skills/wp-security/references/wp-config-security.md +129 -0
  68. package/skills/wp-security/scripts/security_inspect.mjs +393 -0
@@ -0,0 +1,179 @@
1
+ ---
2
+ name: wp-security
3
+ description: "Use when hardening WordPress security: filesystem permissions, HTTP security headers, authentication hardening, REST/XML-RPC API restriction, user capabilities audit, wp-config security constants, and incident response procedures."
4
+ compatibility: "Targets WordPress 6.9+ (PHP 7.2.24+). Filesystem-based agent with bash + node. Some workflows require WP-CLI and SSH access."
5
+ version: 1.0.0
6
+ source: "vinmor/wordpress-manager"
7
+ ---
8
+
9
+ # WP Security
10
+
11
+ ## When to use
12
+
13
+ Use this skill for security hardening and incident response:
14
+
15
+ - Hardening a WordPress installation against common attack vectors
16
+ - Fixing security vulnerabilities found in an audit or scan
17
+ - Responding to a suspected or confirmed site compromise
18
+ - Configuring HTTP security headers (CSP, HSTS, X-Frame-Options)
19
+ - Restricting REST API and XML-RPC exposure
20
+ - Auditing user accounts, roles, and capabilities
21
+ - Securing `wp-config.php` constants and secrets
22
+ - Implementing login protection (brute-force, 2FA)
23
+
24
+ ## Inputs required
25
+
26
+ - **Site access**: SSH/SFTP credentials, WP-CLI availability, or hosting panel
27
+ - **Hosting type**: shared, VPS, managed WordPress, or cloud
28
+ - **Current security posture**: output from `wp-audit` skill or `security_inspect.mjs`
29
+ - **Specific threat or vulnerability**: CVE, audit finding, or incident description
30
+
31
+ ## Procedure
32
+
33
+ ### 0) Security inspection
34
+
35
+ Run the detection script to assess the current security posture:
36
+
37
+ ```bash
38
+ node skills/wp-security/scripts/security_inspect.mjs --cwd=/path/to/wordpress
39
+ ```
40
+
41
+ The script outputs JSON with:
42
+ - `wpConfig` — security-related constants and their values
43
+ - `filesystem` — file permissions and exposed sensitive files
44
+ - `apiExposure` — XML-RPC and REST API exposure
45
+ - `headers` — security headers found in `.htaccess`
46
+ - `riskLevel` — overall risk assessment (low/medium/high/critical)
47
+ - `findings[]` — specific issues with severity and recommended fixes
48
+
49
+ Address findings in priority order: critical → high → medium → low.
50
+
51
+ For a comprehensive initial assessment, run the `wp-audit` skill first. This skill focuses on **remediation** — the procedures to fix issues found during an audit.
52
+
53
+ ### 1) Filesystem hardening
54
+
55
+ Set correct file permissions, prevent unauthorized file modification, and block PHP execution in upload directories.
56
+
57
+ Key actions:
58
+ - Files: `644`, Directories: `755`, wp-config.php: `440` or `400`
59
+ - Add `DISALLOW_FILE_EDIT` to wp-config.php to disable the theme/plugin editor
60
+ - Add `DISALLOW_FILE_MODS` to prevent all file modifications including updates
61
+ - Block PHP execution in `wp-content/uploads/` via `.htaccess` or nginx rules
62
+ - Protect `wp-config.php` and `.htaccess` from direct access
63
+
64
+ Read: `references/filesystem-hardening.md`
65
+
66
+ ### 2) HTTP security headers
67
+
68
+ Configure security headers to prevent clickjacking, XSS, MIME sniffing, and other browser-level attacks.
69
+
70
+ Key headers:
71
+ - `Content-Security-Policy` — control which resources the browser can load
72
+ - `X-Frame-Options: SAMEORIGIN` — prevent clickjacking (WordPress sends this by default)
73
+ - `X-Content-Type-Options: nosniff` — prevent MIME type sniffing
74
+ - `Strict-Transport-Security` — enforce HTTPS connections
75
+ - `Permissions-Policy` — restrict browser feature access
76
+ - `Referrer-Policy: strict-origin-when-cross-origin` — control referrer information
77
+
78
+ Implementation methods: `.htaccess` (Apache), `nginx.conf`, or PHP via `send_headers` action.
79
+
80
+ Read: `references/http-headers.md`
81
+
82
+ ### 3) Authentication hardening
83
+
84
+ Protect the login process against brute-force attacks, credential stuffing, and session hijacking.
85
+
86
+ Key actions:
87
+ - Limit failed login attempts (plugins: Limit Login Attempts Reloaded, or custom `wp_login_failed` hook)
88
+ - Enable two-factor authentication (plugins: Two Factor, WP 2FA)
89
+ - Enforce strong password policies via `user_profile_update_errors` filter
90
+ - Configure session idle timeout and concurrent session limits
91
+ - Use application passwords for REST API access instead of sharing main credentials
92
+ - Block username enumeration via `?author=N` redirects
93
+
94
+ Read: `references/authentication-hardening.md`
95
+
96
+ ### 4) API restriction
97
+
98
+ Reduce the attack surface by restricting or disabling unnecessary API endpoints.
99
+
100
+ Key actions:
101
+ - Disable XML-RPC: `add_filter('xmlrpc_enabled', '__return_false')` plus `.htaccess` block
102
+ - Restrict REST API to authenticated users via `rest_authentication_errors` filter
103
+ - Block user enumeration via `/wp-json/wp/v2/users` endpoint
104
+ - Remove REST API discovery link from HTML `<head>`
105
+ - Rate-limit API endpoints at the server level
106
+
107
+ Read: `references/api-restriction.md`
108
+
109
+ ### 5) User capabilities audit
110
+
111
+ Review and tighten user accounts and permissions following the principle of least privilege.
112
+
113
+ Key actions:
114
+ - Audit all administrator accounts (should be 1-2 maximum)
115
+ - Remove or downgrade dormant accounts (inactive > 90 days)
116
+ - Verify no accounts use weak usernames ("admin", "test", "user")
117
+ - Review custom capabilities and remove unnecessary elevated permissions
118
+ - Use WP-CLI for bulk auditing: `wp user list --role=administrator`
119
+
120
+ Read: `references/user-capabilities.md`
121
+
122
+ ### 6) wp-config.php security constants
123
+
124
+ Secure WordPress configuration with appropriate constants and settings.
125
+
126
+ Key actions:
127
+ - Regenerate security keys and salts (use https://api.wordpress.org/secret-key/1.1/salt/)
128
+ - Change the default table prefix from `wp_` (for new installations)
129
+ - Set `WP_DEBUG` to `false` in production; disable `WP_DEBUG_LOG` and `WP_DEBUG_DISPLAY`
130
+ - Enable `FORCE_SSL_ADMIN` for encrypted admin connections
131
+ - Configure `WP_AUTO_UPDATE_CORE` for automatic security updates
132
+ - Move `wp-config.php` one directory above the web root if hosting allows
133
+
134
+ Read: `references/wp-config-security.md`
135
+
136
+ ### 7) Incident response
137
+
138
+ If the site is suspected or confirmed to be compromised, follow a structured response process.
139
+
140
+ Phases:
141
+ 1. **Containment** — change all passwords, revoke sessions, enable maintenance mode
142
+ 2. **Investigation** — verify core checksums (`wp core verify-checksums`), scan for malware, review access logs, check recently modified files
143
+ 3. **Remediation** — remove malicious code, update all components, reinstall WordPress core, review database for injected content
144
+ 4. **Recovery** — restore from clean backup if available, re-harden the site
145
+ 5. **Post-incident** — document the timeline, update security procedures, notify affected users if data was exposed
146
+
147
+ Read: `references/incident-response.md`
148
+
149
+ ## Verification
150
+
151
+ After hardening, verify the security posture:
152
+
153
+ - Re-run `security_inspect.mjs` and confirm all critical/high findings are resolved
154
+ - Test security headers with https://securityheaders.com
155
+ - Verify file permissions: `find . -type f ! -perm 644` and `find . -type d ! -perm 755`
156
+ - Confirm XML-RPC is disabled: `curl -X POST https://site.com/xmlrpc.php` returns 403 or empty
157
+ - Confirm REST API user enumeration is blocked
158
+ - Test login rate limiting by attempting multiple failed logins
159
+ - Verify `wp-config.php` is not accessible via browser
160
+
161
+ ## Failure modes / debugging
162
+
163
+ - **Headers not applying**: check `.htaccess` is being read (Apache `AllowOverride All`), or nginx config is included and reloaded
164
+ - **CSP breaking admin UI**: WordPress admin requires `unsafe-inline` and `unsafe-eval` for scripts; use a relaxed policy for `/wp-admin/` and a strict one for the frontend
165
+ - **Login lockout**: if rate limiting locks out the admin, access via WP-CLI (`wp user update admin --user_pass=newpassword`) or directly in the database
166
+ - **REST API restriction breaks plugins**: some plugins (Jetpack, WooCommerce) require public REST API access; whitelist specific namespaces
167
+ - **File permission errors after hardening**: ensure the web server user owns the files; use `chown -R www-data:www-data /path/to/wp`
168
+
169
+ ## Escalation
170
+
171
+ - For sites actively under attack: involve the hosting provider's security team
172
+ - For data breaches: follow legal notification requirements (GDPR, state laws)
173
+ - For persistent malware: engage a professional WordPress security service (Sucuri, Wordfence)
174
+ - For complex hosting configurations: consult the server administrator
175
+ - For initial security assessment, use the `wp-audit` skill which provides checklists and scoring
176
+
177
+ ## Recommended Agent
178
+
179
+ For implementing hardening fixes and incident response, use the **`wp-security-hardener`** agent. For read-only security audits, use the **`wp-security-auditor`** agent.
@@ -0,0 +1,147 @@
1
+ # API Restriction
2
+
3
+ Use this file when restricting WordPress API exposure (XML-RPC, REST API).
4
+
5
+ ## Disable XML-RPC
6
+
7
+ XML-RPC is rarely needed and is a common brute-force target.
8
+
9
+ ### Via filter (recommended)
10
+
11
+ ```php
12
+ // Completely disable XML-RPC
13
+ add_filter('xmlrpc_enabled', '__return_false');
14
+
15
+ // Also remove the HTTP header advertising it
16
+ add_filter('wp_headers', function($headers) {
17
+ unset($headers['X-Pingback']);
18
+ return $headers;
19
+ });
20
+ ```
21
+
22
+ ### Via .htaccess (blocks at server level)
23
+
24
+ ```apache
25
+ <Files xmlrpc.php>
26
+ order deny,allow
27
+ deny from all
28
+ </Files>
29
+ ```
30
+
31
+ ### Via nginx
32
+
33
+ ```nginx
34
+ location = /xmlrpc.php {
35
+ deny all;
36
+ }
37
+ ```
38
+
39
+ ## Restrict REST API to authenticated users
40
+
41
+ ```php
42
+ add_filter('rest_authentication_errors', function($result) {
43
+ if (true === $result || is_wp_error($result)) {
44
+ return $result;
45
+ }
46
+ if (!is_user_logged_in()) {
47
+ return new WP_Error(
48
+ 'rest_not_logged_in',
49
+ __('You are not currently logged in.'),
50
+ ['status' => 401]
51
+ );
52
+ }
53
+ return $result;
54
+ });
55
+ ```
56
+
57
+ **Warning**: this breaks any public-facing REST API usage. Whitelist specific namespaces if needed:
58
+
59
+ ```php
60
+ add_filter('rest_authentication_errors', function($result) {
61
+ if (true === $result || is_wp_error($result)) {
62
+ return $result;
63
+ }
64
+ $request_uri = $_SERVER['REQUEST_URI'] ?? '';
65
+ // Allow public access to specific namespaces
66
+ $public = ['/wp-json/wp/v2/posts', '/wp-json/wp/v2/pages', '/wp-json/oembed/'];
67
+ foreach ($public as $path) {
68
+ if (str_contains($request_uri, $path)) {
69
+ return $result;
70
+ }
71
+ }
72
+ if (!is_user_logged_in()) {
73
+ return new WP_Error('rest_not_logged_in', 'Authentication required.', ['status' => 401]);
74
+ }
75
+ return $result;
76
+ });
77
+ ```
78
+
79
+ ## Block user enumeration via REST
80
+
81
+ ```php
82
+ add_filter('rest_endpoints', function($endpoints) {
83
+ if (isset($endpoints['/wp/v2/users'])) {
84
+ unset($endpoints['/wp/v2/users']);
85
+ }
86
+ if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) {
87
+ unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
88
+ }
89
+ return $endpoints;
90
+ });
91
+ ```
92
+
93
+ ## Remove REST API discovery links
94
+
95
+ ```php
96
+ // Remove from HTML <head>
97
+ remove_action('wp_head', 'rest_output_link_wp_head');
98
+ // Remove from HTTP headers
99
+ remove_action('template_redirect', 'rest_output_link_header', 11);
100
+ ```
101
+
102
+ ## Disable file editing and modifications
103
+
104
+ ```php
105
+ // Disable theme/plugin editor in admin
106
+ define('DISALLOW_FILE_EDIT', true);
107
+
108
+ // Disable all file modifications (includes updates)
109
+ define('DISALLOW_FILE_MODS', true);
110
+ ```
111
+
112
+ ## Rate limiting
113
+
114
+ Server-level rate limiting is more effective than PHP-based:
115
+
116
+ ### Nginx
117
+ ```nginx
118
+ limit_req_zone $binary_remote_addr zone=wp_api:10m rate=10r/s;
119
+ location /wp-json/ {
120
+ limit_req zone=wp_api burst=20 nodelay;
121
+ # ... rest of config
122
+ }
123
+ ```
124
+
125
+ ### Apache (mod_ratelimit)
126
+ ```apache
127
+ <Location /wp-json/>
128
+ SetOutputFilter RATE_LIMIT
129
+ SetEnv rate-limit 10
130
+ </Location>
131
+ ```
132
+
133
+ ## Verification
134
+
135
+ ```bash
136
+ # Test XML-RPC is disabled
137
+ curl -X POST https://site.com/xmlrpc.php
138
+ # Should return 403 or connection refused
139
+
140
+ # Test REST API user enumeration
141
+ curl https://site.com/wp-json/wp/v2/users
142
+ # Should return 401 or 404
143
+
144
+ # Test author enumeration
145
+ curl -s -o /dev/null -w "%{redirect_url}" "https://site.com/?author=1"
146
+ # Should redirect to homepage, not author archive
147
+ ```
@@ -0,0 +1,105 @@
1
+ # Authentication Hardening
2
+
3
+ Use this file when securing the WordPress login process and user sessions.
4
+
5
+ ## Limit login attempts
6
+
7
+ ### Via plugin (recommended)
8
+ Install "Limit Login Attempts Reloaded" or "WP Limit Login Attempts" for a ready-made solution.
9
+
10
+ ### Custom implementation
11
+
12
+ ```php
13
+ add_action('wp_login_failed', function($username) {
14
+ $ip = $_SERVER['REMOTE_ADDR'];
15
+ $key = 'login_attempts_' . md5($ip);
16
+ $attempts = (int) get_transient($key);
17
+ set_transient($key, $attempts + 1, 15 * MINUTE_IN_SECONDS);
18
+ });
19
+
20
+ add_filter('authenticate', function($user, $username, $password) {
21
+ $ip = $_SERVER['REMOTE_ADDR'];
22
+ $key = 'login_attempts_' . md5($ip);
23
+ $attempts = (int) get_transient($key);
24
+ if ($attempts >= 5) {
25
+ return new WP_Error('too_many_attempts',
26
+ __('Too many login attempts. Please try again in 15 minutes.'));
27
+ }
28
+ return $user;
29
+ }, 30, 3);
30
+ ```
31
+
32
+ ## Two-factor authentication
33
+
34
+ Recommended plugins:
35
+ - **Two Factor** (WordPress.org) — supports TOTP, email, FIDO U2F
36
+ - **WP 2FA** — user-friendly with grace periods
37
+
38
+ Enable for all administrator accounts at minimum.
39
+
40
+ ## Password policies
41
+
42
+ ```php
43
+ add_action('user_profile_update_errors', function($errors, $update, $user) {
44
+ if (strlen($user->user_pass) < 12) {
45
+ $errors->add('weak_password', __('Password must be at least 12 characters.'));
46
+ }
47
+ }, 10, 3);
48
+ ```
49
+
50
+ WordPress 4.3+ includes a password strength meter. Enforce "strong" passwords for privileged roles.
51
+
52
+ ## Session management
53
+
54
+ List active sessions:
55
+ ```bash
56
+ wp user session list <user_id>
57
+ ```
58
+
59
+ Destroy all sessions for a user:
60
+ ```bash
61
+ wp user session destroy <user_id> --all
62
+ ```
63
+
64
+ Limit concurrent sessions in PHP:
65
+ ```php
66
+ add_filter('attach_session_information', function($info) {
67
+ $manager = WP_Session_Tokens::get_instance(get_current_user_id());
68
+ $sessions = $manager->get_all();
69
+ if (count($sessions) > 2) {
70
+ $oldest = array_key_first($sessions);
71
+ $manager->destroy($oldest);
72
+ }
73
+ return $info;
74
+ });
75
+ ```
76
+
77
+ ## Application passwords
78
+
79
+ WordPress 5.6+ includes application passwords for REST API authentication:
80
+ - Created in Users → Profile → Application Passwords
81
+ - Use Basic Auth: `Authorization: Basic base64(username:app_password)`
82
+ - Each app password has its own revocation control
83
+ - Better than sharing main credentials
84
+
85
+ ## Block username enumeration
86
+
87
+ Prevent `?author=1` from revealing usernames:
88
+
89
+ ```php
90
+ add_filter('redirect_canonical', function($redirect, $request) {
91
+ if (preg_match('/\?author=\d+/', $request)) {
92
+ return home_url('/');
93
+ }
94
+ return $redirect;
95
+ }, 10, 2);
96
+ ```
97
+
98
+ Also block via REST API (see `references/api-restriction.md`).
99
+
100
+ ## Login URL considerations
101
+
102
+ Plugins like "WPS Hide Login" can change the login URL. Trade-offs:
103
+ - **Pro**: reduces automated bot traffic to `wp-login.php`
104
+ - **Con**: security through obscurity; doesn't stop targeted attacks
105
+ - **Verdict**: useful as a supplementary measure, not a primary defense
@@ -0,0 +1,105 @@
1
+ # Filesystem Hardening
2
+
3
+ Use this file when securing WordPress file and directory permissions.
4
+
5
+ ## Standard permissions
6
+
7
+ | Target | Permission | Meaning |
8
+ |--------|-----------|---------|
9
+ | Files (general) | `644` | Owner read/write, group+others read |
10
+ | Directories | `755` | Owner full, group+others read/execute |
11
+ | `wp-config.php` | `440` or `400` | Owner read only (most secure) |
12
+ | `.htaccess` | `644` | Owner read/write, others read |
13
+
14
+ Set recursively:
15
+ ```bash
16
+ find /var/www/html -type f -exec chmod 644 {} \;
17
+ find /var/www/html -type d -exec chmod 755 {} \;
18
+ chmod 440 /var/www/html/wp-config.php
19
+ ```
20
+
21
+ ## Ownership
22
+
23
+ The web server user (usually `www-data` on Debian/Ubuntu, `nginx` on RHEL) should own files:
24
+
25
+ ```bash
26
+ chown -R www-data:www-data /var/www/html
27
+ ```
28
+
29
+ On shared hosting, use your user account and ensure the web server can read files.
30
+
31
+ ## Disable file editing
32
+
33
+ Add to `wp-config.php` to disable the theme/plugin editor:
34
+
35
+ ```php
36
+ define('DISALLOW_FILE_EDIT', true);
37
+ ```
38
+
39
+ To also prevent plugin/theme installation and updates via admin:
40
+
41
+ ```php
42
+ define('DISALLOW_FILE_MODS', true);
43
+ ```
44
+
45
+ ## Block PHP execution in uploads
46
+
47
+ Uploads directory should never execute PHP. Create `wp-content/uploads/.htaccess`:
48
+
49
+ ```apache
50
+ # Block PHP execution
51
+ <Files *.php>
52
+ deny from all
53
+ </Files>
54
+ ```
55
+
56
+ For nginx:
57
+ ```nginx
58
+ location ~* /wp-content/uploads/.*\.php$ {
59
+ deny all;
60
+ }
61
+ ```
62
+
63
+ ## Protect sensitive files
64
+
65
+ In the root `.htaccess`:
66
+
67
+ ```apache
68
+ # Protect wp-config.php
69
+ <Files wp-config.php>
70
+ order allow,deny
71
+ deny from all
72
+ </Files>
73
+
74
+ # Protect .htaccess itself
75
+ <Files .htaccess>
76
+ order allow,deny
77
+ deny from all
78
+ </Files>
79
+
80
+ # Block access to wp-includes
81
+ <IfModule mod_rewrite.c>
82
+ RewriteEngine On
83
+ RewriteBase /
84
+ RewriteRule ^wp-admin/includes/ - [F,L]
85
+ RewriteRule !^wp-includes/ - [S=3]
86
+ RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
87
+ RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
88
+ RewriteRule ^wp-includes/theme-compat/ - [F,L]
89
+ </IfModule>
90
+ ```
91
+
92
+ ## Verification
93
+
94
+ ```bash
95
+ # Find files with wrong permissions
96
+ find /var/www/html -type f ! -perm 644 -ls
97
+ find /var/www/html -type d ! -perm 755 -ls
98
+
99
+ # Check wp-config permissions
100
+ stat -c '%a %n' /var/www/html/wp-config.php
101
+
102
+ # Verify uploads PHP block
103
+ curl -s -o /dev/null -w "%{http_code}" https://site.com/wp-content/uploads/test.php
104
+ # Should return 403
105
+ ```
@@ -0,0 +1,105 @@
1
+ # HTTP Security Headers
2
+
3
+ Use this file when configuring HTTP security headers for a WordPress site.
4
+
5
+ ## Recommended headers
6
+
7
+ ### Content-Security-Policy
8
+
9
+ WordPress admin requires `unsafe-inline` and `unsafe-eval`. Use a relaxed policy for admin, strict for frontend:
10
+
11
+ ```apache
12
+ # Frontend (strict)
13
+ Header set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-ancestors 'self'"
14
+ ```
15
+
16
+ For sites using inline scripts/styles (common with plugins):
17
+ ```apache
18
+ Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data: https:; frame-ancestors 'self'"
19
+ ```
20
+
21
+ ### X-Frame-Options
22
+
23
+ Prevents clickjacking. WordPress sends this by default via `send_frame_options_header()`:
24
+
25
+ ```apache
26
+ Header always set X-Frame-Options "SAMEORIGIN"
27
+ ```
28
+
29
+ ### X-Content-Type-Options
30
+
31
+ Prevents MIME type sniffing:
32
+
33
+ ```apache
34
+ Header always set X-Content-Type-Options "nosniff"
35
+ ```
36
+
37
+ ### Strict-Transport-Security (HSTS)
38
+
39
+ Enforces HTTPS. Only enable if SSL is fully configured:
40
+
41
+ ```apache
42
+ Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
43
+ ```
44
+
45
+ ### Permissions-Policy
46
+
47
+ Restricts browser features:
48
+
49
+ ```apache
50
+ Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
51
+ ```
52
+
53
+ ### Referrer-Policy
54
+
55
+ Controls referrer information:
56
+
57
+ ```apache
58
+ Header always set Referrer-Policy "strict-origin-when-cross-origin"
59
+ ```
60
+
61
+ ## Implementation methods
62
+
63
+ ### Apache (.htaccess)
64
+
65
+ ```apache
66
+ <IfModule mod_headers.c>
67
+ Header always set X-Frame-Options "SAMEORIGIN"
68
+ Header always set X-Content-Type-Options "nosniff"
69
+ Header always set Referrer-Policy "strict-origin-when-cross-origin"
70
+ Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
71
+ Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
72
+ </IfModule>
73
+ ```
74
+
75
+ ### Nginx
76
+
77
+ ```nginx
78
+ add_header X-Frame-Options "SAMEORIGIN" always;
79
+ add_header X-Content-Type-Options "nosniff" always;
80
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
81
+ add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
82
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
83
+ ```
84
+
85
+ ### PHP (via WordPress hook)
86
+
87
+ ```php
88
+ add_action('send_headers', function() {
89
+ header('X-Content-Type-Options: nosniff');
90
+ header('Referrer-Policy: strict-origin-when-cross-origin');
91
+ header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
92
+ });
93
+ ```
94
+
95
+ ## Testing
96
+
97
+ - Use https://securityheaders.com to scan headers
98
+ - Use browser DevTools → Network tab → click request → Headers
99
+ - Use `curl -I https://site.com` to view response headers
100
+
101
+ ## Common issues
102
+
103
+ - **CSP breaks admin**: exclude `/wp-admin/` from strict CSP rules or add `unsafe-inline`/`unsafe-eval`
104
+ - **HSTS lockout**: if SSL breaks, users can't access the site; start with a short `max-age` (3600) and increase gradually
105
+ - **Headers not applying**: verify Apache `mod_headers` is enabled; verify nginx config is included and reloaded