mcp-wordpress 1.1.7 → 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.
Files changed (255) hide show
  1. package/README.md +388 -66
  2. package/dist/cache/CacheInvalidation.d.ts +118 -0
  3. package/dist/cache/CacheInvalidation.d.ts.map +1 -0
  4. package/dist/cache/CacheInvalidation.js +349 -0
  5. package/dist/cache/CacheInvalidation.js.map +1 -0
  6. package/dist/cache/CacheManager.d.ts +143 -0
  7. package/dist/cache/CacheManager.d.ts.map +1 -0
  8. package/dist/cache/CacheManager.js +308 -0
  9. package/dist/cache/CacheManager.js.map +1 -0
  10. package/dist/cache/HttpCacheWrapper.d.ts +121 -0
  11. package/dist/cache/HttpCacheWrapper.d.ts.map +1 -0
  12. package/dist/cache/HttpCacheWrapper.js +280 -0
  13. package/dist/cache/HttpCacheWrapper.js.map +1 -0
  14. package/dist/cache/__tests__/CacheInvalidation.test.d.ts +5 -0
  15. package/dist/cache/__tests__/CacheInvalidation.test.d.ts.map +1 -0
  16. package/dist/cache/__tests__/CacheInvalidation.test.js +236 -0
  17. package/dist/cache/__tests__/CacheInvalidation.test.js.map +1 -0
  18. package/dist/cache/__tests__/CacheManager.test.d.ts +5 -0
  19. package/dist/cache/__tests__/CacheManager.test.d.ts.map +1 -0
  20. package/dist/cache/__tests__/CacheManager.test.js +233 -0
  21. package/dist/cache/__tests__/CacheManager.test.js.map +1 -0
  22. package/dist/cache/__tests__/CachedWordPressClient.test.d.ts +5 -0
  23. package/dist/cache/__tests__/CachedWordPressClient.test.d.ts.map +1 -0
  24. package/dist/cache/__tests__/CachedWordPressClient.test.js +228 -0
  25. package/dist/cache/__tests__/CachedWordPressClient.test.js.map +1 -0
  26. package/dist/cache/__tests__/HttpCacheWrapper.test.d.ts +5 -0
  27. package/dist/cache/__tests__/HttpCacheWrapper.test.d.ts.map +1 -0
  28. package/dist/cache/__tests__/HttpCacheWrapper.test.js +296 -0
  29. package/dist/cache/__tests__/HttpCacheWrapper.test.js.map +1 -0
  30. package/dist/cache/index.d.ts +12 -0
  31. package/dist/cache/index.d.ts.map +1 -0
  32. package/dist/cache/index.js +9 -0
  33. package/dist/cache/index.js.map +1 -0
  34. package/dist/client/CachedWordPressClient.d.ts +160 -0
  35. package/dist/client/CachedWordPressClient.d.ts.map +1 -0
  36. package/dist/client/CachedWordPressClient.js +338 -0
  37. package/dist/client/CachedWordPressClient.js.map +1 -0
  38. package/dist/client/WordPressClient.d.ts +81 -0
  39. package/dist/client/WordPressClient.d.ts.map +1 -0
  40. package/dist/client/WordPressClient.js +354 -0
  41. package/dist/client/WordPressClient.js.map +1 -0
  42. package/dist/config/ConfigurationSchema.d.ts +281 -0
  43. package/dist/config/ConfigurationSchema.d.ts.map +1 -0
  44. package/dist/config/ConfigurationSchema.js +205 -0
  45. package/dist/config/ConfigurationSchema.js.map +1 -0
  46. package/dist/config/ServerConfiguration.d.ts +38 -0
  47. package/dist/config/ServerConfiguration.d.ts.map +1 -0
  48. package/dist/config/ServerConfiguration.js +158 -0
  49. package/dist/config/ServerConfiguration.js.map +1 -0
  50. package/dist/docs/DocumentationGenerator.d.ts +184 -0
  51. package/dist/docs/DocumentationGenerator.d.ts.map +1 -0
  52. package/dist/docs/DocumentationGenerator.js +735 -0
  53. package/dist/docs/DocumentationGenerator.js.map +1 -0
  54. package/dist/docs/MarkdownFormatter.d.ts +84 -0
  55. package/dist/docs/MarkdownFormatter.d.ts.map +1 -0
  56. package/dist/docs/MarkdownFormatter.js +448 -0
  57. package/dist/docs/MarkdownFormatter.js.map +1 -0
  58. package/dist/docs/index.d.ts +8 -0
  59. package/dist/docs/index.d.ts.map +1 -0
  60. package/dist/docs/index.js +7 -0
  61. package/dist/docs/index.js.map +1 -0
  62. package/dist/index.d.ts +1 -4
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +12 -212
  65. package/dist/index.js.map +1 -1
  66. package/dist/performance/AnomalyDetector.d.ts +63 -0
  67. package/dist/performance/AnomalyDetector.d.ts.map +1 -0
  68. package/dist/performance/AnomalyDetector.js +222 -0
  69. package/dist/performance/AnomalyDetector.js.map +1 -0
  70. package/dist/performance/BenchmarkAnalyzer.d.ts +67 -0
  71. package/dist/performance/BenchmarkAnalyzer.d.ts.map +1 -0
  72. package/dist/performance/BenchmarkAnalyzer.js +301 -0
  73. package/dist/performance/BenchmarkAnalyzer.js.map +1 -0
  74. package/dist/performance/MetricsCollector.d.ts +139 -0
  75. package/dist/performance/MetricsCollector.d.ts.map +1 -0
  76. package/dist/performance/MetricsCollector.js +320 -0
  77. package/dist/performance/MetricsCollector.js.map +1 -0
  78. package/dist/performance/PerformanceAnalytics.d.ts +162 -0
  79. package/dist/performance/PerformanceAnalytics.d.ts.map +1 -0
  80. package/dist/performance/PerformanceAnalytics.js +554 -0
  81. package/dist/performance/PerformanceAnalytics.js.map +1 -0
  82. package/dist/performance/PerformanceMonitor.d.ts +202 -0
  83. package/dist/performance/PerformanceMonitor.d.ts.map +1 -0
  84. package/dist/performance/PerformanceMonitor.js +478 -0
  85. package/dist/performance/PerformanceMonitor.js.map +1 -0
  86. package/dist/performance/TrendAnalyzer.d.ts +69 -0
  87. package/dist/performance/TrendAnalyzer.d.ts.map +1 -0
  88. package/dist/performance/TrendAnalyzer.js +203 -0
  89. package/dist/performance/TrendAnalyzer.js.map +1 -0
  90. package/dist/performance/index.d.ts +11 -0
  91. package/dist/performance/index.d.ts.map +1 -0
  92. package/dist/performance/index.js +8 -0
  93. package/dist/performance/index.js.map +1 -0
  94. package/dist/security/InputValidator.d.ts +215 -0
  95. package/dist/security/InputValidator.d.ts.map +1 -0
  96. package/dist/security/InputValidator.js +278 -0
  97. package/dist/security/InputValidator.js.map +1 -0
  98. package/dist/security/SecurityConfig.d.ts +129 -0
  99. package/dist/security/SecurityConfig.d.ts.map +1 -0
  100. package/dist/security/SecurityConfig.js +262 -0
  101. package/dist/security/SecurityConfig.js.map +1 -0
  102. package/dist/server/ConnectionTester.d.ts +24 -0
  103. package/dist/server/ConnectionTester.d.ts.map +1 -0
  104. package/dist/server/ConnectionTester.js +61 -0
  105. package/dist/server/ConnectionTester.js.map +1 -0
  106. package/dist/server/ToolRegistry.d.ts +46 -0
  107. package/dist/server/ToolRegistry.d.ts.map +1 -0
  108. package/dist/server/ToolRegistry.js +148 -0
  109. package/dist/server/ToolRegistry.js.map +1 -0
  110. package/dist/tools/BaseToolClass.d.ts +76 -0
  111. package/dist/tools/BaseToolClass.d.ts.map +1 -0
  112. package/dist/tools/BaseToolClass.js +104 -0
  113. package/dist/tools/BaseToolClass.js.map +1 -0
  114. package/dist/tools/BaseToolManager.d.ts +26 -0
  115. package/dist/tools/BaseToolManager.d.ts.map +1 -0
  116. package/dist/tools/BaseToolManager.js +56 -0
  117. package/dist/tools/BaseToolManager.js.map +1 -0
  118. package/dist/tools/base.d.ts +37 -0
  119. package/dist/tools/base.d.ts.map +1 -0
  120. package/dist/tools/base.js +60 -0
  121. package/dist/tools/base.js.map +1 -0
  122. package/dist/tools/cache.d.ts +260 -0
  123. package/dist/tools/cache.d.ts.map +1 -0
  124. package/dist/tools/cache.js +237 -0
  125. package/dist/tools/cache.js.map +1 -0
  126. package/dist/tools/index.d.ts +2 -0
  127. package/dist/tools/index.d.ts.map +1 -1
  128. package/dist/tools/index.js +2 -0
  129. package/dist/tools/index.js.map +1 -1
  130. package/dist/tools/performance.d.ts +63 -0
  131. package/dist/tools/performance.d.ts.map +1 -0
  132. package/dist/tools/performance.js +865 -0
  133. package/dist/tools/performance.js.map +1 -0
  134. package/dist/types/client.d.ts +1 -0
  135. package/dist/types/client.d.ts.map +1 -1
  136. package/dist/types/client.js.map +1 -1
  137. package/dist/utils/toolWrapper.d.ts +4 -0
  138. package/dist/utils/toolWrapper.d.ts.map +1 -1
  139. package/dist/utils/toolWrapper.js +11 -0
  140. package/dist/utils/toolWrapper.js.map +1 -1
  141. package/dist/utils/validation.d.ts +68 -0
  142. package/dist/utils/validation.d.ts.map +1 -0
  143. package/dist/utils/validation.js +185 -0
  144. package/dist/utils/validation.js.map +1 -0
  145. package/docs/CACHING.md +340 -0
  146. package/docs/DOCKER.md +451 -0
  147. package/docs/PERFORMANCE_MONITORING.md +471 -0
  148. package/docs/SECURITY_TESTING.md +393 -0
  149. package/docs/api/README.md +200 -0
  150. package/docs/api/categories/auth.md +40 -0
  151. package/docs/api/categories/cache.md +41 -0
  152. package/docs/api/categories/comment.md +44 -0
  153. package/docs/api/categories/media.md +43 -0
  154. package/docs/api/categories/page.md +43 -0
  155. package/docs/api/categories/performance.md +44 -0
  156. package/docs/api/categories/post.md +43 -0
  157. package/docs/api/categories/site.md +43 -0
  158. package/docs/api/categories/taxonomy.md +47 -0
  159. package/docs/api/categories/user.md +43 -0
  160. package/docs/api/openapi.json +3305 -0
  161. package/docs/api/summary.json +12 -0
  162. package/docs/api/tools/wp_approve_comment.md +98 -0
  163. package/docs/api/tools/wp_cache_clear.md +120 -0
  164. package/docs/api/tools/wp_cache_info.md +119 -0
  165. package/docs/api/tools/wp_cache_stats.md +119 -0
  166. package/docs/api/tools/wp_cache_warm.md +119 -0
  167. package/docs/api/tools/wp_create_application_password.md +102 -0
  168. package/docs/api/tools/wp_create_category.md +102 -0
  169. package/docs/api/tools/wp_create_comment.md +128 -0
  170. package/docs/api/tools/wp_create_page.md +135 -0
  171. package/docs/api/tools/wp_create_post.md +147 -0
  172. package/docs/api/tools/wp_create_tag.md +101 -0
  173. package/docs/api/tools/wp_create_user.md +135 -0
  174. package/docs/api/tools/wp_delete_application_password.md +101 -0
  175. package/docs/api/tools/wp_delete_category.md +100 -0
  176. package/docs/api/tools/wp_delete_comment.md +101 -0
  177. package/docs/api/tools/wp_delete_media.md +108 -0
  178. package/docs/api/tools/wp_delete_page.md +108 -0
  179. package/docs/api/tools/wp_delete_post.md +117 -0
  180. package/docs/api/tools/wp_delete_tag.md +100 -0
  181. package/docs/api/tools/wp_delete_user.md +108 -0
  182. package/docs/api/tools/wp_get_application_passwords.md +103 -0
  183. package/docs/api/tools/wp_get_auth_status.md +101 -0
  184. package/docs/api/tools/wp_get_category.md +103 -0
  185. package/docs/api/tools/wp_get_comment.md +103 -0
  186. package/docs/api/tools/wp_get_current_user.md +101 -0
  187. package/docs/api/tools/wp_get_media.md +103 -0
  188. package/docs/api/tools/wp_get_page.md +103 -0
  189. package/docs/api/tools/wp_get_page_revisions.md +103 -0
  190. package/docs/api/tools/wp_get_post.md +112 -0
  191. package/docs/api/tools/wp_get_post_revisions.md +103 -0
  192. package/docs/api/tools/wp_get_site_settings.md +108 -0
  193. package/docs/api/tools/wp_get_tag.md +103 -0
  194. package/docs/api/tools/wp_get_user.md +103 -0
  195. package/docs/api/tools/wp_list_categories.md +111 -0
  196. package/docs/api/tools/wp_list_comments.md +111 -0
  197. package/docs/api/tools/wp_list_media.md +145 -0
  198. package/docs/api/tools/wp_list_pages.md +145 -0
  199. package/docs/api/tools/wp_list_posts.md +156 -0
  200. package/docs/api/tools/wp_list_tags.md +110 -0
  201. package/docs/api/tools/wp_list_users.md +111 -0
  202. package/docs/api/tools/wp_performance_alerts.md +162 -0
  203. package/docs/api/tools/wp_performance_benchmark.md +160 -0
  204. package/docs/api/tools/wp_performance_export.md +162 -0
  205. package/docs/api/tools/wp_performance_history.md +161 -0
  206. package/docs/api/tools/wp_performance_optimize.md +162 -0
  207. package/docs/api/tools/wp_performance_stats.md +160 -0
  208. package/docs/api/tools/wp_search_site.md +99 -0
  209. package/docs/api/tools/wp_spam_comment.md +98 -0
  210. package/docs/api/tools/wp_switch_auth_method.md +122 -0
  211. package/docs/api/tools/wp_test_auth.md +96 -0
  212. package/docs/api/tools/wp_update_category.md +102 -0
  213. package/docs/api/tools/wp_update_comment.md +127 -0
  214. package/docs/api/tools/wp_update_media.md +129 -0
  215. package/docs/api/tools/wp_update_page.md +135 -0
  216. package/docs/api/tools/wp_update_post.md +144 -0
  217. package/docs/api/tools/wp_update_site_settings.md +127 -0
  218. package/docs/api/tools/wp_update_tag.md +102 -0
  219. package/docs/api/tools/wp_update_user.md +134 -0
  220. package/docs/api/tools/wp_upload_media.md +131 -0
  221. package/docs/api/types/WordPressPost.md +39 -0
  222. package/docs/contract-testing.md +183 -0
  223. package/docs/developer/NPM_AUTH_SETUP.md +3 -3
  224. package/docs/wordpress-rest-api-authentication-troubleshooting.md +218 -0
  225. package/package.json +84 -64
  226. package/src/cache/CacheInvalidation.ts +421 -0
  227. package/src/cache/CacheManager.ts +391 -0
  228. package/src/cache/HttpCacheWrapper.ts +372 -0
  229. package/src/cache/__tests__/CacheInvalidation.test.ts +299 -0
  230. package/src/cache/__tests__/CacheManager.test.ts +300 -0
  231. package/src/cache/__tests__/CachedWordPressClient.test.ts +304 -0
  232. package/src/cache/__tests__/HttpCacheWrapper.test.ts +359 -0
  233. package/src/cache/index.ts +26 -0
  234. package/src/client/CachedWordPressClient.ts +442 -0
  235. package/src/config/ConfigurationSchema.ts +246 -0
  236. package/src/config/ServerConfiguration.ts +215 -0
  237. package/src/docs/DocumentationGenerator.ts +952 -0
  238. package/src/docs/MarkdownFormatter.ts +494 -0
  239. package/src/docs/index.ts +21 -0
  240. package/src/index.ts +14 -274
  241. package/src/performance/MetricsCollector.ts +447 -0
  242. package/src/performance/PerformanceAnalytics.ts +762 -0
  243. package/src/performance/PerformanceMonitor.ts +649 -0
  244. package/src/performance/index.ts +28 -0
  245. package/src/security/InputValidator.ts +319 -0
  246. package/src/security/SecurityConfig.ts +301 -0
  247. package/src/server/ConnectionTester.ts +74 -0
  248. package/src/server/ToolRegistry.ts +194 -0
  249. package/src/tools/BaseToolManager.ts +66 -0
  250. package/src/tools/cache.ts +259 -0
  251. package/src/tools/index.ts +2 -0
  252. package/src/tools/performance.ts +948 -0
  253. package/src/types/client.ts +1 -0
  254. package/src/utils/toolWrapper.ts +11 -0
  255. package/src/utils/validation.ts +259 -0
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Comprehensive Input Validation and Sanitization System
3
+ * Provides security-focused validation for all MCP tool inputs
4
+ */
5
+
6
+ import { z } from 'zod';
7
+
8
+ // Common validation patterns
9
+ const URL_PATTERN = /^https?:\/\/[^\s<>'"{}|\\^`\[\]]+$/;
10
+ const EMAIL_PATTERN = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
11
+ const SLUG_PATTERN = /^[a-z0-9-]+$/;
12
+ const SCRIPT_PATTERN = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
13
+ const SQL_INJECTION_PATTERN = /('|(\\')|(;)|(\\x00)|(\\n)|(\\r)|(\\x1a)|(\\x22)|(\\x27)|(\\x5c)|(\\x60))/i;
14
+
15
+ /**
16
+ * Security validation schemas
17
+ */
18
+ export const SecuritySchemas = {
19
+ // Safe string with XSS protection
20
+ safeString: z.string()
21
+ .max(10000, 'String too long')
22
+ .refine(val => !SCRIPT_PATTERN.test(val), 'Script tags not allowed')
23
+ .refine(val => !val.includes('javascript:'), 'JavaScript URLs not allowed')
24
+ .refine(val => !val.includes('data:'), 'Data URLs not allowed')
25
+ .refine(val => !val.includes('onerror='), 'Event handlers not allowed')
26
+ .refine(val => !val.includes('onload='), 'Event handlers not allowed')
27
+ .refine(val => !val.includes('onfocus='), 'Event handlers not allowed'),
28
+
29
+ // HTML content with basic sanitization
30
+ htmlContent: z.string()
31
+ .max(100000, 'Content too long')
32
+ .refine(val => !SCRIPT_PATTERN.test(val), 'Script tags not allowed')
33
+ .refine(val => !val.includes('javascript:'), 'JavaScript URLs not allowed')
34
+ .refine(val => !val.includes('on[a-z]+='), 'Event handlers not allowed'),
35
+
36
+ // URL validation
37
+ url: z.string()
38
+ .url('Invalid URL format')
39
+ .regex(URL_PATTERN, 'URL contains invalid characters')
40
+ .refine(val => !val.includes('javascript:'), 'JavaScript URLs not allowed')
41
+ .refine(val => !val.includes('data:'), 'Data URLs not allowed'),
42
+
43
+ // Email validation
44
+ email: z.string()
45
+ .email('Invalid email format')
46
+ .regex(EMAIL_PATTERN, 'Email contains invalid characters')
47
+ .max(254, 'Email too long'),
48
+
49
+ // Slug validation (for URLs, usernames, etc.)
50
+ slug: z.string()
51
+ .min(1, 'Slug cannot be empty')
52
+ .max(100, 'Slug too long')
53
+ .regex(SLUG_PATTERN, 'Slug can only contain lowercase letters, numbers, and hyphens'),
54
+
55
+ // WordPress post/page content
56
+ wpContent: z.string()
57
+ .max(1000000, 'Content too long')
58
+ .refine(val => !SCRIPT_PATTERN.test(val), 'Script tags not allowed in content')
59
+ .refine(val => !val.includes('javascript:'), 'JavaScript URLs not allowed'),
60
+
61
+ // Site ID validation
62
+ siteId: z.string()
63
+ .min(1, 'Site ID cannot be empty')
64
+ .max(50, 'Site ID too long')
65
+ .regex(/^[a-zA-Z0-9\-_]+$/, 'Site ID can only contain letters, numbers, hyphens, and underscores'),
66
+
67
+ // WordPress ID (numeric)
68
+ wpId: z.number()
69
+ .int('ID must be an integer')
70
+ .positive('ID must be positive')
71
+ .max(999999999, 'ID too large'),
72
+
73
+ // Search query with SQL injection protection
74
+ searchQuery: z.string()
75
+ .max(500, 'Search query too long')
76
+ .refine(val => !SQL_INJECTION_PATTERN.test(val), 'Invalid characters in search query')
77
+ .refine(val => !val.includes('--'), 'SQL comments not allowed')
78
+ .refine(val => !val.includes('/*'), 'SQL comments not allowed'),
79
+
80
+ // File path validation
81
+ filePath: z.string()
82
+ .max(500, 'File path too long')
83
+ .refine(val => !val.includes('..'), 'Path traversal not allowed')
84
+ .refine(val => !val.includes('<'), 'Invalid characters in path')
85
+ .refine(val => !val.includes('>'), 'Invalid characters in path'),
86
+
87
+ // Password (for display/logging - never log actual passwords)
88
+ passwordMask: z.string()
89
+ .transform(() => '[REDACTED]'),
90
+
91
+ // WordPress application password format
92
+ appPassword: z.string()
93
+ .regex(/^[a-zA-Z0-9\s]{24}$/, 'Invalid application password format')
94
+ .transform(val => val.replace(/\s/g, ' ')) // Normalize spaces
95
+ };
96
+
97
+ /**
98
+ * Input sanitization functions
99
+ */
100
+ export class InputSanitizer {
101
+ /**
102
+ * Sanitize HTML content by removing dangerous elements
103
+ */
104
+ static sanitizeHtml(input: string): string {
105
+ return input
106
+ .replace(SCRIPT_PATTERN, '') // Remove script tags
107
+ .replace(/javascript:/gi, '') // Remove javascript: URLs
108
+ .replace(/data:/gi, '') // Remove data: URLs
109
+ .replace(/on[a-z]+\s*=/gi, '') // Remove event handlers
110
+ .replace(/<iframe[^>]*>/gi, '') // Remove iframes
111
+ .replace(/<object[^>]*>/gi, '') // Remove objects
112
+ .replace(/<embed[^>]*>/gi, ''); // Remove embeds
113
+ }
114
+
115
+ /**
116
+ * Sanitize search queries to prevent SQL injection
117
+ */
118
+ static sanitizeSearchQuery(query: string): string {
119
+ return query
120
+ .replace(/['"\\;]/g, '') // Remove quotes and backslashes
121
+ .replace(/--/g, '') // Remove SQL comments
122
+ .replace(/\/\*/g, '') // Remove SQL comments
123
+ .replace(/\*/g, '') // Remove wildcards
124
+ .trim()
125
+ .substring(0, 500); // Limit length
126
+ }
127
+
128
+ /**
129
+ * Sanitize file paths to prevent directory traversal
130
+ */
131
+ static sanitizeFilePath(path: string): string {
132
+ return path
133
+ .replace(/\.\./g, '') // Remove directory traversal
134
+ .replace(/[<>]/g, '') // Remove angle brackets
135
+ .replace(/[|&;$`\\]/g, '') // Remove shell metacharacters
136
+ .trim();
137
+ }
138
+
139
+ /**
140
+ * Encode output for safe display
141
+ */
142
+ static encodeOutput(input: string): string {
143
+ return input
144
+ .replace(/&/g, '&amp;')
145
+ .replace(/</g, '&lt;')
146
+ .replace(/>/g, '&gt;')
147
+ .replace(/"/g, '&quot;')
148
+ .replace(/'/g, '&#x27;');
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Security validation decorator for tool methods
154
+ */
155
+ export function validateSecurity(schema: z.ZodSchema) {
156
+ return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
157
+ const method = descriptor.value;
158
+
159
+ descriptor.value = async function (...args: any[]) {
160
+ try {
161
+ // Validate input parameters
162
+ const params = args[0] || {};
163
+ const validatedParams = schema.parse(params);
164
+
165
+ // Log security validation (without sensitive data)
166
+ console.log(`Security validation passed for ${propertyName}`, {
167
+ timestamp: new Date().toISOString(),
168
+ method: propertyName,
169
+ paramCount: Object.keys(validatedParams).length
170
+ });
171
+
172
+ // Call original method with validated params
173
+ return await method.call(this, validatedParams, ...args.slice(1));
174
+ } catch (error) {
175
+ // Log security validation failure
176
+ console.error(`Security validation failed for ${propertyName}`, {
177
+ timestamp: new Date().toISOString(),
178
+ method: propertyName,
179
+ error: error instanceof z.ZodError ? error.errors : (error instanceof Error ? error.message : String(error))
180
+ });
181
+
182
+ throw new SecurityValidationError(
183
+ `Security validation failed for ${propertyName}`,
184
+ error instanceof z.ZodError ? error.errors : [{ message: error instanceof Error ? error.message : String(error) }]
185
+ );
186
+ }
187
+ };
188
+
189
+ return descriptor;
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Custom security validation error
195
+ */
196
+ export class SecurityValidationError extends Error {
197
+ public readonly errors: any[];
198
+
199
+ constructor(message: string, errors: any[] = []) {
200
+ super(message);
201
+ this.name = 'SecurityValidationError';
202
+ this.errors = errors;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Tool-specific validation schemas
208
+ */
209
+ export const ToolSchemas = {
210
+ // Post creation/update
211
+ postData: z.object({
212
+ site: SecuritySchemas.siteId.optional(),
213
+ title: SecuritySchemas.safeString.optional(),
214
+ content: SecuritySchemas.wpContent.optional(),
215
+ excerpt: SecuritySchemas.safeString.optional(),
216
+ status: z.enum(['publish', 'draft', 'private', 'pending']).optional(),
217
+ slug: SecuritySchemas.slug.optional(),
218
+ categories: z.array(SecuritySchemas.wpId).optional(),
219
+ tags: z.array(SecuritySchemas.wpId).optional()
220
+ }),
221
+
222
+ // User creation/update
223
+ userData: z.object({
224
+ site: SecuritySchemas.siteId.optional(),
225
+ username: SecuritySchemas.slug,
226
+ email: SecuritySchemas.email,
227
+ password: SecuritySchemas.safeString.optional(),
228
+ roles: z.array(z.string()).optional(),
229
+ firstName: SecuritySchemas.safeString.optional(),
230
+ lastName: SecuritySchemas.safeString.optional()
231
+ }),
232
+
233
+ // Search parameters
234
+ searchParams: z.object({
235
+ site: SecuritySchemas.siteId.optional(),
236
+ query: SecuritySchemas.searchQuery,
237
+ type: z.enum(['post', 'page', 'any']).optional(),
238
+ limit: z.number().int().min(1).max(100).optional()
239
+ }),
240
+
241
+ // Media upload
242
+ mediaUpload: z.object({
243
+ site: SecuritySchemas.siteId.optional(),
244
+ filename: SecuritySchemas.filePath,
245
+ title: SecuritySchemas.safeString.optional(),
246
+ caption: SecuritySchemas.safeString.optional(),
247
+ description: SecuritySchemas.safeString.optional()
248
+ }),
249
+
250
+ // Site settings
251
+ siteSettings: z.object({
252
+ site: SecuritySchemas.siteId.optional(),
253
+ title: SecuritySchemas.safeString.optional(),
254
+ description: SecuritySchemas.safeString.optional(),
255
+ url: SecuritySchemas.url.optional(),
256
+ adminEmail: SecuritySchemas.email.optional()
257
+ }),
258
+
259
+ // Generic list parameters
260
+ listParams: z.object({
261
+ site: SecuritySchemas.siteId.optional(),
262
+ page: z.number().int().min(1).max(1000).optional(),
263
+ perPage: z.number().int().min(1).max(100).optional(),
264
+ search: SecuritySchemas.searchQuery.optional(),
265
+ orderBy: z.string().max(50).optional(),
266
+ order: z.enum(['asc', 'desc']).optional()
267
+ }),
268
+
269
+ // ID-based operations
270
+ idParams: z.object({
271
+ site: SecuritySchemas.siteId.optional(),
272
+ id: SecuritySchemas.wpId
273
+ })
274
+ };
275
+
276
+ /**
277
+ * Rate limiting and DoS protection
278
+ */
279
+ export class SecurityLimiter {
280
+ private static requestCounts = new Map<string, { count: number; resetTime: number }>();
281
+ private static readonly RATE_LIMIT = 1000; // requests per window
282
+ private static readonly WINDOW_MS = 60 * 1000; // 1 minute
283
+
284
+ /**
285
+ * Check if request is within rate limits
286
+ */
287
+ static checkRateLimit(identifier: string): boolean {
288
+ const now = Date.now();
289
+ const key = identifier;
290
+ const current = this.requestCounts.get(key);
291
+
292
+ if (!current || now > current.resetTime) {
293
+ this.requestCounts.set(key, { count: 1, resetTime: now + this.WINDOW_MS });
294
+ return true;
295
+ }
296
+
297
+ if (current.count >= this.RATE_LIMIT) {
298
+ return false;
299
+ }
300
+
301
+ current.count++;
302
+ return true;
303
+ }
304
+
305
+ /**
306
+ * Clean up expired rate limit entries
307
+ */
308
+ static cleanup(): void {
309
+ const now = Date.now();
310
+ for (const [key, data] of this.requestCounts.entries()) {
311
+ if (now > data.resetTime) {
312
+ this.requestCounts.delete(key);
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ // Start cleanup interval
319
+ setInterval(() => SecurityLimiter.cleanup(), 60000); // Clean up every minute
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Security configuration and constants for MCP WordPress
3
+ */
4
+
5
+ import { randomBytes } from 'crypto';
6
+
7
+ export const SecurityConfig = {
8
+ // Rate limiting
9
+ rateLimiting: {
10
+ default: {
11
+ windowMs: 60 * 1000, // 1 minute
12
+ maxRequests: 60
13
+ },
14
+ authentication: {
15
+ windowMs: 5 * 60 * 1000, // 5 minutes
16
+ maxAttempts: 5
17
+ },
18
+ upload: {
19
+ windowMs: 60 * 1000, // 1 minute
20
+ maxRequests: 10
21
+ }
22
+ },
23
+
24
+ // File upload restrictions
25
+ fileUpload: {
26
+ maxSizeMB: 10,
27
+ allowedMimeTypes: [
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
+ ],
41
+ // Dangerous file extensions to block
42
+ blockedExtensions: [
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
+ },
68
+
69
+ // Input validation
70
+ validation: {
71
+ maxStringLength: 1000,
72
+ maxTitleLength: 200,
73
+ maxContentLength: 50000,
74
+ maxExcerptLength: 500,
75
+ maxUrlLength: 2048,
76
+ maxUsernameLength: 60,
77
+ minUsernameLength: 3,
78
+ maxPasswordLength: 128,
79
+ minPasswordLength: 8
80
+ },
81
+
82
+ // Request timeouts (milliseconds)
83
+ timeouts: {
84
+ default: 30000, // 30 seconds
85
+ upload: 600000, // 10 minutes
86
+ auth: 10000 // 10 seconds
87
+ },
88
+
89
+ // Security headers
90
+ headers: {
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
+ },
97
+
98
+ // Error messages (generic to avoid information disclosure)
99
+ errorMessages: {
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
+ },
107
+
108
+ // Logging configuration
109
+ logging: {
110
+ // Fields to exclude from logs
111
+ excludeFields: [
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
+ ],
124
+ // Patterns to redact in log messages
125
+ redactPatterns: [
126
+ /password["\s:=]+["']?([^"'\s]+)["']?/gi,
127
+ /token["\s:=]+["']?([^"'\s]+)["']?/gi,
128
+ /secret["\s:=]+["']?([^"'\s]+)["']?/gi,
129
+ /key["\s:=]+["']?([^"'\s]+)["']?/gi
130
+ ]
131
+ },
132
+
133
+ // Cache configuration
134
+ cache: {
135
+ // Default cache settings
136
+ enabled: true,
137
+ maxSize: 1000, // Maximum number of cached entries
138
+ defaultTTL: 15 * 60 * 1000, // 15 minutes default TTL
139
+ enableLRU: true,
140
+ enableStats: true,
141
+
142
+ // TTL presets by data type (milliseconds)
143
+ ttlPresets: {
144
+ static: 4 * 60 * 60 * 1000, // 4 hours - site settings, user roles
145
+ semiStatic: 2 * 60 * 60 * 1000, // 2 hours - categories, tags, user profiles
146
+ dynamic: 15 * 60 * 1000, // 15 minutes - posts, pages, comments
147
+ session: 30 * 60 * 1000, // 30 minutes - authentication, current user
148
+ realtime: 60 * 1000 // 1 minute - real-time data
149
+ },
150
+
151
+ // Cache-Control headers by data type
152
+ cacheHeaders: {
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
+ },
159
+
160
+ // Invalidation settings
161
+ invalidation: {
162
+ enabled: true,
163
+ batchSize: 100, // Max events to process in one batch
164
+ queueTimeout: 5000, // Max time to wait before processing queue (ms)
165
+ enableCascading: true // Allow cascading invalidations
166
+ },
167
+
168
+ // Memory management
169
+ cleanup: {
170
+ interval: 60 * 1000, // Cleanup interval in milliseconds (1 minute)
171
+ maxMemoryMB: 50, // Maximum memory usage for cache
172
+ evictionThreshold: 0.8 // Start evicting when 80% full
173
+ }
174
+ }
175
+ };
176
+
177
+ /**
178
+ * Security utility functions
179
+ */
180
+ export class SecurityUtils {
181
+ /**
182
+ * Redact sensitive information from objects
183
+ */
184
+ static redactSensitiveData(obj: any): any {
185
+ if (typeof obj !== 'object' || obj === null) {
186
+ return obj;
187
+ }
188
+
189
+ const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
190
+
191
+ for (const key in redacted) {
192
+ if (
193
+ SecurityConfig.logging.excludeFields.some((field) =>
194
+ key.toLowerCase().includes(field.toLowerCase())
195
+ )
196
+ ) {
197
+ redacted[key] = '[REDACTED]';
198
+ } else if (typeof redacted[key] === 'object') {
199
+ redacted[key] = SecurityUtils.redactSensitiveData(redacted[key]);
200
+ }
201
+ }
202
+
203
+ return redacted;
204
+ }
205
+
206
+ /**
207
+ * Redact sensitive patterns from strings
208
+ */
209
+ static redactString(str: string): string {
210
+ let redacted = str;
211
+ for (const pattern of SecurityConfig.logging.redactPatterns) {
212
+ redacted = redacted.replace(pattern, (match, value) => {
213
+ return match.replace(value, '[REDACTED]');
214
+ });
215
+ }
216
+ return redacted;
217
+ }
218
+
219
+ /**
220
+ * Generate secure random strings
221
+ */
222
+ static generateSecureToken(length: number = 32): string {
223
+ const chars =
224
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
225
+ const array = new Uint8Array(length);
226
+
227
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
228
+ crypto.getRandomValues(array);
229
+ } else {
230
+ // Fallback for Node.js
231
+ const buffer = randomBytes(length);
232
+ array.set(buffer);
233
+ }
234
+
235
+ return Array.from(array, (byte) => chars[byte % chars.length]).join('');
236
+ }
237
+
238
+ /**
239
+ * Check if a file extension is allowed
240
+ */
241
+ static isFileExtensionAllowed(filename: string): boolean {
242
+ const ext = path.extname(filename).toLowerCase();
243
+ return !SecurityConfig.fileUpload.blockedExtensions.includes(ext);
244
+ }
245
+
246
+ /**
247
+ * Sanitize log output
248
+ */
249
+ static sanitizeForLog(data: any): any {
250
+ if (typeof data === 'string') {
251
+ return SecurityUtils.redactString(data);
252
+ }
253
+ if (typeof data === 'object') {
254
+ return SecurityUtils.redactSensitiveData(data);
255
+ }
256
+ return data;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Secure error handler that prevents information disclosure
262
+ */
263
+ export function createSecureError(
264
+ error: any,
265
+ fallbackMessage: string = SecurityConfig.errorMessages.serverError
266
+ ): Error {
267
+ // Log the actual error for debugging (with sanitization)
268
+ if (process.env.NODE_ENV !== 'production') {
269
+ console.error('Secure Error:', SecurityUtils.sanitizeForLog(error));
270
+ }
271
+
272
+ // Return generic error to prevent information disclosure
273
+ const secureError = new Error(fallbackMessage);
274
+
275
+ // Preserve error code if it's safe
276
+ if (error && typeof error.code === 'string' && !error.code.includes('_')) {
277
+ (secureError as any).code = error.code;
278
+ }
279
+
280
+ return secureError;
281
+ }
282
+
283
+ // Import path for file extension checking
284
+ import * as path from 'path';
285
+
286
+ /**
287
+ * Environment-specific security settings
288
+ */
289
+ export function getEnvironmentSecurity(): {
290
+ strictMode: boolean;
291
+ verboseErrors: boolean;
292
+ enforceHttps: boolean;
293
+ } {
294
+ const isProduction = process.env.NODE_ENV === 'production';
295
+
296
+ return {
297
+ strictMode: isProduction,
298
+ verboseErrors: !isProduction,
299
+ enforceHttps: isProduction
300
+ };
301
+ }
@@ -0,0 +1,74 @@
1
+ import { WordPressClient } from '../client/api.js';
2
+ import { getErrorMessage } from '../utils/error.js';
3
+
4
+ /**
5
+ * Service for testing WordPress client connections
6
+ * Handles connection validation and health checks
7
+ */
8
+ export class ConnectionTester {
9
+ /**
10
+ * Test connections to all configured WordPress sites
11
+ */
12
+ public static async testClientConnections(
13
+ wordpressClients: Map<string, WordPressClient>
14
+ ): Promise<void> {
15
+ console.error('INFO: Testing connections to all configured WordPress sites...');
16
+
17
+ const connectionPromises = Array.from(wordpressClients.entries()).map(
18
+ async ([siteId, client]) => {
19
+ try {
20
+ await client.ping();
21
+ console.error(`SUCCESS: Connection to site '${siteId}' successful.`);
22
+ } catch (error) {
23
+ console.error(`ERROR: Failed to connect to site '${siteId}': ${getErrorMessage(error)}`);
24
+
25
+ if (ConnectionTester.isAuthenticationError(error)) {
26
+ console.error(`Authentication may have failed for site '${siteId}'. Please check credentials.`);
27
+ }
28
+ }
29
+ }
30
+ );
31
+
32
+ await Promise.all(connectionPromises);
33
+ console.error('INFO: Connection tests complete.');
34
+ }
35
+
36
+ /**
37
+ * Check if error is authentication-related
38
+ */
39
+ private static isAuthenticationError(error: any): boolean {
40
+ if (error?.response?.status && [401, 403].includes(error.response.status)) {
41
+ return true;
42
+ }
43
+ return error?.code === 'WORDPRESS_AUTH_ERROR';
44
+ }
45
+
46
+ /**
47
+ * Perform health check for a specific client
48
+ */
49
+ public static async healthCheck(client: WordPressClient): Promise<boolean> {
50
+ try {
51
+ await client.ping();
52
+ return true;
53
+ } catch (error) {
54
+ console.error(`Health check failed: ${getErrorMessage(error)}`);
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Perform health checks for all clients
61
+ */
62
+ public static async healthCheckAll(
63
+ wordpressClients: Map<string, WordPressClient>
64
+ ): Promise<Map<string, boolean>> {
65
+ const results = new Map<string, boolean>();
66
+
67
+ for (const [siteId, client] of wordpressClients.entries()) {
68
+ const isHealthy = await ConnectionTester.healthCheck(client);
69
+ results.set(siteId, isHealthy);
70
+ }
71
+
72
+ return results;
73
+ }
74
+ }