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
@@ -105,6 +105,11 @@ export class CircuitBreaker {
105
105
  this.failures.push(now);
106
106
  // Clean old failures outside window
107
107
  this.failures = this.failures.filter(t => t > now - this.config.failureWindowMs);
108
+ // Hard cap: prevent unbounded growth under rapid-fire recordFailure() calls
109
+ // before the window filter prunes entries (e.g. clock skew or test hammering).
110
+ if (this.failures.length > 10_000) {
111
+ this.failures = this.failures.slice(-10_000);
112
+ }
108
113
  // Check if we should open the circuit
109
114
  if (this.state === 'closed' && this.failures.length >= this.config.failureThreshold) {
110
115
  this.transitionTo('open');
@@ -214,17 +219,26 @@ export class CircuitBreaker {
214
219
  // ============================================================================
215
220
  const circuitBreakers = new Map();
216
221
  const MAX_CIRCUIT_BREAKERS = 1000;
222
+ const MAX_CIRCUIT_BREAKER_NAME_LEN = 256;
223
+ const FORBIDDEN_CB_NAMES = new Set(['__proto__', 'constructor', 'prototype']);
217
224
  /**
218
225
  * Get or create a circuit breaker by name
219
226
  */
220
227
  export function getCircuitBreaker(name, config) {
221
- if (!circuitBreakers.has(name)) {
228
+ // Sanitize name: cap length and block prototype-pollution keys
229
+ const safeName = typeof name === 'string'
230
+ ? name.slice(0, MAX_CIRCUIT_BREAKER_NAME_LEN)
231
+ : 'default';
232
+ if (FORBIDDEN_CB_NAMES.has(safeName)) {
233
+ throw new Error(`Invalid circuit breaker name: ${safeName}`);
234
+ }
235
+ if (!circuitBreakers.has(safeName)) {
222
236
  if (circuitBreakers.size >= MAX_CIRCUIT_BREAKERS) {
223
237
  throw new Error(`Circuit breaker registry full (max ${MAX_CIRCUIT_BREAKERS})`);
224
238
  }
225
- circuitBreakers.set(name, new CircuitBreaker(config));
239
+ circuitBreakers.set(safeName, new CircuitBreaker(config));
226
240
  }
227
- return circuitBreakers.get(name);
241
+ return circuitBreakers.get(safeName);
228
242
  }
229
243
  /**
230
244
  * Get all circuit breaker stats
@@ -134,6 +134,9 @@ export class ErrorHandler {
134
134
  return input;
135
135
  if (_depth > 20)
136
136
  return { '[MAX_DEPTH]': true };
137
+ // Primitives and null cannot be stored in a WeakSet — return them as-is.
138
+ if (input === null || typeof input !== 'object')
139
+ return input;
137
140
  if (_seen.has(input))
138
141
  return { '[CIRCULAR]': true };
139
142
  _seen.add(input);
@@ -78,11 +78,25 @@ export class MonitoringHooks {
78
78
  // Apply sampling
79
79
  if (Math.random() > this.config.samplingRate)
80
80
  return;
81
+ // Cap metric name and label keys/values to prevent DoS
82
+ const safeName = String(name).slice(0, 256);
83
+ const safeLabels = {};
84
+ let labelCount = 0;
85
+ for (const [k, v] of Object.entries(labels)) {
86
+ if (!Object.hasOwn(labels, k))
87
+ continue;
88
+ if (++labelCount > 20)
89
+ break;
90
+ // Block prototype pollution keys
91
+ if (k === '__proto__' || k === 'constructor' || k === 'prototype')
92
+ continue;
93
+ safeLabels[String(k).slice(0, 64)] = String(v).slice(0, 512);
94
+ }
81
95
  const event = {
82
- name,
96
+ name: safeName,
83
97
  type,
84
98
  value,
85
- labels: { ...this.config.globalLabels, ...labels },
99
+ labels: { ...this.config.globalLabels, ...safeLabels },
86
100
  timestamp: Date.now(),
87
101
  };
88
102
  this.metrics.push(event);
@@ -131,7 +145,8 @@ export class MonitoringHooks {
131
145
  * Register a health check
132
146
  */
133
147
  registerHealthCheck(name, check) {
134
- this.healthChecks.set(name, check);
148
+ // Cap name length to prevent map-key inflation
149
+ this.healthChecks.set(String(name).slice(0, 128), check);
135
150
  }
136
151
  /**
137
152
  * Run all health checks
@@ -285,6 +300,8 @@ export class MonitoringHooks {
285
300
  // Private Methods
286
301
  // ============================================================================
287
302
  checkAlerts(name, value) {
303
+ if (!Object.hasOwn(this.config.alertThresholds, name))
304
+ return;
288
305
  const thresholds = this.config.alertThresholds[name];
289
306
  if (!thresholds)
290
307
  return;
@@ -33,6 +33,11 @@ export class RateLimiter {
33
33
  * Check if a request is allowed
34
34
  */
35
35
  check(operation, userId) {
36
+ // Cap operation and userId lengths so bucket keys cannot inflate the Map
37
+ const safeOp = typeof operation === 'string' ? operation.slice(0, 256) : 'unknown';
38
+ const safeUserId = userId ? userId.slice(0, 256) : undefined;
39
+ operation = safeOp;
40
+ userId = safeUserId;
36
41
  // Check whitelist
37
42
  if (this.config.whitelist.includes(operation)) {
38
43
  return { allowed: true, remaining: Infinity, resetAt: 0 };
@@ -157,10 +162,14 @@ export class RateLimiter {
157
162
  // Private Methods
158
163
  // ============================================================================
159
164
  getLimits(operation) {
160
- return (this.config.operationLimits[operation] || {
161
- maxRequests: this.config.maxRequests,
162
- windowMs: this.config.windowMs,
163
- });
165
+ // Object.hasOwn guards against prototype-pollution: if someone passes
166
+ // '__proto__' as the operation name, falling through to the default is safe.
167
+ return (Object.hasOwn(this.config.operationLimits, operation)
168
+ ? this.config.operationLimits[operation]
169
+ : {
170
+ maxRequests: this.config.maxRequests,
171
+ windowMs: this.config.windowMs,
172
+ });
164
173
  }
165
174
  createBucket() {
166
175
  return {
@@ -59,13 +59,15 @@ function calculateDelay(attempt, config, strategy = 'exponential') {
59
59
  return Math.round(Math.max(0, delay));
60
60
  }
61
61
  /**
62
- * Fibonacci helper for fibonacci backoff
62
+ * Fibonacci helper for fibonacci backoff — capped at n=40 to prevent
63
+ * excessively long loops when maxAttempts is set to a large value.
63
64
  */
64
65
  function fibonacci(n) {
65
- if (n <= 1)
66
+ const capped = Math.min(n, 40);
67
+ if (capped <= 1)
66
68
  return 1;
67
69
  let a = 1, b = 1;
68
- for (let i = 2; i <= n; i++) {
70
+ for (let i = 2; i <= capped; i++) {
69
71
  [a, b] = [b, a + b];
70
72
  }
71
73
  return b;
@@ -99,8 +101,12 @@ function sleep(ms) {
99
101
  */
100
102
  export async function withRetry(fn, config = {}, strategy = 'exponential') {
101
103
  const finalConfig = { ...DEFAULT_CONFIG, ...config };
104
+ // Clamp maxAttempts and maxDelayMs to safe ranges to prevent runaway retry loops
105
+ finalConfig.maxAttempts = Math.min(Math.max(1, finalConfig.maxAttempts), 100);
106
+ finalConfig.maxDelayMs = Math.min(Math.max(0, finalConfig.maxDelayMs), 300_000); // 5 min ceiling
102
107
  const startTime = Date.now();
103
108
  const retryHistory = [];
109
+ const MAX_RETRY_HISTORY = 100;
104
110
  let lastError;
105
111
  let attempt = 0;
106
112
  while (attempt < finalConfig.maxAttempts) {
@@ -129,12 +135,14 @@ export async function withRetry(fn, config = {}, strategy = 'exponential') {
129
135
  }
130
136
  // Calculate delay
131
137
  const delay = calculateDelay(attempt, finalConfig, strategy);
132
- // Record retry
133
- retryHistory.push({
134
- attempt,
135
- error: lastError.message,
136
- delayMs: delay,
137
- });
138
+ // Record retry (cap history size and error message length to prevent OOM)
139
+ if (retryHistory.length < MAX_RETRY_HISTORY) {
140
+ retryHistory.push({
141
+ attempt,
142
+ error: lastError.message.slice(0, 512),
143
+ delayMs: delay,
144
+ });
145
+ }
138
146
  // Call retry callback
139
147
  if (finalConfig.onRetry) {
140
148
  finalConfig.onRetry(lastError, attempt, delay);
@@ -22,12 +22,16 @@ import { createSemanticRouting } from './embedder.js';
22
22
  // don't land on stdout and corrupt the single JSON result line the parent
23
23
  // parses. Must be set before the (lazy) transformers import in embedder.ts.
24
24
  process.env.TRANSFORMERS_VERBOSITY ??= 'error';
25
+ /** Max task string length before it's silently truncated — prevents OOM in the embedding model. */
26
+ const MAX_TASK_LENGTH = 2_000;
25
27
  async function main() {
26
- const task = process.argv[2];
27
- if (!task) {
28
+ const rawTask = process.argv[2];
29
+ if (!rawTask) {
28
30
  process.stderr.write('embed-worker: missing task argument\n');
29
31
  process.exit(2);
30
32
  }
33
+ // Cap the task to prevent OOM when an oversized string is passed to the embedding model.
34
+ const task = rawTask.length > MAX_TASK_LENGTH ? rawTask.slice(0, MAX_TASK_LENGTH) : rawTask;
31
35
  const { RouteLayer, ALL_ROUTES } = await import('@monomind/routing');
32
36
  const semantic = await createSemanticRouting(ALL_ROUTES);
33
37
  if (!semantic) {
@@ -16,6 +16,8 @@ import { spawn, execSync } from 'child_process';
16
16
  import { tmpdir } from 'os';
17
17
  /** Default model for routing fallback — Haiku is fast and cheap for slug classification. */
18
18
  const DEFAULT_ROUTING_MODEL = 'haiku';
19
+ /** Runtime-validated set of allowed model aliases. */
20
+ const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
19
21
  /**
20
22
  * Max time to wait for a single classification before giving up.
21
23
  * `claude --print` is a full headless session (cold start ~10s, can spike),
@@ -23,6 +25,8 @@ const DEFAULT_ROUTING_MODEL = 'haiku';
23
25
  * on timeout rather than blocking the caller.
24
26
  */
25
27
  const DEFAULT_TIMEOUT_MS = 45_000;
28
+ /** Hard upper cap on caller-supplied timeoutMs — prevents disabling the SIGTERM/SIGKILL guard. */
29
+ const MAX_TIMEOUT_MS = 120_000;
26
30
  /** Cap captured output so a runaway child can't grow parent memory unbounded. */
27
31
  const MAX_OUTPUT = 1024 * 1024; // 1 MB — a slug response is tiny
28
32
  let claudeAvailable = null;
@@ -49,8 +53,15 @@ export function isClaudeCodeAvailable() {
49
53
  export function createClaudeLLMCaller(options = {}) {
50
54
  if (!isClaudeCodeAvailable())
51
55
  return null;
52
- const model = options.model ?? DEFAULT_ROUTING_MODEL;
53
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
56
+ // Runtime enum guard TypeScript union is compile-time only; JS callers
57
+ // could otherwise inject an arbitrary string into the --model flag.
58
+ const rawModel = options.model ?? DEFAULT_ROUTING_MODEL;
59
+ const model = ALLOWED_MODELS.has(rawModel) ? rawModel : DEFAULT_ROUTING_MODEL;
60
+ // Cap caller-supplied timeout so a huge value can't disable the SIGTERM/SIGKILL guard.
61
+ const rawTimeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
62
+ const timeoutMs = Math.min(typeof rawTimeout === 'number' && Number.isFinite(rawTimeout) && rawTimeout > 0
63
+ ? rawTimeout
64
+ : DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
54
65
  const cwd = options.cwd ?? tmpdir();
55
66
  return (prompt) => new Promise((resolve, reject) => {
56
67
  const env = { ...process.env };
@@ -25,12 +25,20 @@ const WORKER_PATH = join(dirname(fileURLToPath(import.meta.url)), 'embed-worker.
25
25
  const WORKER_TIMEOUT_MS = 90_000;
26
26
  /** Cap worker stdout so a runaway child can't grow parent memory unbounded. */
27
27
  const MAX_WORKER_OUTPUT = 4 * 1024 * 1024; // 4 MB
28
+ /** Cap the task string length to prevent OOM/DoS via oversized argv. */
29
+ const MAX_TASK_LENGTH = 2_000;
30
+ /** Cap stderr reflected in error messages to prevent log-inflation attacks. */
31
+ const MAX_STDERR_IN_ERROR = 500;
32
+ /** Cap allScores array passed to the LLM fallback to prevent downstream OOM. */
33
+ const MAX_ALL_SCORES = 100;
28
34
  /** Marker the worker prefixes its result line with (see embed-worker.ts). */
29
35
  const RESULT_MARKER = '__ROUTE_RESULT__';
30
36
  /** Run the embedding worker for one task. Resolves the RouteResult, or rejects. */
31
37
  function runWorker(task) {
38
+ // Cap task length before passing as argv to prevent OOM/DoS via oversized args.
39
+ const safeTask = task.length > MAX_TASK_LENGTH ? task.slice(0, MAX_TASK_LENGTH) : task;
32
40
  return new Promise((resolve, reject) => {
33
- const child = spawn(process.execPath, [WORKER_PATH, task], {
41
+ const child = spawn(process.execPath, [WORKER_PATH, safeTask], {
34
42
  stdio: ['ignore', 'pipe', 'pipe'],
35
43
  windowsHide: true,
36
44
  });
@@ -75,7 +83,10 @@ function runWorker(task) {
75
83
  settled = true;
76
84
  clearTimeout(timer);
77
85
  if (code !== 0) {
78
- reject(new Error(stderr.trim() || `worker exited ${code}`));
86
+ // Truncate stderr before embedding in Error to prevent log-inflation via
87
+ // a malicious or runaway child writing oversized diagnostic output.
88
+ const stderrSnippet = stderr.trim().slice(0, MAX_STDERR_IN_ERROR);
89
+ reject(new Error(stderrSnippet || `worker exited ${code}`));
79
90
  return;
80
91
  }
81
92
  // Extract the marked result line — model-loader output may have written
@@ -130,8 +141,12 @@ export async function createConfiguredRouteLayer(opts = {}) {
130
141
  }
131
142
  // 3. Below threshold → Claude fallback in the parent, reusing scores.
132
143
  if (llmCaller && Array.isArray(semantic.allScores) && semantic.allScores.length) {
144
+ // Cap allScores to prevent downstream OOM if the worker sends an oversized array.
145
+ const scores = semantic.allScores.length > MAX_ALL_SCORES
146
+ ? semantic.allScores.slice(0, MAX_ALL_SCORES)
147
+ : semantic.allScores;
133
148
  const fallback = new LLMFallbackRouter({ llmCaller, model: 'haiku' });
134
- return fallback.classify(taskDescription, ALL_ROUTES, semantic.allScores);
149
+ return fallback.classify(taskDescription, ALL_ROUTES, scores);
135
150
  }
136
151
  if (!opts.debug)
137
152
  delete semantic.allScores;
@@ -129,6 +129,7 @@ export declare class ClaimService extends EventEmitter {
129
129
  private eventLog;
130
130
  constructor(projectRoot: string, config?: Partial<ClaimServiceConfig>);
131
131
  initialize(): Promise<void>;
132
+ private static readonly MAX_CLAIMS_FILE_BYTES;
132
133
  private loadClaims;
133
134
  private _saveQueue;
134
135
  private saveClaims;
@@ -57,10 +57,18 @@ export class ClaimService extends EventEmitter {
57
57
  // Load existing claims
58
58
  await this.loadClaims();
59
59
  }
60
+ // Hard cap on claims.json: a file larger than this is malformed or
61
+ // adversarially crafted (normal claim files are tiny).
62
+ static MAX_CLAIMS_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
60
63
  async loadClaims() {
61
64
  const claimsFile = path.join(this.storagePath, 'claims.json');
62
65
  if (fs.existsSync(claimsFile)) {
63
66
  try {
67
+ const fileSize = fs.statSync(claimsFile).size;
68
+ if (fileSize > ClaimService.MAX_CLAIMS_FILE_BYTES) {
69
+ // Oversized file — skip loading rather than OOM-ing the daemon
70
+ return;
71
+ }
64
72
  const data = JSON.parse(fs.readFileSync(claimsFile, 'utf-8'));
65
73
  for (const claim of data.claims || []) {
66
74
  claim.claimedAt = new Date(claim.claimedAt);
@@ -3,6 +3,7 @@
3
3
  * Shared JSON config file persistence with atomic writes and Zod validation
4
4
  */
5
5
  import * as fs from 'fs';
6
+ import * as os from 'os';
6
7
  import * as path from 'path';
7
8
  /** Config file search paths in priority order */
8
9
  const CONFIG_FILENAMES = [
@@ -123,7 +124,7 @@ export class ConfigFileManager {
123
124
  * drop the first writer's API key.
124
125
  */
125
126
  set(cwd, key, value) {
126
- const KNOWN_SET_SECTIONS = new Set(['version', 'agents', 'swarm', 'memory', 'mcp', 'cli', 'hooks', 'neural']);
127
+ const KNOWN_SET_SECTIONS = new Set(['version', 'agents', 'swarm', 'memory', 'mcp', 'cli', 'hooks', 'neural', 'providers']);
127
128
  const topSection = String(key).split('.')[0];
128
129
  if (!KNOWN_SET_SECTIONS.has(topSection)) {
129
130
  throw new Error(`Unknown config section: "${topSection}". Allowed: ${[...KNOWN_SET_SECTIONS].join(', ')}`);
@@ -181,6 +182,17 @@ export class ConfigFileManager {
181
182
  /** Import config from a specific path */
182
183
  importFrom(cwd, importPath) {
183
184
  const resolved = path.resolve(cwd, importPath);
185
+ // Guard against path traversal: the resolved path must be within the
186
+ // project cwd or the user's home directory. Without this check an
187
+ // automated script can pass "/etc/passwd" or "../../.env" and exfiltrate
188
+ // files outside the project tree.
189
+ const projectRoot = path.resolve(cwd);
190
+ const home = os.homedir();
191
+ const isUnderProject = resolved === projectRoot || resolved.startsWith(projectRoot + path.sep);
192
+ const isUnderHome = resolved === home || resolved.startsWith(home + path.sep);
193
+ if (!isUnderProject && !isUnderHome) {
194
+ throw new Error(`Import path must be within the project directory or home directory: ${resolved}`);
195
+ }
184
196
  if (!fs.existsSync(resolved)) {
185
197
  throw new Error(`Import file not found: ${resolved}`);
186
198
  }
@@ -199,7 +211,7 @@ export class ConfigFileManager {
199
211
  // KNOWN_SECTIONS only validates top-level keys, leaving nested
200
212
  // {agents:{providers:[{__proto__:{...}}]}} unsanitized.
201
213
  const imported = sanitizeConfigObject(importedRaw);
202
- const KNOWN_SECTIONS = new Set(['version', 'agents', 'swarm', 'memory', 'mcp', 'cli', 'hooks']);
214
+ const KNOWN_SECTIONS = new Set(['version', 'agents', 'swarm', 'memory', 'mcp', 'cli', 'hooks', 'neural', 'providers']);
203
215
  for (const key of Object.keys(imported)) {
204
216
  if (!KNOWN_SECTIONS.has(key)) {
205
217
  throw new Error(`Unknown config section: "${key}"`);
@@ -1017,6 +1017,12 @@ Analyze the above codebase context and provide your response following the forma
1017
1017
  let stdout = '';
1018
1018
  let stderr = '';
1019
1019
  let resolved = false;
1020
+ // Cap stdout + stderr to 10 MB each to prevent OOM if a spawned Claude
1021
+ // Code process emits unexpectedly large output (e.g. a worker that dumps
1022
+ // a large file listing). Once the cap is reached we stop appending; the
1023
+ // truncation marker lets callers detect that output was cut.
1024
+ const MAX_HEADLESS_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB
1025
+ const TRUNCATION_MARKER = '\n[... output truncated at 10 MB ...]';
1020
1026
  const cleanup = () => {
1021
1027
  clearTimeout(timeoutHandle);
1022
1028
  clearTimeout(sigkillTimer);
@@ -1024,7 +1030,12 @@ Analyze the above codebase context and provide your response following the forma
1024
1030
  };
1025
1031
  child.stdout?.on('data', (data) => {
1026
1032
  const chunk = data.toString();
1027
- stdout += chunk;
1033
+ if (stdout.length < MAX_HEADLESS_OUTPUT_BYTES) {
1034
+ stdout += chunk;
1035
+ if (stdout.length >= MAX_HEADLESS_OUTPUT_BYTES) {
1036
+ stdout = stdout.slice(0, MAX_HEADLESS_OUTPUT_BYTES) + TRUNCATION_MARKER;
1037
+ }
1038
+ }
1028
1039
  this.emit('output', {
1029
1040
  executionId: options.executionId,
1030
1041
  type: 'stdout',
@@ -1033,7 +1044,12 @@ Analyze the above codebase context and provide your response following the forma
1033
1044
  });
1034
1045
  child.stderr?.on('data', (data) => {
1035
1046
  const chunk = data.toString();
1036
- stderr += chunk;
1047
+ if (stderr.length < MAX_HEADLESS_OUTPUT_BYTES) {
1048
+ stderr += chunk;
1049
+ if (stderr.length >= MAX_HEADLESS_OUTPUT_BYTES) {
1050
+ stderr = stderr.slice(0, MAX_HEADLESS_OUTPUT_BYTES) + TRUNCATION_MARKER;
1051
+ }
1052
+ }
1037
1053
  this.emit('output', {
1038
1054
  executionId: options.executionId,
1039
1055
  type: 'stderr',