mcp-wordpress 1.2.0 → 1.2.3
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/README.md +268 -14
- package/dist/security/SecurityConfig.d.ts +5 -5
- package/dist/security/SecurityConfig.d.ts.map +1 -1
- package/dist/security/SecurityConfig.js +92 -92
- package/dist/security/SecurityConfig.js.map +1 -1
- package/dist/tools/BaseToolManager.js +5 -5
- package/docs/DOCKER.md +13 -13
- package/docs/developer/MAINTENANCE.md +2 -2
- package/package.json +11 -5
- package/src/security/SecurityConfig.ts +95 -95
- package/src/tools/BaseToolManager.ts +6 -6
|
@@ -2,68 +2,68 @@
|
|
|
2
2
|
* Security configuration and constants for MCP WordPress
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { randomBytes } from
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
6
6
|
|
|
7
7
|
export const SecurityConfig = {
|
|
8
8
|
// Rate limiting
|
|
9
9
|
rateLimiting: {
|
|
10
10
|
default: {
|
|
11
11
|
windowMs: 60 * 1000, // 1 minute
|
|
12
|
-
maxRequests: 60
|
|
12
|
+
maxRequests: 60
|
|
13
13
|
},
|
|
14
14
|
authentication: {
|
|
15
15
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
16
|
-
maxAttempts: 5
|
|
16
|
+
maxAttempts: 5
|
|
17
17
|
},
|
|
18
18
|
upload: {
|
|
19
19
|
windowMs: 60 * 1000, // 1 minute
|
|
20
|
-
maxRequests: 10
|
|
21
|
-
}
|
|
20
|
+
maxRequests: 10
|
|
21
|
+
}
|
|
22
22
|
},
|
|
23
23
|
|
|
24
24
|
// File upload restrictions
|
|
25
25
|
fileUpload: {
|
|
26
26
|
maxSizeMB: 10,
|
|
27
27
|
allowedMimeTypes: [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
'image/jpeg',
|
|
29
|
+
'image/png',
|
|
30
|
+
'image/gif',
|
|
31
|
+
'image/webp',
|
|
32
|
+
'image/svg+xml',
|
|
33
|
+
'application/pdf',
|
|
34
|
+
'application/msword',
|
|
35
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
36
|
+
'application/vnd.ms-excel',
|
|
37
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
38
|
+
'text/plain',
|
|
39
|
+
'text/csv'
|
|
40
40
|
],
|
|
41
41
|
// Dangerous file extensions to block
|
|
42
42
|
blockedExtensions: [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
]
|
|
43
|
+
'.exe',
|
|
44
|
+
'.bat',
|
|
45
|
+
'.cmd',
|
|
46
|
+
'.com',
|
|
47
|
+
'.pif',
|
|
48
|
+
'.scr',
|
|
49
|
+
'.vbs',
|
|
50
|
+
'.js',
|
|
51
|
+
'.jar',
|
|
52
|
+
'.zip',
|
|
53
|
+
'.rar',
|
|
54
|
+
'.tar',
|
|
55
|
+
'.php',
|
|
56
|
+
'.php3',
|
|
57
|
+
'.php4',
|
|
58
|
+
'.php5',
|
|
59
|
+
'.phtml',
|
|
60
|
+
'.sh',
|
|
61
|
+
'.bash',
|
|
62
|
+
'.zsh',
|
|
63
|
+
'.fish',
|
|
64
|
+
'.ps1',
|
|
65
|
+
'.psm1'
|
|
66
|
+
]
|
|
67
67
|
},
|
|
68
68
|
|
|
69
69
|
// Input validation
|
|
@@ -76,58 +76,58 @@ export const SecurityConfig = {
|
|
|
76
76
|
maxUsernameLength: 60,
|
|
77
77
|
minUsernameLength: 3,
|
|
78
78
|
maxPasswordLength: 128,
|
|
79
|
-
minPasswordLength: 8
|
|
79
|
+
minPasswordLength: 8
|
|
80
80
|
},
|
|
81
81
|
|
|
82
82
|
// Request timeouts (milliseconds)
|
|
83
83
|
timeouts: {
|
|
84
84
|
default: 30000, // 30 seconds
|
|
85
85
|
upload: 600000, // 10 minutes
|
|
86
|
-
auth: 10000
|
|
86
|
+
auth: 10000 // 10 seconds
|
|
87
87
|
},
|
|
88
88
|
|
|
89
89
|
// Security headers
|
|
90
90
|
headers: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
'X-Content-Type-Options': 'nosniff',
|
|
92
|
+
'X-Frame-Options': 'DENY',
|
|
93
|
+
'X-XSS-Protection': '1; mode=block',
|
|
94
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
95
|
+
'Content-Security-Policy': 'default-src \'self\''
|
|
96
96
|
},
|
|
97
97
|
|
|
98
98
|
// Error messages (generic to avoid information disclosure)
|
|
99
99
|
errorMessages: {
|
|
100
|
-
authentication:
|
|
101
|
-
authorization:
|
|
102
|
-
validation:
|
|
103
|
-
rateLimit:
|
|
104
|
-
serverError:
|
|
105
|
-
notFound:
|
|
100
|
+
authentication: 'Authentication failed. Please check your credentials.',
|
|
101
|
+
authorization: 'You do not have permission to perform this action.',
|
|
102
|
+
validation: 'Invalid input provided.',
|
|
103
|
+
rateLimit: 'Too many requests. Please try again later.',
|
|
104
|
+
serverError: 'An error occurred processing your request.',
|
|
105
|
+
notFound: 'The requested resource was not found.'
|
|
106
106
|
},
|
|
107
107
|
|
|
108
108
|
// Logging configuration
|
|
109
109
|
logging: {
|
|
110
110
|
// Fields to exclude from logs
|
|
111
111
|
excludeFields: [
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
'password',
|
|
113
|
+
'appPassword',
|
|
114
|
+
'app_password',
|
|
115
|
+
'token',
|
|
116
|
+
'secret',
|
|
117
|
+
'authorization',
|
|
118
|
+
'cookie',
|
|
119
|
+
'session',
|
|
120
|
+
'key',
|
|
121
|
+
'apiKey',
|
|
122
|
+
'api_key'
|
|
123
123
|
],
|
|
124
124
|
// Patterns to redact in log messages
|
|
125
125
|
redactPatterns: [
|
|
126
126
|
/password["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
127
127
|
/token["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
128
128
|
/secret["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
129
|
-
/key["\s:=]+["']?([^"'\s]+)["']?/gi
|
|
130
|
-
]
|
|
129
|
+
/key["\s:=]+["']?([^"'\s]+)["']?/gi
|
|
130
|
+
]
|
|
131
131
|
},
|
|
132
132
|
|
|
133
133
|
// Cache configuration
|
|
@@ -145,16 +145,16 @@ export const SecurityConfig = {
|
|
|
145
145
|
semiStatic: 2 * 60 * 60 * 1000, // 2 hours - categories, tags, user profiles
|
|
146
146
|
dynamic: 15 * 60 * 1000, // 15 minutes - posts, pages, comments
|
|
147
147
|
session: 30 * 60 * 1000, // 30 minutes - authentication, current user
|
|
148
|
-
realtime: 60 * 1000
|
|
148
|
+
realtime: 60 * 1000 // 1 minute - real-time data
|
|
149
149
|
},
|
|
150
150
|
|
|
151
151
|
// Cache-Control headers by data type
|
|
152
152
|
cacheHeaders: {
|
|
153
|
-
static:
|
|
154
|
-
semiStatic:
|
|
155
|
-
dynamic:
|
|
156
|
-
session:
|
|
157
|
-
realtime:
|
|
153
|
+
static: 'public, max-age=14400', // 4 hours
|
|
154
|
+
semiStatic: 'public, max-age=7200', // 2 hours
|
|
155
|
+
dynamic: 'public, max-age=900', // 15 minutes
|
|
156
|
+
session: 'private, max-age=1800', // 30 minutes
|
|
157
|
+
realtime: 'public, max-age=60' // 1 minute
|
|
158
158
|
},
|
|
159
159
|
|
|
160
160
|
// Invalidation settings
|
|
@@ -162,16 +162,16 @@ export const SecurityConfig = {
|
|
|
162
162
|
enabled: true,
|
|
163
163
|
batchSize: 100, // Max events to process in one batch
|
|
164
164
|
queueTimeout: 5000, // Max time to wait before processing queue (ms)
|
|
165
|
-
enableCascading: true
|
|
165
|
+
enableCascading: true // Allow cascading invalidations
|
|
166
166
|
},
|
|
167
167
|
|
|
168
168
|
// Memory management
|
|
169
169
|
cleanup: {
|
|
170
170
|
interval: 60 * 1000, // Cleanup interval in milliseconds (1 minute)
|
|
171
171
|
maxMemoryMB: 50, // Maximum memory usage for cache
|
|
172
|
-
evictionThreshold: 0.8
|
|
173
|
-
}
|
|
174
|
-
}
|
|
172
|
+
evictionThreshold: 0.8 // Start evicting when 80% full
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
175
|
};
|
|
176
176
|
|
|
177
177
|
/**
|
|
@@ -182,7 +182,7 @@ export class SecurityUtils {
|
|
|
182
182
|
* Redact sensitive information from objects
|
|
183
183
|
*/
|
|
184
184
|
static redactSensitiveData(obj: any): any {
|
|
185
|
-
if (typeof obj !==
|
|
185
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
186
186
|
return obj;
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -191,11 +191,11 @@ export class SecurityUtils {
|
|
|
191
191
|
for (const key in redacted) {
|
|
192
192
|
if (
|
|
193
193
|
SecurityConfig.logging.excludeFields.some((field) =>
|
|
194
|
-
key.toLowerCase().includes(field.toLowerCase())
|
|
194
|
+
key.toLowerCase().includes(field.toLowerCase())
|
|
195
195
|
)
|
|
196
196
|
) {
|
|
197
|
-
redacted[key] =
|
|
198
|
-
} else if (typeof redacted[key] ===
|
|
197
|
+
redacted[key] = '[REDACTED]';
|
|
198
|
+
} else if (typeof redacted[key] === 'object') {
|
|
199
199
|
redacted[key] = SecurityUtils.redactSensitiveData(redacted[key]);
|
|
200
200
|
}
|
|
201
201
|
}
|
|
@@ -210,7 +210,7 @@ export class SecurityUtils {
|
|
|
210
210
|
let redacted = str;
|
|
211
211
|
for (const pattern of SecurityConfig.logging.redactPatterns) {
|
|
212
212
|
redacted = redacted.replace(pattern, (match, value) => {
|
|
213
|
-
return match.replace(value,
|
|
213
|
+
return match.replace(value, '[REDACTED]');
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
216
|
return redacted;
|
|
@@ -221,10 +221,10 @@ export class SecurityUtils {
|
|
|
221
221
|
*/
|
|
222
222
|
static generateSecureToken(length: number = 32): string {
|
|
223
223
|
const chars =
|
|
224
|
-
|
|
224
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
225
225
|
const array = new Uint8Array(length);
|
|
226
226
|
|
|
227
|
-
if (typeof crypto !==
|
|
227
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
228
228
|
crypto.getRandomValues(array);
|
|
229
229
|
} else {
|
|
230
230
|
// Fallback for Node.js
|
|
@@ -232,7 +232,7 @@ export class SecurityUtils {
|
|
|
232
232
|
array.set(buffer);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
return Array.from(array, (byte) => chars[byte % chars.length]).join(
|
|
235
|
+
return Array.from(array, (byte) => chars[byte % chars.length]).join('');
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
/**
|
|
@@ -247,10 +247,10 @@ export class SecurityUtils {
|
|
|
247
247
|
* Sanitize log output
|
|
248
248
|
*/
|
|
249
249
|
static sanitizeForLog(data: any): any {
|
|
250
|
-
if (typeof data ===
|
|
250
|
+
if (typeof data === 'string') {
|
|
251
251
|
return SecurityUtils.redactString(data);
|
|
252
252
|
}
|
|
253
|
-
if (typeof data ===
|
|
253
|
+
if (typeof data === 'object') {
|
|
254
254
|
return SecurityUtils.redactSensitiveData(data);
|
|
255
255
|
}
|
|
256
256
|
return data;
|
|
@@ -262,18 +262,18 @@ export class SecurityUtils {
|
|
|
262
262
|
*/
|
|
263
263
|
export function createSecureError(
|
|
264
264
|
error: any,
|
|
265
|
-
fallbackMessage: string = SecurityConfig.errorMessages.serverError
|
|
265
|
+
fallbackMessage: string = SecurityConfig.errorMessages.serverError
|
|
266
266
|
): Error {
|
|
267
267
|
// Log the actual error for debugging (with sanitization)
|
|
268
|
-
if (process.env.NODE_ENV !==
|
|
269
|
-
console.error(
|
|
268
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
269
|
+
console.error('Secure Error:', SecurityUtils.sanitizeForLog(error));
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
// Return generic error to prevent information disclosure
|
|
273
273
|
const secureError = new Error(fallbackMessage);
|
|
274
274
|
|
|
275
275
|
// Preserve error code if it's safe
|
|
276
|
-
if (error && typeof error.code ===
|
|
276
|
+
if (error && typeof error.code === 'string' && !error.code.includes('_')) {
|
|
277
277
|
(secureError as any).code = error.code;
|
|
278
278
|
}
|
|
279
279
|
|
|
@@ -281,7 +281,7 @@ export function createSecureError(
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
// Import path for file extension checking
|
|
284
|
-
import * as path from
|
|
284
|
+
import * as path from 'path';
|
|
285
285
|
|
|
286
286
|
/**
|
|
287
287
|
* Environment-specific security settings
|
|
@@ -290,12 +290,12 @@ export function getEnvironmentSecurity(): {
|
|
|
290
290
|
strictMode: boolean;
|
|
291
291
|
verboseErrors: boolean;
|
|
292
292
|
enforceHttps: boolean;
|
|
293
|
-
} {
|
|
294
|
-
const isProduction = process.env.NODE_ENV ===
|
|
293
|
+
} {
|
|
294
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
295
295
|
|
|
296
296
|
return {
|
|
297
297
|
strictMode: isProduction,
|
|
298
298
|
verboseErrors: !isProduction,
|
|
299
|
-
enforceHttps: isProduction
|
|
299
|
+
enforceHttps: isProduction
|
|
300
300
|
};
|
|
301
301
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Base utility class for tool managers to reduce code duplication
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { getErrorMessage } from
|
|
5
|
+
import { getErrorMessage } from '../utils/error.js';
|
|
6
6
|
|
|
7
7
|
export class BaseToolUtils {
|
|
8
8
|
/**
|
|
@@ -23,7 +23,7 @@ export class BaseToolUtils {
|
|
|
23
23
|
/**
|
|
24
24
|
* Validate ID parameter
|
|
25
25
|
*/
|
|
26
|
-
static validateId(id: unknown, name =
|
|
26
|
+
static validateId(id: unknown, name = 'id'): number {
|
|
27
27
|
const numId = Number(id);
|
|
28
28
|
if (!Number.isInteger(numId) || numId <= 0) {
|
|
29
29
|
throw new Error(`Invalid ${name}: must be a positive integer`);
|
|
@@ -44,14 +44,14 @@ export class BaseToolUtils {
|
|
|
44
44
|
*/
|
|
45
45
|
static generateCacheKey(
|
|
46
46
|
operation: string,
|
|
47
|
-
params: Record<string, unknown
|
|
47
|
+
params: Record<string, unknown>
|
|
48
48
|
): string {
|
|
49
|
-
const site = params.site ||
|
|
49
|
+
const site = params.site || 'default';
|
|
50
50
|
const paramStr = Object.entries(params)
|
|
51
|
-
.filter(([key]) => key !==
|
|
51
|
+
.filter(([key]) => key !== 'site')
|
|
52
52
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
53
53
|
.map(([key, value]) => `${key}:${value}`)
|
|
54
|
-
.join(
|
|
54
|
+
.join('|');
|
|
55
55
|
return `${site}:${operation}:${paramStr}`;
|
|
56
56
|
}
|
|
57
57
|
|