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.
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +97 -0
- package/README.md +27 -13
- package/agents/wp-accessibility-auditor.md +206 -0
- package/agents/wp-content-strategist.md +18 -0
- package/agents/wp-deployment-engineer.md +34 -2
- package/agents/wp-performance-optimizer.md +12 -0
- package/agents/wp-security-auditor.md +20 -0
- package/agents/wp-security-hardener.md +266 -0
- package/agents/wp-site-manager.md +14 -0
- package/agents/wp-test-engineer.md +207 -0
- package/docs/guides/INDEX.md +46 -0
- package/docs/guides/wp-blog.md +590 -0
- package/docs/guides/wp-design-system.md +976 -0
- package/docs/guides/wp-ecommerce.md +786 -0
- package/docs/guides/wp-landing-page.md +762 -0
- package/docs/guides/wp-portfolio.md +713 -0
- package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
- package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
- package/package.json +2 -2
- package/skills/wordpress-router/references/decision-tree.md +12 -2
- package/skills/wp-accessibility/SKILL.md +170 -0
- package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
- package/skills/wp-accessibility/references/a11y-testing.md +222 -0
- package/skills/wp-accessibility/references/block-a11y.md +247 -0
- package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
- package/skills/wp-accessibility/references/media-a11y.md +254 -0
- package/skills/wp-accessibility/references/theme-a11y.md +309 -0
- package/skills/wp-audit/SKILL.md +4 -0
- package/skills/wp-block-development/SKILL.md +5 -0
- package/skills/wp-block-themes/SKILL.md +4 -0
- package/skills/wp-e2e-testing/SKILL.md +186 -0
- package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
- package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
- package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
- package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
- package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
- package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
- package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
- package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
- package/skills/wp-headless/SKILL.md +168 -0
- package/skills/wp-headless/references/api-layer-choice.md +160 -0
- package/skills/wp-headless/references/cors-config.md +245 -0
- package/skills/wp-headless/references/frontend-integration.md +331 -0
- package/skills/wp-headless/references/headless-auth.md +286 -0
- package/skills/wp-headless/references/webhooks.md +277 -0
- package/skills/wp-headless/references/wpgraphql.md +331 -0
- package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
- package/skills/wp-i18n/SKILL.md +170 -0
- package/skills/wp-i18n/references/js-i18n.md +201 -0
- package/skills/wp-i18n/references/multilingual-setup.md +219 -0
- package/skills/wp-i18n/references/php-i18n.md +196 -0
- package/skills/wp-i18n/references/rtl-support.md +206 -0
- package/skills/wp-i18n/references/translation-workflow.md +178 -0
- package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
- package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
- package/skills/wp-interactivity-api/SKILL.md +4 -0
- package/skills/wp-plugin-development/SKILL.md +6 -0
- package/skills/wp-rest-api/SKILL.md +4 -0
- package/skills/wp-security/SKILL.md +179 -0
- package/skills/wp-security/references/api-restriction.md +147 -0
- package/skills/wp-security/references/authentication-hardening.md +105 -0
- package/skills/wp-security/references/filesystem-hardening.md +105 -0
- package/skills/wp-security/references/http-headers.md +105 -0
- package/skills/wp-security/references/incident-response.md +144 -0
- package/skills/wp-security/references/user-capabilities.md +115 -0
- package/skills/wp-security/references/wp-config-security.md +129 -0
- package/skills/wp-security/scripts/security_inspect.mjs +393 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* security_inspect.mjs — Detect WordPress security configuration and potential issues.
|
|
3
|
+
*
|
|
4
|
+
* Scans wp-config.php constants, file permissions, debug settings,
|
|
5
|
+
* .htaccess hardening, and security plugin presence.
|
|
6
|
+
* Outputs a JSON report to stdout.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node security_inspect.mjs [--cwd=/path/to/wordpress]
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 — WordPress installation found and scanned
|
|
13
|
+
* 1 — no WordPress installation found
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import process from "node:process";
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
|
|
21
|
+
const TOOL_VERSION = "1.0.0";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function statSafe(p) {
|
|
28
|
+
try {
|
|
29
|
+
return fs.statSync(p);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readFileSafe(p) {
|
|
36
|
+
try {
|
|
37
|
+
return fs.readFileSync(p, "utf8");
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readJsonSafe(p) {
|
|
44
|
+
const raw = readFileSafe(p);
|
|
45
|
+
if (!raw) return null;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function execSafe(cmd, cwd, timeoutMs = 5000) {
|
|
54
|
+
try {
|
|
55
|
+
return execSync(cmd, { encoding: "utf8", timeout: timeoutMs, cwd, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Parse --cwd argument
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
function parseCwd() {
|
|
66
|
+
const cwdArg = process.argv.find((a) => a.startsWith("--cwd="));
|
|
67
|
+
return cwdArg ? cwdArg.slice(6) : process.cwd();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Detect WordPress installation
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
function findWpRoot(cwd) {
|
|
75
|
+
// Check common locations
|
|
76
|
+
const candidates = [
|
|
77
|
+
cwd,
|
|
78
|
+
path.join(cwd, "wordpress"),
|
|
79
|
+
path.join(cwd, "wp"),
|
|
80
|
+
path.join(cwd, "public_html"),
|
|
81
|
+
path.join(cwd, "htdocs"),
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const dir of candidates) {
|
|
85
|
+
if (statSafe(path.join(dir, "wp-config.php"))?.isFile()) return dir;
|
|
86
|
+
if (statSafe(path.join(dir, "wp-includes", "version.php"))?.isFile()) return dir;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// wp-config.php one level up (common security practice)
|
|
90
|
+
const parent = path.dirname(cwd);
|
|
91
|
+
if (statSafe(path.join(parent, "wp-config.php"))?.isFile()) {
|
|
92
|
+
return cwd;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Scan wp-config.php
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
function scanWpConfig(wpRoot) {
|
|
103
|
+
const result = {
|
|
104
|
+
found: false,
|
|
105
|
+
location: null,
|
|
106
|
+
constants: {},
|
|
107
|
+
issues: [],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Check current dir and parent
|
|
111
|
+
let configPath = path.join(wpRoot, "wp-config.php");
|
|
112
|
+
if (!statSafe(configPath)?.isFile()) {
|
|
113
|
+
configPath = path.join(path.dirname(wpRoot), "wp-config.php");
|
|
114
|
+
}
|
|
115
|
+
if (!statSafe(configPath)?.isFile()) return result;
|
|
116
|
+
|
|
117
|
+
result.found = true;
|
|
118
|
+
result.location = configPath;
|
|
119
|
+
|
|
120
|
+
const content = readFileSafe(configPath);
|
|
121
|
+
if (!content) return result;
|
|
122
|
+
|
|
123
|
+
// Extract defined constants
|
|
124
|
+
const constantPattern = /define\s*\(\s*['"](\w+)['"]\s*,\s*(.+?)\s*\)/g;
|
|
125
|
+
let match;
|
|
126
|
+
while ((match = constantPattern.exec(content)) !== null) {
|
|
127
|
+
const name = match[1];
|
|
128
|
+
const rawValue = match[2].trim().replace(/['"]/g, "").replace(/\);$/, "");
|
|
129
|
+
result.constants[name] = rawValue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check security constants
|
|
133
|
+
const securityConstants = {
|
|
134
|
+
WP_DEBUG: { safe: "false", risk: "Exposes error details in production" },
|
|
135
|
+
WP_DEBUG_DISPLAY: { safe: "false", risk: "Shows errors to visitors" },
|
|
136
|
+
WP_DEBUG_LOG: { safe: "false", risk: "Log file may be publicly accessible" },
|
|
137
|
+
DISALLOW_FILE_EDIT: { safe: "true", risk: "Theme/plugin editor is enabled" },
|
|
138
|
+
FORCE_SSL_ADMIN: { safe: "true", risk: "Admin not forced to HTTPS" },
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
for (const [name, check] of Object.entries(securityConstants)) {
|
|
142
|
+
const value = result.constants[name];
|
|
143
|
+
if (value !== undefined && value.toLowerCase() !== check.safe) {
|
|
144
|
+
result.issues.push({ constant: name, value, risk: check.risk });
|
|
145
|
+
}
|
|
146
|
+
if (value === undefined && name === "DISALLOW_FILE_EDIT") {
|
|
147
|
+
result.issues.push({ constant: name, value: "not set", risk: "File editor not explicitly disabled" });
|
|
148
|
+
}
|
|
149
|
+
if (value === undefined && name === "FORCE_SSL_ADMIN") {
|
|
150
|
+
result.issues.push({ constant: name, value: "not set", risk: "SSL not forced for admin" });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check security keys for defaults
|
|
155
|
+
const keyNames = ["AUTH_KEY", "SECURE_AUTH_KEY", "LOGGED_IN_KEY", "NONCE_KEY"];
|
|
156
|
+
for (const key of keyNames) {
|
|
157
|
+
const val = result.constants[key];
|
|
158
|
+
if (val && val.includes("put your unique phrase here")) {
|
|
159
|
+
result.issues.push({ constant: key, value: "default", risk: "Security key not changed from default" });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check table prefix
|
|
164
|
+
const prefixMatch = content.match(/\$table_prefix\s*=\s*['"](\w+)['"]/);
|
|
165
|
+
if (prefixMatch) {
|
|
166
|
+
result.constants._table_prefix = prefixMatch[1];
|
|
167
|
+
if (prefixMatch[1] === "wp_") {
|
|
168
|
+
result.issues.push({ constant: "$table_prefix", value: "wp_", risk: "Default table prefix" });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Scan file permissions
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
function scanPermissions(wpRoot) {
|
|
180
|
+
const result = { checks: [], issues: [] };
|
|
181
|
+
|
|
182
|
+
if (process.platform === "win32") {
|
|
183
|
+
result.checks.push({ file: "N/A", note: "Permission checks not available on Windows" });
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const fileChecks = [
|
|
188
|
+
{ path: "wp-config.php", maxMode: 0o440, label: "wp-config.php" },
|
|
189
|
+
{ path: ".htaccess", maxMode: 0o644, label: ".htaccess" },
|
|
190
|
+
{ path: "wp-content", maxMode: 0o755, label: "wp-content/" },
|
|
191
|
+
{ path: "wp-content/uploads", maxMode: 0o755, label: "wp-content/uploads/" },
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
for (const check of fileChecks) {
|
|
195
|
+
const full = path.join(wpRoot, check.path);
|
|
196
|
+
const stat = statSafe(full);
|
|
197
|
+
if (!stat) {
|
|
198
|
+
result.checks.push({ file: check.label, exists: false });
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const mode = stat.mode & 0o777;
|
|
203
|
+
const modeStr = "0" + mode.toString(8);
|
|
204
|
+
const isWorldWritable = (mode & 0o002) !== 0;
|
|
205
|
+
|
|
206
|
+
result.checks.push({
|
|
207
|
+
file: check.label,
|
|
208
|
+
exists: true,
|
|
209
|
+
mode: modeStr,
|
|
210
|
+
worldWritable: isWorldWritable,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (isWorldWritable) {
|
|
214
|
+
result.issues.push({ file: check.label, mode: modeStr, risk: "World-writable" });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (check.label === "wp-config.php" && mode > check.maxMode) {
|
|
218
|
+
result.issues.push({
|
|
219
|
+
file: check.label,
|
|
220
|
+
mode: modeStr,
|
|
221
|
+
recommended: "0" + check.maxMode.toString(8),
|
|
222
|
+
risk: "wp-config.php permissions too open",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check for PHP files in uploads
|
|
228
|
+
const uploadsDir = path.join(wpRoot, "wp-content", "uploads");
|
|
229
|
+
if (statSafe(uploadsDir)?.isDirectory()) {
|
|
230
|
+
const phpInUploads = execSafe(
|
|
231
|
+
`find "${uploadsDir}" -name "*.php" -type f 2>/dev/null | head -5`,
|
|
232
|
+
wpRoot
|
|
233
|
+
);
|
|
234
|
+
if (phpInUploads && phpInUploads.length > 0) {
|
|
235
|
+
result.issues.push({
|
|
236
|
+
file: "wp-content/uploads/",
|
|
237
|
+
risk: "PHP files found in uploads directory",
|
|
238
|
+
files: phpInUploads.split("\n").map((f) => path.relative(wpRoot, f)),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// Scan .htaccess
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
function scanHtaccess(wpRoot) {
|
|
251
|
+
const result = { found: false, hardening: [], missing: [] };
|
|
252
|
+
|
|
253
|
+
const htaccessPath = path.join(wpRoot, ".htaccess");
|
|
254
|
+
const content = readFileSafe(htaccessPath);
|
|
255
|
+
if (!content) return result;
|
|
256
|
+
|
|
257
|
+
result.found = true;
|
|
258
|
+
|
|
259
|
+
const hardeningChecks = [
|
|
260
|
+
{ name: "xmlrpc-blocked", pattern: /xmlrpc\.php/i },
|
|
261
|
+
{ name: "directory-listing-disabled", pattern: /Options\s+-Indexes/i },
|
|
262
|
+
{ name: "wp-config-protected", pattern: /wp-config\.php/i },
|
|
263
|
+
{ name: "server-signature-off", pattern: /ServerSignature\s+Off/i },
|
|
264
|
+
{ name: "file-type-restriction", pattern: /FilesMatch.*\.(php|phtml)/i },
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
for (const check of hardeningChecks) {
|
|
268
|
+
if (check.pattern.test(content)) {
|
|
269
|
+
result.hardening.push(check.name);
|
|
270
|
+
} else {
|
|
271
|
+
result.missing.push(check.name);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Scan security plugins
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
function scanSecurityPlugins(wpRoot) {
|
|
283
|
+
const pluginsDir = path.join(wpRoot, "wp-content", "plugins");
|
|
284
|
+
if (!statSafe(pluginsDir)?.isDirectory()) return { detected: [] };
|
|
285
|
+
|
|
286
|
+
const securityPlugins = [
|
|
287
|
+
{ dir: "wordfence", name: "Wordfence Security" },
|
|
288
|
+
{ dir: "sucuri-scanner", name: "Sucuri Security" },
|
|
289
|
+
{ dir: "ithemes-security-pro", name: "iThemes Security Pro" },
|
|
290
|
+
{ dir: "better-wp-security", name: "iThemes Security" },
|
|
291
|
+
{ dir: "all-in-one-wp-security-and-firewall", name: "All In One WP Security" },
|
|
292
|
+
{ dir: "wp-cerber", name: "WP Cerber Security" },
|
|
293
|
+
{ dir: "limit-login-attempts-reloaded", name: "Limit Login Attempts Reloaded" },
|
|
294
|
+
{ dir: "two-factor", name: "Two Factor Authentication" },
|
|
295
|
+
{ dir: "disable-xml-rpc", name: "Disable XML-RPC" },
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
const detected = [];
|
|
299
|
+
for (const plugin of securityPlugins) {
|
|
300
|
+
if (statSafe(path.join(pluginsDir, plugin.dir))?.isDirectory()) {
|
|
301
|
+
detected.push(plugin.name);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return { detected };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
// Scan HTTP headers (via WP-CLI if available)
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
function scanHeaders(wpRoot) {
|
|
313
|
+
// Only possible if site is running — skip if no WP-CLI
|
|
314
|
+
const wpCli = execSafe("command -v wp", wpRoot);
|
|
315
|
+
if (!wpCli) return { available: false };
|
|
316
|
+
|
|
317
|
+
const siteUrl = execSafe("wp option get siteurl --skip-themes --skip-plugins 2>/dev/null", wpRoot);
|
|
318
|
+
if (!siteUrl) return { available: false };
|
|
319
|
+
|
|
320
|
+
// Try to fetch headers
|
|
321
|
+
const headers = execSafe(`curl -sI -o /dev/null -w '%{http_code}' "${siteUrl}" 2>/dev/null`, wpRoot);
|
|
322
|
+
return { available: true, siteUrl, reachable: headers === "200" || headers === "301" || headers === "302" };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// Main
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
function main() {
|
|
330
|
+
const cwd = parseCwd();
|
|
331
|
+
|
|
332
|
+
if (!statSafe(cwd)?.isDirectory()) {
|
|
333
|
+
console.error(`Error: directory not found: ${cwd}`);
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const wpRoot = findWpRoot(cwd);
|
|
338
|
+
if (!wpRoot) {
|
|
339
|
+
console.log(JSON.stringify({
|
|
340
|
+
tool: "security_inspect",
|
|
341
|
+
version: TOOL_VERSION,
|
|
342
|
+
cwd,
|
|
343
|
+
detected: false,
|
|
344
|
+
error: "No WordPress installation found",
|
|
345
|
+
}, null, 2));
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const wpConfig = scanWpConfig(wpRoot);
|
|
350
|
+
const permissions = scanPermissions(wpRoot);
|
|
351
|
+
const htaccess = scanHtaccess(wpRoot);
|
|
352
|
+
const plugins = scanSecurityPlugins(wpRoot);
|
|
353
|
+
const headers = scanHeaders(wpRoot);
|
|
354
|
+
|
|
355
|
+
const totalIssues = wpConfig.issues.length + permissions.issues.length + htaccess.missing.length;
|
|
356
|
+
|
|
357
|
+
const report = {
|
|
358
|
+
tool: "security_inspect",
|
|
359
|
+
version: TOOL_VERSION,
|
|
360
|
+
cwd,
|
|
361
|
+
wpRoot,
|
|
362
|
+
detected: true,
|
|
363
|
+
summary: {
|
|
364
|
+
totalIssues,
|
|
365
|
+
severity: totalIssues === 0 ? "good" : totalIssues <= 3 ? "moderate" : "needs-attention",
|
|
366
|
+
},
|
|
367
|
+
wpConfig,
|
|
368
|
+
permissions,
|
|
369
|
+
htaccess,
|
|
370
|
+
securityPlugins: plugins,
|
|
371
|
+
headers,
|
|
372
|
+
recommendations: [],
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// Generate recommendations
|
|
376
|
+
if (plugins.detected.length === 0) {
|
|
377
|
+
report.recommendations.push("No security plugin detected. Consider installing Wordfence or Sucuri.");
|
|
378
|
+
}
|
|
379
|
+
if (wpConfig.issues.length > 0) {
|
|
380
|
+
report.recommendations.push(`${wpConfig.issues.length} wp-config.php issue(s) found. Review security constants.`);
|
|
381
|
+
}
|
|
382
|
+
if (htaccess.missing.length > 0) {
|
|
383
|
+
report.recommendations.push(`Missing .htaccess hardening: ${htaccess.missing.join(", ")}`);
|
|
384
|
+
}
|
|
385
|
+
if (permissions.issues.length > 0) {
|
|
386
|
+
report.recommendations.push(`${permissions.issues.length} file permission issue(s) found.`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
console.log(JSON.stringify(report, null, 2));
|
|
390
|
+
process.exit(0);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
main();
|