mcp-wordpress 1.2.0 → 1.2.2
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 +164 -6
- 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/package.json +3 -2
- package/src/security/SecurityConfig.ts +95 -95
- package/src/tools/BaseToolManager.ts +6 -6
package/README.md
CHANGED
|
@@ -63,7 +63,29 @@ npx mcp-wordpress setup
|
|
|
63
63
|
npm run setup
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
## 🏆 Latest Achievement: v1.2.
|
|
66
|
+
## 🏆 Latest Achievement: v1.2.1 - Test Infrastructure & Multi-Site Enhancement
|
|
67
|
+
|
|
68
|
+
Building on v1.2.0's performance monitoring and documentation features, v1.2.1 delivers **100% test reliability**, **enhanced multi-site support**, and **production-ready repository organization**:
|
|
69
|
+
|
|
70
|
+
### ✅ Test Infrastructure Overhaul (v1.2.1)
|
|
71
|
+
|
|
72
|
+
- **🔧 100% Test Success Rate**: Fixed all failing integration and tool tests
|
|
73
|
+
- **🌐 Multi-Site Testing**: Comprehensive test suite for multi-site WordPress configurations
|
|
74
|
+
- **🛡️ Enhanced Security**: Improved .gitignore and credential protection
|
|
75
|
+
- **📋 Test Organization**: Streamlined test scripts and better error reporting
|
|
76
|
+
- **⚡ Quick Validation**: New `npm run test:multisite` command for rapid configuration testing
|
|
77
|
+
|
|
78
|
+
### 🌐 Multi-Site Configuration Enhancements (v1.2.1)
|
|
79
|
+
|
|
80
|
+
- **📝 Complete Documentation**: Comprehensive Claude Desktop setup guide for multi-site usage
|
|
81
|
+
- **🔧 Fixed Configuration**: Resolved JWT authentication validation issues
|
|
82
|
+
- **✅ Site Validation**: Enhanced uniqueness checks for site URLs and IDs
|
|
83
|
+
- **🚀 Quick Testing**: Instant validation of all configured WordPress sites
|
|
84
|
+
- **📊 Status Reporting**: Clear success/failure reporting for each site configuration
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🏆 Previous Achievement: v1.2.0 - Performance & Documentation Revolution
|
|
67
89
|
|
|
68
90
|
We've implemented a **comprehensive performance monitoring system**, **intelligent caching**, **auto-generated documentation**, and **Docker containerization** - all while maintaining complete backward compatibility:
|
|
69
91
|
|
|
@@ -106,13 +128,16 @@ We've implemented a **comprehensive performance monitoring system**, **intellige
|
|
|
106
128
|
- [API Documentation](./docs/api/README.md)
|
|
107
129
|
- [Docker Deployment Guide](./docs/DOCKER.md)
|
|
108
130
|
|
|
109
|
-
## 🔐 Authentication & Testing Status
|
|
131
|
+
## 🔐 Authentication & Testing Status (v1.2.1)
|
|
110
132
|
|
|
111
133
|
✅ **Application Passwords** - Tested and working perfectly
|
|
112
134
|
✅ **JWT Authentication** - Supported with plugin
|
|
113
135
|
✅ **Basic Authentication** - Development ready
|
|
114
136
|
✅ **API Key Authentication** - Plugin-based support
|
|
115
|
-
✅ **
|
|
137
|
+
✅ **Integration Tests** - 100% success rate (9/9 tests passing)
|
|
138
|
+
✅ **Multi-Site Tests** - 100% success rate (3/3 sites verified)
|
|
139
|
+
✅ **Security Tests** - 100% success rate (40/40 tests passing)
|
|
140
|
+
✅ **Performance Tests** - 100% success rate (8/8 tests passing)
|
|
116
141
|
✅ **Tool Tests** - 100% success rate (14/14 tools working)
|
|
117
142
|
|
|
118
143
|
The setup wizard guides you through:
|
|
@@ -148,7 +173,7 @@ I want to use the NPX version (mcp-wordpress) so I don't need to install anythin
|
|
|
148
173
|
|
|
149
174
|
For local development and customization:
|
|
150
175
|
|
|
151
|
-
```
|
|
176
|
+
```text
|
|
152
177
|
Build and configure the MCP WordPress server project from https://github.com/thomasdyhr/mcp-wordpress locally on my computer.
|
|
153
178
|
|
|
154
179
|
Please:
|
|
@@ -291,6 +316,139 @@ Here's a complete `claude_desktop_config.json` file with MCP WordPress:
|
|
|
291
316
|
}
|
|
292
317
|
```
|
|
293
318
|
|
|
319
|
+
## 🌐 Multi-Site Configuration
|
|
320
|
+
|
|
321
|
+
MCP WordPress Server supports managing multiple WordPress sites from a single configuration. This is perfect for agencies, developers managing multiple client sites, or anyone with multiple WordPress installations.
|
|
322
|
+
|
|
323
|
+
### Setting Up Multi-Site Configuration
|
|
324
|
+
|
|
325
|
+
1. **Create a `mcp-wordpress.config.json` file** in your project root:
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"sites": [
|
|
330
|
+
{
|
|
331
|
+
"id": "site1",
|
|
332
|
+
"name": "My Main Site",
|
|
333
|
+
"config": {
|
|
334
|
+
"WORDPRESS_SITE_URL": "https://site1.com",
|
|
335
|
+
"WORDPRESS_USERNAME": "admin",
|
|
336
|
+
"WORDPRESS_APP_PASSWORD": "xxxx xxxx xxxx xxxx xxxx xxxx"
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"id": "site2",
|
|
341
|
+
"name": "My Blog",
|
|
342
|
+
"config": {
|
|
343
|
+
"WORDPRESS_SITE_URL": "https://blog.site2.com",
|
|
344
|
+
"WORDPRESS_USERNAME": "editor",
|
|
345
|
+
"WORDPRESS_APP_PASSWORD": "yyyy yyyy yyyy yyyy yyyy yyyy",
|
|
346
|
+
"WORDPRESS_AUTH_METHOD": "app-password"
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"id": "dev",
|
|
351
|
+
"name": "Development Site",
|
|
352
|
+
"config": {
|
|
353
|
+
"WORDPRESS_SITE_URL": "http://localhost:8080",
|
|
354
|
+
"WORDPRESS_USERNAME": "dev_user",
|
|
355
|
+
"WORDPRESS_APP_PASSWORD": "zzzz zzzz zzzz zzzz zzzz zzzz"
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
1. **Configure Claude Desktop for Multi-Site**:
|
|
363
|
+
|
|
364
|
+
```json
|
|
365
|
+
{
|
|
366
|
+
"mcpServers": {
|
|
367
|
+
"mcp-wordpress": {
|
|
368
|
+
"command": "npx",
|
|
369
|
+
"args": ["mcp-wordpress"]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Note: When using multi-site configuration, you don't need to specify environment variables in Claude Desktop. The server will read from your `mcp-wordpress.config.json` file.
|
|
376
|
+
|
|
377
|
+
### Using Multi-Site Tools
|
|
378
|
+
|
|
379
|
+
When you have multiple sites configured, all tools require a `site` parameter:
|
|
380
|
+
|
|
381
|
+
```text
|
|
382
|
+
# List posts from site1
|
|
383
|
+
wp_list_posts --site="site1"
|
|
384
|
+
|
|
385
|
+
# Create a post on the blog site
|
|
386
|
+
wp_create_post --site="site2" --title="New Blog Post" --content="Content here"
|
|
387
|
+
|
|
388
|
+
# Get user info from development site
|
|
389
|
+
wp_get_current_user --site="dev"
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Important Multi-Site Notes
|
|
393
|
+
|
|
394
|
+
- **Site IDs must be unique** - Each site needs a unique identifier
|
|
395
|
+
- **Security**: The `mcp-wordpress.config.json` file contains sensitive credentials and should NEVER be committed to version control
|
|
396
|
+
- **Default Site**: If only one site is configured, it will be used by default without needing the `--site` parameter
|
|
397
|
+
- **Authentication**: Each site can use different authentication methods (app-password, jwt, basic, api-key)
|
|
398
|
+
|
|
399
|
+
### Example: Managing Multiple Client Sites
|
|
400
|
+
|
|
401
|
+
```json
|
|
402
|
+
{
|
|
403
|
+
"sites": [
|
|
404
|
+
{
|
|
405
|
+
"id": "client-acme",
|
|
406
|
+
"name": "ACME Corporation",
|
|
407
|
+
"config": {
|
|
408
|
+
"WORDPRESS_SITE_URL": "https://acme-corp.com",
|
|
409
|
+
"WORDPRESS_USERNAME": "mcp_admin",
|
|
410
|
+
"WORDPRESS_APP_PASSWORD": "aaaa bbbb cccc dddd eeee ffff"
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"id": "client-tech",
|
|
415
|
+
"name": "TechStartup Blog",
|
|
416
|
+
"config": {
|
|
417
|
+
"WORDPRESS_SITE_URL": "https://blog.techstartup.io",
|
|
418
|
+
"WORDPRESS_USERNAME": "content_manager",
|
|
419
|
+
"WORDPRESS_APP_PASSWORD": "gggg hhhh iiii jjjj kkkk llll"
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"id": "client-shop",
|
|
424
|
+
"name": "Online Shop",
|
|
425
|
+
"config": {
|
|
426
|
+
"WORDPRESS_SITE_URL": "https://shop.example.com",
|
|
427
|
+
"WORDPRESS_USERNAME": "shop_admin",
|
|
428
|
+
"WORDPRESS_APP_PASSWORD": "mmmm nnnn oooo pppp qqqq rrrr",
|
|
429
|
+
"WORDPRESS_AUTH_METHOD": "jwt",
|
|
430
|
+
"WORDPRESS_JWT_SECRET": "your-jwt-secret-here"
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Then in Claude Desktop, you can manage all sites:
|
|
438
|
+
|
|
439
|
+
```text
|
|
440
|
+
# Check posts across all client sites
|
|
441
|
+
wp_list_posts --site="client-acme" --per_page=5
|
|
442
|
+
wp_list_posts --site="client-tech" --per_page=5
|
|
443
|
+
wp_list_posts --site="client-shop" --per_page=5
|
|
444
|
+
|
|
445
|
+
# Update content on specific sites
|
|
446
|
+
wp_update_post --site="client-acme" --id=123 --title="Updated Title"
|
|
447
|
+
|
|
448
|
+
# Manage media across sites
|
|
449
|
+
wp_list_media --site="client-shop" --media_type="image"
|
|
450
|
+
```
|
|
451
|
+
|
|
294
452
|
## 🛠 Build System
|
|
295
453
|
|
|
296
454
|
### TypeScript Build
|
|
@@ -529,7 +687,7 @@ npm run lint:fix
|
|
|
529
687
|
|
|
530
688
|
## 🏗 Project Structure
|
|
531
689
|
|
|
532
|
-
```
|
|
690
|
+
```diagram
|
|
533
691
|
mcp-wordpress/
|
|
534
692
|
├── src/ # TypeScript source code
|
|
535
693
|
│ ├── index.ts # Main MCP server
|
|
@@ -665,7 +823,7 @@ services:
|
|
|
665
823
|
|
|
666
824
|
Test the MCP server against a real WordPress instance using our automated testing setup:
|
|
667
825
|
|
|
668
|
-
```bash
|
|
826
|
+
```bash copy
|
|
669
827
|
# Automated live contract testing (recommended)
|
|
670
828
|
npm run test:contracts:live
|
|
671
829
|
```
|
|
@@ -38,11 +38,11 @@ export declare const SecurityConfig: {
|
|
|
38
38
|
auth: number;
|
|
39
39
|
};
|
|
40
40
|
headers: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
'X-Content-Type-Options': string;
|
|
42
|
+
'X-Frame-Options': string;
|
|
43
|
+
'X-XSS-Protection': string;
|
|
44
|
+
'Strict-Transport-Security': string;
|
|
45
|
+
'Content-Security-Policy': string;
|
|
46
46
|
};
|
|
47
47
|
errorMessages: {
|
|
48
48
|
authentication: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityConfig.d.ts","sourceRoot":"","sources":["../../src/security/SecurityConfig.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwK1B,CAAC;AAEF;;GAEG;AACH,qBAAa,aAAa;IACxB;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IAsBzC;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAUxC;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAgBvD;;OAEG;IACH,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAKxD;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG;CAStC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,GAAG,EACV,eAAe,GAAE,MAAiD,GACjE,KAAK,CAeP;AAKD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SecurityConfig.d.ts","sourceRoot":"","sources":["../../src/security/SecurityConfig.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwK1B,CAAC;AAEF;;GAEG;AACH,qBAAa,aAAa;IACxB;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IAsBzC;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAUxC;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAgBvD;;OAEG;IACH,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAKxD;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG;CAStC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,GAAG,EACV,eAAe,GAAE,MAAiD,GACjE,KAAK,CAeP;AAKD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;CACrB,CAQF"}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Security configuration and constants for MCP WordPress
|
|
3
3
|
*/
|
|
4
|
-
import { randomBytes } from
|
|
4
|
+
import { randomBytes } from 'crypto';
|
|
5
5
|
export const SecurityConfig = {
|
|
6
6
|
// Rate limiting
|
|
7
7
|
rateLimiting: {
|
|
8
8
|
default: {
|
|
9
9
|
windowMs: 60 * 1000, // 1 minute
|
|
10
|
-
maxRequests: 60
|
|
10
|
+
maxRequests: 60
|
|
11
11
|
},
|
|
12
12
|
authentication: {
|
|
13
13
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
14
|
-
maxAttempts: 5
|
|
14
|
+
maxAttempts: 5
|
|
15
15
|
},
|
|
16
16
|
upload: {
|
|
17
17
|
windowMs: 60 * 1000, // 1 minute
|
|
18
|
-
maxRequests: 10
|
|
19
|
-
}
|
|
18
|
+
maxRequests: 10
|
|
19
|
+
}
|
|
20
20
|
},
|
|
21
21
|
// File upload restrictions
|
|
22
22
|
fileUpload: {
|
|
23
23
|
maxSizeMB: 10,
|
|
24
24
|
allowedMimeTypes: [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
'image/jpeg',
|
|
26
|
+
'image/png',
|
|
27
|
+
'image/gif',
|
|
28
|
+
'image/webp',
|
|
29
|
+
'image/svg+xml',
|
|
30
|
+
'application/pdf',
|
|
31
|
+
'application/msword',
|
|
32
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
33
|
+
'application/vnd.ms-excel',
|
|
34
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
35
|
+
'text/plain',
|
|
36
|
+
'text/csv'
|
|
37
37
|
],
|
|
38
38
|
// Dangerous file extensions to block
|
|
39
39
|
blockedExtensions: [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
]
|
|
40
|
+
'.exe',
|
|
41
|
+
'.bat',
|
|
42
|
+
'.cmd',
|
|
43
|
+
'.com',
|
|
44
|
+
'.pif',
|
|
45
|
+
'.scr',
|
|
46
|
+
'.vbs',
|
|
47
|
+
'.js',
|
|
48
|
+
'.jar',
|
|
49
|
+
'.zip',
|
|
50
|
+
'.rar',
|
|
51
|
+
'.tar',
|
|
52
|
+
'.php',
|
|
53
|
+
'.php3',
|
|
54
|
+
'.php4',
|
|
55
|
+
'.php5',
|
|
56
|
+
'.phtml',
|
|
57
|
+
'.sh',
|
|
58
|
+
'.bash',
|
|
59
|
+
'.zsh',
|
|
60
|
+
'.fish',
|
|
61
|
+
'.ps1',
|
|
62
|
+
'.psm1'
|
|
63
|
+
]
|
|
64
64
|
},
|
|
65
65
|
// Input validation
|
|
66
66
|
validation: {
|
|
@@ -72,54 +72,54 @@ export const SecurityConfig = {
|
|
|
72
72
|
maxUsernameLength: 60,
|
|
73
73
|
minUsernameLength: 3,
|
|
74
74
|
maxPasswordLength: 128,
|
|
75
|
-
minPasswordLength: 8
|
|
75
|
+
minPasswordLength: 8
|
|
76
76
|
},
|
|
77
77
|
// Request timeouts (milliseconds)
|
|
78
78
|
timeouts: {
|
|
79
79
|
default: 30000, // 30 seconds
|
|
80
80
|
upload: 600000, // 10 minutes
|
|
81
|
-
auth: 10000
|
|
81
|
+
auth: 10000 // 10 seconds
|
|
82
82
|
},
|
|
83
83
|
// Security headers
|
|
84
84
|
headers: {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
'X-Content-Type-Options': 'nosniff',
|
|
86
|
+
'X-Frame-Options': 'DENY',
|
|
87
|
+
'X-XSS-Protection': '1; mode=block',
|
|
88
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
89
|
+
'Content-Security-Policy': 'default-src \'self\''
|
|
90
90
|
},
|
|
91
91
|
// Error messages (generic to avoid information disclosure)
|
|
92
92
|
errorMessages: {
|
|
93
|
-
authentication:
|
|
94
|
-
authorization:
|
|
95
|
-
validation:
|
|
96
|
-
rateLimit:
|
|
97
|
-
serverError:
|
|
98
|
-
notFound:
|
|
93
|
+
authentication: 'Authentication failed. Please check your credentials.',
|
|
94
|
+
authorization: 'You do not have permission to perform this action.',
|
|
95
|
+
validation: 'Invalid input provided.',
|
|
96
|
+
rateLimit: 'Too many requests. Please try again later.',
|
|
97
|
+
serverError: 'An error occurred processing your request.',
|
|
98
|
+
notFound: 'The requested resource was not found.'
|
|
99
99
|
},
|
|
100
100
|
// Logging configuration
|
|
101
101
|
logging: {
|
|
102
102
|
// Fields to exclude from logs
|
|
103
103
|
excludeFields: [
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
'password',
|
|
105
|
+
'appPassword',
|
|
106
|
+
'app_password',
|
|
107
|
+
'token',
|
|
108
|
+
'secret',
|
|
109
|
+
'authorization',
|
|
110
|
+
'cookie',
|
|
111
|
+
'session',
|
|
112
|
+
'key',
|
|
113
|
+
'apiKey',
|
|
114
|
+
'api_key'
|
|
115
115
|
],
|
|
116
116
|
// Patterns to redact in log messages
|
|
117
117
|
redactPatterns: [
|
|
118
118
|
/password["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
119
119
|
/token["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
120
120
|
/secret["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
121
|
-
/key["\s:=]+["']?([^"'\s]+)["']?/gi
|
|
122
|
-
]
|
|
121
|
+
/key["\s:=]+["']?([^"'\s]+)["']?/gi
|
|
122
|
+
]
|
|
123
123
|
},
|
|
124
124
|
// Cache configuration
|
|
125
125
|
cache: {
|
|
@@ -135,30 +135,30 @@ export const SecurityConfig = {
|
|
|
135
135
|
semiStatic: 2 * 60 * 60 * 1000, // 2 hours - categories, tags, user profiles
|
|
136
136
|
dynamic: 15 * 60 * 1000, // 15 minutes - posts, pages, comments
|
|
137
137
|
session: 30 * 60 * 1000, // 30 minutes - authentication, current user
|
|
138
|
-
realtime: 60 * 1000
|
|
138
|
+
realtime: 60 * 1000 // 1 minute - real-time data
|
|
139
139
|
},
|
|
140
140
|
// Cache-Control headers by data type
|
|
141
141
|
cacheHeaders: {
|
|
142
|
-
static:
|
|
143
|
-
semiStatic:
|
|
144
|
-
dynamic:
|
|
145
|
-
session:
|
|
146
|
-
realtime:
|
|
142
|
+
static: 'public, max-age=14400', // 4 hours
|
|
143
|
+
semiStatic: 'public, max-age=7200', // 2 hours
|
|
144
|
+
dynamic: 'public, max-age=900', // 15 minutes
|
|
145
|
+
session: 'private, max-age=1800', // 30 minutes
|
|
146
|
+
realtime: 'public, max-age=60' // 1 minute
|
|
147
147
|
},
|
|
148
148
|
// Invalidation settings
|
|
149
149
|
invalidation: {
|
|
150
150
|
enabled: true,
|
|
151
151
|
batchSize: 100, // Max events to process in one batch
|
|
152
152
|
queueTimeout: 5000, // Max time to wait before processing queue (ms)
|
|
153
|
-
enableCascading: true
|
|
153
|
+
enableCascading: true // Allow cascading invalidations
|
|
154
154
|
},
|
|
155
155
|
// Memory management
|
|
156
156
|
cleanup: {
|
|
157
157
|
interval: 60 * 1000, // Cleanup interval in milliseconds (1 minute)
|
|
158
158
|
maxMemoryMB: 50, // Maximum memory usage for cache
|
|
159
|
-
evictionThreshold: 0.8
|
|
160
|
-
}
|
|
161
|
-
}
|
|
159
|
+
evictionThreshold: 0.8 // Start evicting when 80% full
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
162
|
};
|
|
163
163
|
/**
|
|
164
164
|
* Security utility functions
|
|
@@ -168,15 +168,15 @@ export class SecurityUtils {
|
|
|
168
168
|
* Redact sensitive information from objects
|
|
169
169
|
*/
|
|
170
170
|
static redactSensitiveData(obj) {
|
|
171
|
-
if (typeof obj !==
|
|
171
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
172
172
|
return obj;
|
|
173
173
|
}
|
|
174
174
|
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
175
175
|
for (const key in redacted) {
|
|
176
176
|
if (SecurityConfig.logging.excludeFields.some((field) => key.toLowerCase().includes(field.toLowerCase()))) {
|
|
177
|
-
redacted[key] =
|
|
177
|
+
redacted[key] = '[REDACTED]';
|
|
178
178
|
}
|
|
179
|
-
else if (typeof redacted[key] ===
|
|
179
|
+
else if (typeof redacted[key] === 'object') {
|
|
180
180
|
redacted[key] = SecurityUtils.redactSensitiveData(redacted[key]);
|
|
181
181
|
}
|
|
182
182
|
}
|
|
@@ -189,7 +189,7 @@ export class SecurityUtils {
|
|
|
189
189
|
let redacted = str;
|
|
190
190
|
for (const pattern of SecurityConfig.logging.redactPatterns) {
|
|
191
191
|
redacted = redacted.replace(pattern, (match, value) => {
|
|
192
|
-
return match.replace(value,
|
|
192
|
+
return match.replace(value, '[REDACTED]');
|
|
193
193
|
});
|
|
194
194
|
}
|
|
195
195
|
return redacted;
|
|
@@ -198,9 +198,9 @@ export class SecurityUtils {
|
|
|
198
198
|
* Generate secure random strings
|
|
199
199
|
*/
|
|
200
200
|
static generateSecureToken(length = 32) {
|
|
201
|
-
const chars =
|
|
201
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
202
202
|
const array = new Uint8Array(length);
|
|
203
|
-
if (typeof crypto !==
|
|
203
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
204
204
|
crypto.getRandomValues(array);
|
|
205
205
|
}
|
|
206
206
|
else {
|
|
@@ -208,7 +208,7 @@ export class SecurityUtils {
|
|
|
208
208
|
const buffer = randomBytes(length);
|
|
209
209
|
array.set(buffer);
|
|
210
210
|
}
|
|
211
|
-
return Array.from(array, (byte) => chars[byte % chars.length]).join(
|
|
211
|
+
return Array.from(array, (byte) => chars[byte % chars.length]).join('');
|
|
212
212
|
}
|
|
213
213
|
/**
|
|
214
214
|
* Check if a file extension is allowed
|
|
@@ -221,10 +221,10 @@ export class SecurityUtils {
|
|
|
221
221
|
* Sanitize log output
|
|
222
222
|
*/
|
|
223
223
|
static sanitizeForLog(data) {
|
|
224
|
-
if (typeof data ===
|
|
224
|
+
if (typeof data === 'string') {
|
|
225
225
|
return SecurityUtils.redactString(data);
|
|
226
226
|
}
|
|
227
|
-
if (typeof data ===
|
|
227
|
+
if (typeof data === 'object') {
|
|
228
228
|
return SecurityUtils.redactSensitiveData(data);
|
|
229
229
|
}
|
|
230
230
|
return data;
|
|
@@ -235,28 +235,28 @@ export class SecurityUtils {
|
|
|
235
235
|
*/
|
|
236
236
|
export function createSecureError(error, fallbackMessage = SecurityConfig.errorMessages.serverError) {
|
|
237
237
|
// Log the actual error for debugging (with sanitization)
|
|
238
|
-
if (process.env.NODE_ENV !==
|
|
239
|
-
console.error(
|
|
238
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
239
|
+
console.error('Secure Error:', SecurityUtils.sanitizeForLog(error));
|
|
240
240
|
}
|
|
241
241
|
// Return generic error to prevent information disclosure
|
|
242
242
|
const secureError = new Error(fallbackMessage);
|
|
243
243
|
// Preserve error code if it's safe
|
|
244
|
-
if (error && typeof error.code ===
|
|
244
|
+
if (error && typeof error.code === 'string' && !error.code.includes('_')) {
|
|
245
245
|
secureError.code = error.code;
|
|
246
246
|
}
|
|
247
247
|
return secureError;
|
|
248
248
|
}
|
|
249
249
|
// Import path for file extension checking
|
|
250
|
-
import * as path from
|
|
250
|
+
import * as path from 'path';
|
|
251
251
|
/**
|
|
252
252
|
* Environment-specific security settings
|
|
253
253
|
*/
|
|
254
254
|
export function getEnvironmentSecurity() {
|
|
255
|
-
const isProduction = process.env.NODE_ENV ===
|
|
255
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
256
256
|
return {
|
|
257
257
|
strictMode: isProduction,
|
|
258
258
|
verboseErrors: !isProduction,
|
|
259
|
-
enforceHttps: isProduction
|
|
259
|
+
enforceHttps: isProduction
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
262
|
//# sourceMappingURL=SecurityConfig.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityConfig.js","sourceRoot":"","sources":["../../src/security/SecurityConfig.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,gBAAgB;IAChB,YAAY,EAAE;QACZ,OAAO,EAAE;YACP,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW;YAChC,WAAW,EAAE,EAAE;SAChB;QACD,cAAc,EAAE;YACd,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,YAAY;YACrC,WAAW,EAAE,CAAC;SACf;QACD,MAAM,EAAE;YACN,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW;YAChC,WAAW,EAAE,EAAE;SAChB;KACF;IAED,2BAA2B;IAC3B,UAAU,EAAE;QACV,SAAS,EAAE,EAAE;QACb,gBAAgB,EAAE;YAChB,YAAY;YACZ,WAAW;YACX,WAAW;YACX,YAAY;YACZ,eAAe;YACf,iBAAiB;YACjB,oBAAoB;YACpB,yEAAyE;YACzE,0BAA0B;YAC1B,mEAAmE;YACnE,YAAY;YACZ,UAAU;SACX;QACD,qCAAqC;QACrC,iBAAiB,EAAE;YACjB,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,KAAK;YACL,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,OAAO;YACP,OAAO;YACP,OAAO;YACP,QAAQ;YACR,KAAK;YACL,OAAO;YACP,MAAM;YACN,OAAO;YACP,MAAM;YACN,OAAO;SACR;KACF;IAED,mBAAmB;IACnB,UAAU,EAAE;QACV,eAAe,EAAE,IAAI;QACrB,cAAc,EAAE,GAAG;QACnB,gBAAgB,EAAE,KAAK;QACvB,gBAAgB,EAAE,GAAG;QACrB,YAAY,EAAE,IAAI;QAClB,iBAAiB,EAAE,EAAE;QACrB,iBAAiB,EAAE,CAAC;QACpB,iBAAiB,EAAE,GAAG;QACtB,iBAAiB,EAAE,CAAC;KACrB;IAED,kCAAkC;IAClC,QAAQ,EAAE;QACR,OAAO,EAAE,KAAK,EAAE,aAAa;QAC7B,MAAM,EAAE,MAAM,EAAE,aAAa;QAC7B,IAAI,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"SecurityConfig.js","sourceRoot":"","sources":["../../src/security/SecurityConfig.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Base utility class for tool managers to reduce code duplication
|
|
3
3
|
*/
|
|
4
|
-
import { getErrorMessage } from
|
|
4
|
+
import { getErrorMessage } from '../utils/error.js';
|
|
5
5
|
export class BaseToolUtils {
|
|
6
6
|
/**
|
|
7
7
|
* Validate required parameters
|
|
@@ -18,7 +18,7 @@ export class BaseToolUtils {
|
|
|
18
18
|
/**
|
|
19
19
|
* Validate ID parameter
|
|
20
20
|
*/
|
|
21
|
-
static validateId(id, name =
|
|
21
|
+
static validateId(id, name = 'id') {
|
|
22
22
|
const numId = Number(id);
|
|
23
23
|
if (!Number.isInteger(numId) || numId <= 0) {
|
|
24
24
|
throw new Error(`Invalid ${name}: must be a positive integer`);
|
|
@@ -36,12 +36,12 @@ export class BaseToolUtils {
|
|
|
36
36
|
* Cache key generation helper
|
|
37
37
|
*/
|
|
38
38
|
static generateCacheKey(operation, params) {
|
|
39
|
-
const site = params.site ||
|
|
39
|
+
const site = params.site || 'default';
|
|
40
40
|
const paramStr = Object.entries(params)
|
|
41
|
-
.filter(([key]) => key !==
|
|
41
|
+
.filter(([key]) => key !== 'site')
|
|
42
42
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
43
43
|
.map(([key, value]) => `${key}:${value}`)
|
|
44
|
-
.join(
|
|
44
|
+
.join('|');
|
|
45
45
|
return `${site}:${operation}:${paramStr}`;
|
|
46
46
|
}
|
|
47
47
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-wordpress",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Comprehensive Model Context Protocol server for WordPress management with 59 tools, performance monitoring, intelligent caching, auto-generated documentation, Docker support, TypeScript, and production-ready authentication",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
"test:security": "npm run build && NODE_OPTIONS=\"--experimental-vm-modules\" jest tests/security/ --config=jest.typescript.config.json",
|
|
79
79
|
"test:security:penetration": "npm run build && NODE_OPTIONS=\"--experimental-vm-modules\" jest tests/security/penetration-tests.test.js --config=jest.typescript.config.json",
|
|
80
80
|
"test:security:validation": "npm run build && NODE_OPTIONS=\"--experimental-vm-modules\" jest tests/security/security-validation.test.js --config=jest.typescript.config.json",
|
|
81
|
-
"test:tools": "node scripts/test-all-tools.js",
|
|
81
|
+
"test:tools": "node scripts/test-all-tools-fixed.js",
|
|
82
|
+
"test:multisite": "node scripts/test-multisite-quick.js",
|
|
82
83
|
"test:typescript": "npm run build && NODE_OPTIONS=\"--experimental-vm-modules\" jest --config=jest.typescript.config.json",
|
|
83
84
|
"test:watch": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --watch --config=jest.typescript.config.json",
|
|
84
85
|
"test:with-env": "bash scripts/start-test-env.sh && npm run test:typescript",
|
|
@@ -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
|
|