monomind 1.11.14 → 1.13.0

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 (179) hide show
  1. package/.claude/agents/generated/channel-intelligence-director.md +87 -0
  2. package/.claude/agents/generated/chief-growth-officer.md +88 -0
  3. package/.claude/agents/generated/content-seo-strategist.md +90 -0
  4. package/.claude/agents/generated/developer-community-strategist.md +91 -0
  5. package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
  6. package/.claude/agents/generated/social-media-strategist.md +91 -0
  7. package/.claude/agents/generated/video-visual-strategist.md +90 -0
  8. package/.claude/commands/mastermind/master.md +1 -1
  9. package/.claude/helpers/auto-memory-hook.mjs +13 -4
  10. package/.claude/helpers/control-start.cjs +5 -0
  11. package/.claude/helpers/event-logger.cjs +114 -0
  12. package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
  13. package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
  14. package/.claude/helpers/handlers/compact-handler.cjs +2 -0
  15. package/.claude/helpers/handlers/edit-handler.cjs +1 -1
  16. package/.claude/helpers/handlers/gates-handler.cjs +3 -0
  17. package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
  18. package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
  19. package/.claude/helpers/handlers/route-handler.cjs +24 -10
  20. package/.claude/helpers/handlers/session-handler.cjs +11 -4
  21. package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
  22. package/.claude/helpers/handlers/task-handler.cjs +13 -5
  23. package/.claude/helpers/hook-handler.cjs +40 -0
  24. package/.claude/helpers/intelligence.cjs +130 -53
  25. package/.claude/helpers/loop-tracker.cjs +15 -3
  26. package/.claude/helpers/memory-palace.cjs +461 -0
  27. package/.claude/helpers/memory.cjs +138 -14
  28. package/.claude/helpers/metrics-db.mjs +87 -0
  29. package/.claude/helpers/router.cjs +300 -42
  30. package/.claude/helpers/session.cjs +89 -30
  31. package/.claude/helpers/statusline.cjs +148 -4
  32. package/.claude/helpers/toggle-statusline.cjs +73 -0
  33. package/.claude/helpers/token-tracker.cjs +934 -0
  34. package/.claude/helpers/utils/micro-agents.cjs +20 -4
  35. package/.claude/helpers/utils/monograph.cjs +39 -4
  36. package/.claude/helpers/utils/telemetry.cjs +3 -3
  37. package/.claude/scheduled_tasks.lock +1 -1
  38. package/.claude/settings.json +92 -1
  39. package/.claude/skills/mastermind/_protocol.md +25 -15
  40. package/.claude/skills/mastermind/architect.md +3 -3
  41. package/.claude/skills/mastermind/autodev.md +4 -2
  42. package/.claude/skills/mastermind/idea.md +10 -0
  43. package/.claude/skills/mastermind/ops.md +3 -3
  44. package/.claude/skills/mastermind/runorg.md +153 -86
  45. package/package.json +20 -3
  46. package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
  47. package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
  48. package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
  49. package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
  50. package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
  51. package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
  52. package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
  53. package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
  54. package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
  55. package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
  56. package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
  57. package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
  58. package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
  59. package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
  60. package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
  61. package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
  62. package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
  63. package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
  64. package/packages/@monomind/cli/dist/src/commands/doctor.js +192 -23
  65. package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
  66. package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
  67. package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
  68. package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
  69. package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
  70. package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
  71. package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
  72. package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
  73. package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
  74. package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
  75. package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
  76. package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
  77. package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
  78. package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
  79. package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
  80. package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
  81. package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
  82. package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
  83. package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
  84. package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
  85. package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
  86. package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
  87. package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
  88. package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
  89. package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
  90. package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
  91. package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
  92. package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
  93. package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
  94. package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
  95. package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
  96. package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
  97. package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
  98. package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
  99. package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
  100. package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
  101. package/packages/@monomind/cli/dist/src/index.js +7 -3
  102. package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
  103. package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
  104. package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
  105. package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
  106. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
  107. package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
  108. package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
  109. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
  110. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
  111. package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
  112. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
  113. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
  114. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
  115. package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
  116. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
  117. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
  118. package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
  119. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
  120. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +476 -62
  121. package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
  122. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
  123. package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
  124. package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
  125. package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
  126. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
  127. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
  128. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
  129. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
  130. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
  131. package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
  132. package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
  133. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
  134. package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
  135. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
  136. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
  137. package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
  138. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
  139. package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
  140. package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
  141. package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
  142. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
  143. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
  144. package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
  145. package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
  146. package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
  147. package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
  148. package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
  149. package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
  150. package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
  151. package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
  152. package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
  153. package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
  154. package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
  155. package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
  156. package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
  157. package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
  158. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
  159. package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
  160. package/packages/@monomind/cli/dist/src/services/worker-daemon.js +348 -17
  161. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
  162. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
  163. package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
  164. package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
  165. package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +8 -2
  166. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
  167. package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
  168. package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
  169. package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
  170. package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
  171. package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
  172. package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
  173. package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
  174. package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
  175. package/packages/@monomind/cli/dist/src/update/index.js +18 -1
  176. package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
  177. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
  178. package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
  179. package/packages/@monomind/cli/package.json +2 -3
@@ -147,7 +147,12 @@ export const neuralTools = [
147
147
  },
148
148
  handler: async (input) => {
149
149
  const store = loadNeuralStore();
150
- const modelId = input.modelId || `model-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
150
+ // Cap modelId to prevent DoS via oversized object keys written to the neural store JSON.
151
+ const MAX_MODEL_ID_LEN = 256;
152
+ const rawModelId = input.modelId || `model-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
153
+ const modelId = typeof rawModelId === 'string' && rawModelId.length > MAX_MODEL_ID_LEN
154
+ ? rawModelId.slice(0, MAX_MODEL_ID_LEN)
155
+ : rawModelId;
151
156
  const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
152
157
  if (RESERVED_KEYS.has(modelId)) {
153
158
  return { success: false, error: 'Invalid modelId' };
@@ -160,8 +165,19 @@ export const neuralTools = [
160
165
  if (Object.keys(store.models ?? {}).length >= MAX_MODELS) {
161
166
  return { success: false, error: `Model store full (max ${MAX_MODELS}). Delete old models first.` };
162
167
  }
163
- const modelType = input.modelType;
164
- const epochs = input.epochs || 10;
168
+ // Runtime-validate modelType against the allowed enum. The JSON schema
169
+ // declares an enum but callers that bypass schema validation (e.g. direct
170
+ // MCP calls) can pass arbitrary strings, which would be stored verbatim.
171
+ const VALID_MODEL_TYPES = new Set(['moe', 'transformer', 'classifier', 'embedding']);
172
+ const rawModelType = input.modelType;
173
+ if (!rawModelType || !VALID_MODEL_TYPES.has(rawModelType)) {
174
+ return { success: false, error: `Invalid modelType "${rawModelType}". Must be one of: moe, transformer, classifier, embedding` };
175
+ }
176
+ const modelType = rawModelType;
177
+ // Cap epochs to prevent storing absurdly large numbers in the JSON store.
178
+ const MAX_EPOCHS = 10000;
179
+ const rawEpochs = typeof input.epochs === 'number' && Number.isFinite(input.epochs) ? input.epochs : 10;
180
+ const epochs = Math.max(1, Math.min(Math.floor(rawEpochs), MAX_EPOCHS));
165
181
  const model = {
166
182
  id: modelId,
167
183
  name: `${modelType}-model`,
@@ -225,7 +241,7 @@ export const neuralTools = [
225
241
  try {
226
242
  const patternsPath = join(getNeuralDir(), PATTERNS_FILE);
227
243
  let existing = [];
228
- if (existsSync(patternsPath)) {
244
+ if (existsSync(patternsPath) && statSync(patternsPath).size <= MAX_NEURAL_STORE_BYTES) {
229
245
  const raw = readFileSync(patternsPath, 'utf-8');
230
246
  const parsed = JSON.parse(raw);
231
247
  if (Array.isArray(parsed))
@@ -282,7 +298,12 @@ export const neuralTools = [
282
298
  },
283
299
  handler: async (input) => {
284
300
  const store = loadNeuralStore();
285
- const modelId = input.modelId;
301
+ // Cap modelId to prevent O(n) hash/compare cost on absurdly long keys.
302
+ const MAX_MODEL_ID_LEN_PRED = 256;
303
+ const rawModelIdPred = input.modelId;
304
+ const modelId = typeof rawModelIdPred === 'string' && rawModelIdPred.length > MAX_MODEL_ID_LEN_PRED
305
+ ? rawModelIdPred.slice(0, MAX_MODEL_ID_LEN_PRED)
306
+ : rawModelIdPred;
286
307
  const inputText = typeof input.input === 'string' ? input.input.slice(0, 16 * 1024) : '';
287
308
  const topK = Math.max(1, Math.min(input.topK || 3, 50));
288
309
  const RESERVED_KEYS_PRED = new Set(['__proto__', 'constructor', 'prototype']);
@@ -309,7 +330,7 @@ export const neuralTools = [
309
330
  const cliPatternsRaw = [];
310
331
  try {
311
332
  const cliPath = join(getNeuralDir(), PATTERNS_FILE);
312
- if (existsSync(cliPath)) {
333
+ if (existsSync(cliPath) && statSync(cliPath).size <= MAX_NEURAL_STORE_BYTES) {
313
334
  const raw = JSON.parse(readFileSync(cliPath, 'utf-8'));
314
335
  if (Array.isArray(raw)) {
315
336
  const mcpIds = new Set(mcpPatterns.map(p => p.id));
@@ -412,7 +433,12 @@ export const neuralTools = [
412
433
  return { success: false, error: `Pattern store full (max ${MAX_PATTERNS}). Run neural_compress first.` };
413
434
  }
414
435
  const patternId = `pattern-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
415
- const patternName = input.name || 'Unnamed pattern';
436
+ // Cap name length to prevent DoS in generateEmbedding (hash path is O(n))
437
+ const MAX_PATTERN_NAME_LENGTH = 16 * 1024; // 16 KB
438
+ const rawPatternName = input.name || 'Unnamed pattern';
439
+ const patternName = typeof rawPatternName === 'string' && rawPatternName.length > MAX_PATTERN_NAME_LENGTH
440
+ ? rawPatternName.slice(0, MAX_PATTERN_NAME_LENGTH)
441
+ : rawPatternName;
416
442
  // Generate embedding from pattern name/content
417
443
  const embedding = await generateEmbedding(patternName, 384);
418
444
  const pattern = {
@@ -437,7 +463,12 @@ export const neuralTools = [
437
463
  };
438
464
  }
439
465
  if (action === 'search') {
440
- const query = input.query;
466
+ // Cap query length to prevent DoS in generateEmbedding (hash path is O(n))
467
+ const MAX_SEARCH_QUERY_LENGTH = 16 * 1024; // 16 KB — matches neural_predict cap
468
+ const rawQuery = input.query;
469
+ const query = typeof rawQuery === 'string' && rawQuery.length > MAX_SEARCH_QUERY_LENGTH
470
+ ? rawQuery.slice(0, MAX_SEARCH_QUERY_LENGTH)
471
+ : rawQuery;
441
472
  // Generate query embedding for real similarity search
442
473
  const queryEmbedding = await generateEmbedding(query, 384);
443
474
  // Calculate REAL cosine similarity against stored patterns
@@ -606,7 +637,11 @@ export const neuralTools = [
606
637
  handler: async (input) => {
607
638
  const store = loadNeuralStore();
608
639
  if (input.modelId) {
609
- const modelId = input.modelId;
640
+ const MAX_MODEL_ID_LEN_STATUS = 256;
641
+ const rawModelIdStatus = input.modelId;
642
+ const modelId = typeof rawModelIdStatus === 'string' && rawModelIdStatus.length > MAX_MODEL_ID_LEN_STATUS
643
+ ? rawModelIdStatus.slice(0, MAX_MODEL_ID_LEN_STATUS)
644
+ : rawModelIdStatus;
610
645
  if (NEURAL_RESERVED_KEYS.has(modelId)) {
611
646
  return { success: false, error: 'Invalid modelId' };
612
647
  }
@@ -12,7 +12,7 @@
12
12
  * Note: Some optimization suggestions are illustrative
13
13
  */
14
14
  import { getProjectCwd } from './types.js';
15
- import { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync, readdirSync, mkdirSync } from 'node:fs';
15
+ import { existsSync, readFileSync, statSync, writeFileSync, renameSync, unlinkSync, readdirSync, mkdirSync } from 'node:fs';
16
16
  import { join } from 'node:path';
17
17
  import * as os from 'node:os';
18
18
  // Storage paths
@@ -32,10 +32,11 @@ function ensurePerfDir() {
32
32
  mkdirSync(dir, { recursive: true });
33
33
  }
34
34
  }
35
+ const MAX_PERF_STORE_BYTES = 50 * 1024 * 1024; // 50 MB
35
36
  function loadPerfStore() {
36
37
  try {
37
38
  const path = getPerfPath();
38
- if (existsSync(path)) {
39
+ if (existsSync(path) && statSync(path).size <= MAX_PERF_STORE_BYTES) {
39
40
  return JSON.parse(readFileSync(path, 'utf-8'));
40
41
  }
41
42
  }
@@ -226,8 +227,20 @@ export const performanceTools = [
226
227
  },
227
228
  handler: async (input) => {
228
229
  const store = loadPerfStore();
229
- const suite = input.suite || 'all';
230
- const iterations = input.iterations || 100;
230
+ // Validate suite against enum to prevent uncapped string from being stored
231
+ // as a benchmark key on disk.
232
+ const VALID_SUITES = new Set(['all', 'memory', 'neural', 'swarm', 'io']);
233
+ const rawSuite = input.suite || 'all';
234
+ const suite = VALID_SUITES.has(rawSuite) ? rawSuite : 'all';
235
+ // Cap iterations to prevent event-loop blocking DoS. The `neural`
236
+ // suite runs an O(n³) 64×64 matrix multiply per iteration; at 10 000
237
+ // iterations it already completes in milliseconds. Uncapped, a caller
238
+ // could pass iterations=9_999_999 and block the process for minutes.
239
+ const MAX_BENCHMARK_ITERATIONS = 10_000;
240
+ const rawIterations = typeof input.iterations === 'number' ? input.iterations : 100;
241
+ const iterations = Number.isFinite(rawIterations) && rawIterations > 0
242
+ ? Math.min(Math.floor(rawIterations), MAX_BENCHMARK_ITERATIONS)
243
+ : 100;
231
244
  const warmup = input.warmup !== false;
232
245
  // REAL benchmark functions
233
246
  const benchmarkFunctions = {
@@ -356,7 +369,11 @@ export const performanceTools = [
356
369
  },
357
370
  },
358
371
  handler: async (input) => {
359
- const target = input.target || 'all';
372
+ // Validate target against enum to prevent arbitrary string from being
373
+ // stored in the perf store and echoed in the response.
374
+ const VALID_PROFILE_TARGETS = new Set(['all', 'memory', 'io', 'cpu']);
375
+ const rawTarget = input.target || 'all';
376
+ const target = VALID_PROFILE_TARGETS.has(rawTarget) ? rawTarget : 'all';
360
377
  const durationSec = Math.min(input.duration || 1, 10);
361
378
  const durationMs = durationSec * 1000;
362
379
  const cpuBefore = process.cpuUsage();
@@ -457,7 +474,11 @@ export const performanceTools = [
457
474
  },
458
475
  },
459
476
  handler: async (input) => {
460
- const target = input.target || 'all';
477
+ // Validate target against enum the value is echoed in the response and
478
+ // stored to the perf store; an unvalidated string could inflate disk state.
479
+ const VALID_OPT_TARGETS = new Set(['all', 'memory', 'latency', 'throughput']);
480
+ const rawOptTarget = input.target || 'all';
481
+ const target = VALID_OPT_TARGETS.has(rawOptTarget) ? rawOptTarget : 'all';
461
482
  const aggressive = input.aggressive === true;
462
483
  // Snapshot system state BEFORE optimizations
463
484
  const loadBefore = os.loadavg();
@@ -559,8 +580,15 @@ export const performanceTools = [
559
580
  },
560
581
  },
561
582
  handler: async (input) => {
562
- const metric = input.metric || 'all';
563
- const aggregation = input.aggregation || 'avg';
583
+ // Validate metric and aggregation against their enums. Both values are
584
+ // later used as property index keys on plain objects — an attacker who
585
+ // passes "__proto__" or "constructor" could cause prototype pollution.
586
+ const VALID_METRICS = new Set(['cpu', 'memory', 'latency', 'throughput', 'all']);
587
+ const VALID_AGGREGATIONS = new Set(['avg', 'min', 'max', 'p50', 'p95', 'p99']);
588
+ const rawMetric = input.metric || 'all';
589
+ const metric = VALID_METRICS.has(rawMetric) ? rawMetric : 'all';
590
+ const rawAggregation = input.aggregation || 'avg';
591
+ const aggregation = VALID_AGGREGATIONS.has(rawAggregation) ? rawAggregation : 'avg';
564
592
  // Get REAL system metrics
565
593
  const memUsage = process.memoryUsage();
566
594
  const loadAvg = os.loadavg();
@@ -641,11 +669,18 @@ export const performanceTools = [
641
669
  timestamp: new Date().toISOString(),
642
670
  };
643
671
  }
644
- const selectedMetric = allMetrics[metric];
672
+ // Use Object.hasOwn to prevent prototype chain traversal when indexing
673
+ // by caller-supplied metric/aggregation strings.
674
+ const selectedMetric = Object.hasOwn(allMetrics, metric)
675
+ ? allMetrics[metric]
676
+ : allMetrics.cpu;
677
+ const aggValue = Object.hasOwn(selectedMetric, aggregation)
678
+ ? selectedMetric[aggregation]
679
+ : undefined;
645
680
  return {
646
681
  _real: ['cpu', 'memory'].includes(metric),
647
682
  metric,
648
- value: selectedMetric[aggregation],
683
+ value: aggValue,
649
684
  unit: selectedMetric.unit,
650
685
  details: selectedMetric,
651
686
  timestamp: new Date().toISOString(),
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @module @monomind/cli/mcp-tools/progress
7
7
  */
8
- import { existsSync, readdirSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'fs';
8
+ import { existsSync, readdirSync, readFileSync, writeFileSync, renameSync, mkdirSync, statSync } from 'fs';
9
9
  import { join, basename, dirname } from 'path';
10
10
  import { fileURLToPath } from 'url';
11
11
  // Get project root - handles both src and dist paths
@@ -53,6 +53,9 @@ function countFilesAndLines(dir, ext = '.ts') {
53
53
  else if (entry.isFile() && entry.name.endsWith(ext)) {
54
54
  files++;
55
55
  try {
56
+ // Skip files > 1 MB to avoid loading generated/minified bundles into memory.
57
+ if (statSync(fullPath).size > 1024 * 1024)
58
+ continue;
56
59
  const content = readFileSync(fullPath, 'utf-8');
57
60
  lines += content.split('\n').length;
58
61
  }
@@ -120,7 +123,7 @@ async function calculateProgress() {
120
123
  // Count CLI commands (from commands/index.ts)
121
124
  let cliCommands = 28; // Default to known count
122
125
  const commandsIndexPath = join(PACKAGES_DIR, '@monomind/cli/src/commands/index.ts');
123
- if (existsSync(commandsIndexPath)) {
126
+ if (existsSync(commandsIndexPath) && statSync(commandsIndexPath).size <= 1024 * 1024) {
124
127
  try {
125
128
  const content = readFileSync(commandsIndexPath, 'utf-8');
126
129
  const matches = content.match(/export const commands.*\[([^\]]+)\]/s);
@@ -133,7 +136,7 @@ async function calculateProgress() {
133
136
  // Count MCP tools
134
137
  let mcpTools = 100; // Approximate
135
138
  const toolsIndexPath = join(PACKAGES_DIR, '@monomind/cli/src/mcp-tools/index.ts');
136
- if (existsSync(toolsIndexPath)) {
139
+ if (existsSync(toolsIndexPath) && statSync(toolsIndexPath).size <= 1024 * 1024) {
137
140
  try {
138
141
  const content = readFileSync(toolsIndexPath, 'utf-8');
139
142
  mcpTools = (content.match(/export.*Tools/g) || []).length * 10 || 100;
@@ -143,7 +146,7 @@ async function calculateProgress() {
143
146
  // Count hooks subcommands (count const *Command definitions)
144
147
  let hooksSubcommands = 27; // Default to documented count
145
148
  const hooksPath = join(PACKAGES_DIR, '@monomind/cli/src/commands/hooks.ts');
146
- if (existsSync(hooksPath)) {
149
+ if (existsSync(hooksPath) && statSync(hooksPath).size <= 1024 * 1024) {
147
150
  try {
148
151
  const content = readFileSync(hooksPath, 'utf-8');
149
152
  // Count command definitions like "const fooCommand: Command = {"
@@ -4,6 +4,10 @@
4
4
  * Used by system_metrics to report real request counts.
5
5
  */
6
6
  const MAX_TRACKED_TOOLS = 500;
7
+ const MAX_TOOL_NAME_LEN = 256;
8
+ // Keys that would corrupt Object.prototype or its constructor if used as plain
9
+ // object property keys without Object.hasOwn() protection.
10
+ const FORBIDDEN_TOOL_NAMES = new Set(['__proto__', 'constructor', 'prototype']);
7
11
  let counts = {
8
12
  total: 0,
9
13
  success: 0,
@@ -17,7 +21,17 @@ export function trackRequest(toolName, success) {
17
21
  counts.success++;
18
22
  else
19
23
  counts.errors++;
20
- if (Object.keys(counts.byTool).length < MAX_TRACKED_TOOLS || toolName in counts.byTool) {
24
+ // Guard against prototype pollution via toolName.
25
+ // The previous `toolName in counts.byTool` check traversed the prototype
26
+ // chain, so "__proto__" was always truthy and could corrupt Object.prototype.
27
+ // Use Object.hasOwn() instead, and reject forbidden key names outright.
28
+ if (typeof toolName !== 'string' ||
29
+ toolName.length === 0 ||
30
+ toolName.length > MAX_TOOL_NAME_LEN ||
31
+ FORBIDDEN_TOOL_NAMES.has(toolName)) {
32
+ return; // Drop invalid tool names silently
33
+ }
34
+ if (Object.keys(counts.byTool).length < MAX_TRACKED_TOOLS || Object.hasOwn(counts.byTool, toolName)) {
21
35
  counts.byTool[toolName] = (counts.byTool[toolName] || 0) + 1;
22
36
  }
23
37
  }
@@ -17,6 +17,21 @@ const require = createRequire(import.meta.url);
17
17
  let monofenceInstance = null;
18
18
  // Track if we've attempted install this session
19
19
  let installAttempted = false;
20
+ // ── Security input bounds ─────────────────────────────────────────────────────
21
+ // MonoFence runs multiple regex patterns over the entire input string (O(n × P)
22
+ // complexity where P is the pattern count). Uncapped input enables ReDoS-style
23
+ // denial-of-service. 64 KB is more than enough for any real threat-scan
24
+ // payload while preventing CPU exhaustion from megabyte-scale inputs.
25
+ const MAX_SECURITY_INPUT_LEN = 64 * 1024; // 64 KB
26
+ const MAX_SECURITY_K = 100;
27
+ const MAX_SECURITY_VERDICT_LEN = 512;
28
+ const MAX_SECURITY_THREAT_TYPE_LEN = 256;
29
+ const MAX_SECURITY_MITIGATION_STRATEGY_LEN = 512;
30
+ function capSecurityInput(raw, fieldName = 'input') {
31
+ if (typeof raw !== 'string')
32
+ throw new Error(`${fieldName} must be a string`);
33
+ return raw.length > MAX_SECURITY_INPUT_LEN ? raw.slice(0, MAX_SECURITY_INPUT_LEN) : raw;
34
+ }
20
35
  /**
21
36
  * Get or create MonoFence instance (throws if unavailable)
22
37
  */
@@ -79,7 +94,13 @@ const monofenceScanTool = {
79
94
  required: ['input'],
80
95
  },
81
96
  handler: async (args) => {
82
- const input = args.input;
97
+ let input;
98
+ try {
99
+ input = capSecurityInput(args.input);
100
+ }
101
+ catch (e) {
102
+ return { content: [{ type: 'text', text: JSON.stringify({ error: e.message }) }], isError: true };
103
+ }
83
104
  const quick = args.quick;
84
105
  try {
85
106
  const defender = await getMonoFence();
@@ -153,9 +174,16 @@ const monofenceAnalyzeTool = {
153
174
  required: ['input'],
154
175
  },
155
176
  handler: async (args) => {
156
- const input = args.input;
177
+ let input;
178
+ try {
179
+ input = capSecurityInput(args.input);
180
+ }
181
+ catch (e) {
182
+ return { content: [{ type: 'text', text: JSON.stringify({ error: e.message }) }], isError: true };
183
+ }
157
184
  const searchSimilar = args.searchSimilar !== false;
158
- const k = args.k || 5;
185
+ const rawK = args.k || 5;
186
+ const k = Number.isFinite(rawK) && rawK > 0 ? Math.min(Math.floor(rawK), MAX_SECURITY_K) : 5;
159
187
  try {
160
188
  const defender = await getMonoFence();
161
189
  const result = await defender.detect(input);
@@ -282,11 +310,23 @@ const monofenceLearnTool = {
282
310
  required: ['input', 'wasAccurate'],
283
311
  },
284
312
  handler: async (args) => {
285
- const input = args.input;
313
+ let input;
314
+ try {
315
+ input = capSecurityInput(args.input);
316
+ }
317
+ catch (e) {
318
+ return { content: [{ type: 'text', text: JSON.stringify({ error: e.message }) }], isError: true };
319
+ }
286
320
  const wasAccurate = args.wasAccurate;
287
- const verdict = args.verdict;
288
- const threatType = args.threatType;
289
- const mitigationStrategy = args.mitigationStrategy;
321
+ const rawVerdict = args.verdict;
322
+ const verdict = typeof rawVerdict === 'string' && rawVerdict.length > MAX_SECURITY_VERDICT_LEN
323
+ ? rawVerdict.slice(0, MAX_SECURITY_VERDICT_LEN) : rawVerdict;
324
+ const rawThreatType = args.threatType;
325
+ const threatType = typeof rawThreatType === 'string' && rawThreatType.length > MAX_SECURITY_THREAT_TYPE_LEN
326
+ ? rawThreatType.slice(0, MAX_SECURITY_THREAT_TYPE_LEN) : rawThreatType;
327
+ const rawMitigationStrategy = args.mitigationStrategy;
328
+ const mitigationStrategy = typeof rawMitigationStrategy === 'string' && rawMitigationStrategy.length > MAX_SECURITY_MITIGATION_STRATEGY_LEN
329
+ ? rawMitigationStrategy.slice(0, MAX_SECURITY_MITIGATION_STRATEGY_LEN) : rawMitigationStrategy;
290
330
  const mitigationSuccess = args.mitigationSuccess;
291
331
  try {
292
332
  const defender = await getMonoFence();
@@ -344,7 +384,13 @@ const monofenceIsSafeTool = {
344
384
  required: ['input'],
345
385
  },
346
386
  handler: async (args) => {
347
- const input = args.input;
387
+ let input;
388
+ try {
389
+ input = capSecurityInput(args.input);
390
+ }
391
+ catch (e) {
392
+ return { content: [{ type: 'text', text: JSON.stringify({ error: e.message }) }], isError: true };
393
+ }
348
394
  try {
349
395
  await getMonoFence(); // triggers auto-install if package is missing
350
396
  const { isSafe } = await import('monofence-ai');
@@ -384,7 +430,13 @@ const monofenceHasPIITool = {
384
430
  required: ['input'],
385
431
  },
386
432
  handler: async (args) => {
387
- const input = args.input;
433
+ let input;
434
+ try {
435
+ input = capSecurityInput(args.input);
436
+ }
437
+ catch (e) {
438
+ return { content: [{ type: 'text', text: JSON.stringify({ error: e.message }) }], isError: true };
439
+ }
388
440
  try {
389
441
  const defender = await getMonoFence();
390
442
  const hasPII = defender.hasPII(input);
@@ -131,6 +131,19 @@ export const sessionTools = [
131
131
  },
132
132
  handler: async (input) => {
133
133
  const sessionId = `session-${Date.now()}-${randomBytes(6).toString('hex')}`;
134
+ // Cap name and description: both are persisted verbatim to the session
135
+ // JSON file on disk. Without a cap, an attacker can inflate the session
136
+ // store by supplying a very long name or description.
137
+ const MAX_SESSION_NAME_LEN = 256;
138
+ const MAX_SESSION_DESC_LEN = 4 * 1024;
139
+ const rawSessionName = input.name;
140
+ const sessionName = typeof rawSessionName === 'string' && rawSessionName.length > MAX_SESSION_NAME_LEN
141
+ ? rawSessionName.slice(0, MAX_SESSION_NAME_LEN)
142
+ : rawSessionName;
143
+ const rawSessionDesc = input.description;
144
+ const sessionDesc = typeof rawSessionDesc === 'string' && rawSessionDesc.length > MAX_SESSION_DESC_LEN
145
+ ? rawSessionDesc.slice(0, MAX_SESSION_DESC_LEN)
146
+ : rawSessionDesc;
134
147
  // Load related data based on options
135
148
  const data = loadRelatedStores({
136
149
  includeMemory: input.includeMemory,
@@ -146,8 +159,8 @@ export const sessionTools = [
146
159
  };
147
160
  const session = {
148
161
  sessionId,
149
- name: input.name,
150
- description: input.description,
162
+ name: sessionName,
163
+ description: sessionDesc,
151
164
  savedAt: new Date().toISOString(),
152
165
  stats,
153
166
  data: Object.keys(data).length > 0 ? data : undefined,
@@ -211,17 +224,25 @@ export const sessionTools = [
211
224
  const { storeEntry } = await import('../memory/memory-initializer.js');
212
225
  const memoryData = session.data.memory;
213
226
  if (memoryData.entries) {
227
+ // Cap individual key and value lengths before writing to the DB.
228
+ // A malicious or corrupted session file could contain arbitrarily
229
+ // long strings; without caps these flow straight into the HNSW
230
+ // embedder and SQL layer, causing OOM or DoS.
231
+ const MAX_RESTORE_KEY = 1_000;
232
+ const MAX_RESTORE_VALUE = 100_000;
233
+ const MAX_RESTORE_NS = 200;
214
234
  for (const entry of Object.values(memoryData.entries)) {
215
- const key = entry.key || entry.id || '';
216
- const value = entry.value || entry.content || '';
217
- if (key && value) {
218
- await storeEntry({
219
- key,
220
- value,
221
- namespace: entry.namespace || 'restored',
222
- upsert: true,
223
- });
224
- }
235
+ let key = entry.key || entry.id || '';
236
+ let value = entry.value || entry.content || '';
237
+ if (!key || !value)
238
+ continue;
239
+ if (key.length > MAX_RESTORE_KEY)
240
+ key = key.slice(0, MAX_RESTORE_KEY);
241
+ if (value.length > MAX_RESTORE_VALUE)
242
+ value = value.slice(0, MAX_RESTORE_VALUE);
243
+ const rawNs = entry.namespace || 'restored';
244
+ const namespace = rawNs.length > MAX_RESTORE_NS ? rawNs.slice(0, MAX_RESTORE_NS) : rawNs;
245
+ await storeEntry({ key, value, namespace, upsert: true });
225
246
  }
226
247
  }
227
248
  }
@@ -353,14 +374,24 @@ export const sessionTools = [
353
374
  const session = loadSession(sessionId);
354
375
  if (session) {
355
376
  const path = getSessionPath(sessionId);
356
- const stat = statSync(path);
377
+ // Guard against TOCTOU: the file could be deleted between loadSession()
378
+ // and statSync(). Catch ENOENT (and any other fs error) so the MCP
379
+ // handler never throws an unhandled exception; callers get a clean
380
+ // response instead of a server-side crash.
381
+ let fileSize = 0;
382
+ try {
383
+ fileSize = statSync(path).size;
384
+ }
385
+ catch {
386
+ // File deleted or inaccessible after loadSession succeeded
387
+ }
357
388
  return {
358
389
  sessionId: session.sessionId,
359
390
  name: session.name,
360
391
  description: session.description,
361
392
  savedAt: session.savedAt,
362
393
  stats: session.stats,
363
- fileSize: stat.size,
394
+ fileSize,
364
395
  hasData: {
365
396
  memory: !!session.data?.memory,
366
397
  tasks: !!session.data?.tasks,
@@ -64,7 +64,13 @@ export const swarmTools = [
64
64
  handler: async (input) => {
65
65
  const topology = input.topology || 'hierarchical-mesh';
66
66
  const maxAgents = Math.min(Math.max(input.maxAgents || 8, 1), 50);
67
- const strategy = input.strategy || 'specialized';
67
+ // Cap strategy and config string fields: all are persisted in the swarm
68
+ // JSON store. topology is already validated against VALID_TOPOLOGIES so
69
+ // an invalid long value is rejected; the others have no validation.
70
+ const MAX_SWARM_FIELD_LEN = 256;
71
+ const rawStrategy = input.strategy || 'specialized';
72
+ const strategy = typeof rawStrategy === 'string' && rawStrategy.length > MAX_SWARM_FIELD_LEN
73
+ ? rawStrategy.slice(0, MAX_SWARM_FIELD_LEN) : rawStrategy;
68
74
  const config = (input.config || {});
69
75
  if (!VALID_TOPOLOGIES.has(topology)) {
70
76
  return {
@@ -85,9 +91,15 @@ export const swarmTools = [
85
91
  topology,
86
92
  maxAgents,
87
93
  strategy,
88
- communicationProtocol: config.communicationProtocol || 'message-bus',
94
+ communicationProtocol: (() => {
95
+ const raw = config.communicationProtocol || 'message-bus';
96
+ return typeof raw === 'string' && raw.length > MAX_SWARM_FIELD_LEN ? raw.slice(0, MAX_SWARM_FIELD_LEN) : raw;
97
+ })(),
89
98
  autoScaling: config.autoScaling ?? true,
90
- consensusMechanism: config.consensusMechanism || 'majority',
99
+ consensusMechanism: (() => {
100
+ const raw = config.consensusMechanism || 'majority';
101
+ return typeof raw === 'string' && raw.length > MAX_SWARM_FIELD_LEN ? raw.slice(0, MAX_SWARM_FIELD_LEN) : raw;
102
+ })(),
91
103
  },
92
104
  createdAt: now,
93
105
  updatedAt: now,
@@ -9,7 +9,7 @@
9
9
  * - os module for system information
10
10
  */
11
11
  import { getProjectCwd } from './types.js';
12
- import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
12
+ import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
13
13
  import { join, dirname } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import * as os from 'node:os';
@@ -43,10 +43,11 @@ function ensureSystemDir() {
43
43
  mkdirSync(dir, { recursive: true });
44
44
  }
45
45
  }
46
+ const MAX_SYSTEM_STORE_BYTES = 50 * 1024 * 1024; // 50 MB
46
47
  function loadMetrics() {
47
48
  try {
48
49
  const path = getMetricsPath();
49
- if (existsSync(path)) {
50
+ if (existsSync(path) && statSync(path).size <= MAX_SYSTEM_STORE_BYTES) {
50
51
  return JSON.parse(readFileSync(path, 'utf-8'));
51
52
  }
52
53
  }
@@ -128,7 +129,9 @@ export const systemTools = [
128
129
  },
129
130
  handler: async (input) => {
130
131
  const store = loadMetrics();
131
- const category = input.category || 'all';
132
+ const VALID_CATEGORIES = new Set(['all', 'cpu', 'memory', 'agents', 'tasks', 'requests']);
133
+ const rawCategory = input.category || 'all';
134
+ const category = VALID_CATEGORIES.has(rawCategory) ? rawCategory : 'all';
132
135
  // Get REAL system metrics via Node.js APIs
133
136
  const memUsage = process.memoryUsage();
134
137
  const loadAvg = os.loadavg();
@@ -182,7 +185,7 @@ export const systemTools = [
182
185
  if (_metricsSource === 'none') {
183
186
  try {
184
187
  const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json');
185
- if (existsSync(agentStorePath)) {
188
+ if (existsSync(agentStorePath) && statSync(agentStorePath).size <= MAX_SYSTEM_STORE_BYTES) {
186
189
  const agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
187
190
  const agents = Object.values(agentStore.agents || {});
188
191
  agentCounts = {
@@ -195,7 +198,7 @@ export const systemTools = [
195
198
  catch { /* agent store not available */ }
196
199
  try {
197
200
  const taskStorePath = join(getProjectCwd(), STORAGE_DIR, 'tasks', 'store.json');
198
- if (existsSync(taskStorePath)) {
201
+ if (existsSync(taskStorePath) && statSync(taskStorePath).size <= MAX_SYSTEM_STORE_BYTES) {
199
202
  const taskStore = JSON.parse(readFileSync(taskStorePath, 'utf-8'));
200
203
  const tasks = Object.values(taskStore.tasks || {});
201
204
  taskCounts = {
@@ -438,7 +441,11 @@ export const systemTools = [
438
441
  if (!input.confirm) {
439
442
  return { success: false, error: 'Reset requires confirmation' };
440
443
  }
441
- const component = input.component || 'metrics';
444
+ // Validate component against the allowed set to prevent unbounded string
445
+ // reflection in the response message.
446
+ const VALID_COMPONENTS = new Set(['all', 'metrics', 'agents', 'tasks']);
447
+ const rawComponent = input.component || 'metrics';
448
+ const component = VALID_COMPONENTS.has(rawComponent) ? rawComponent : 'metrics';
442
449
  // Reset metrics to defaults
443
450
  const defaultMetrics = {
444
451
  startTime: new Date().toISOString(),
@@ -530,7 +537,7 @@ export const systemTools = [
530
537
  const storePath = join(getProjectCwd(), '.monomind', 'tasks', 'store.json');
531
538
  let tasks = [];
532
539
  try {
533
- if (existsSync(storePath)) {
540
+ if (existsSync(storePath) && statSync(storePath).size <= MAX_SYSTEM_STORE_BYTES) {
534
541
  const data = readFileSync(storePath, 'utf-8');
535
542
  const store = JSON.parse(data);
536
543
  tasks = Object.values(store.tasks || {});