monomind 1.11.14 → 1.12.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 (172) 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 +13 -6
  20. package/.claude/helpers/handlers/session-handler.cjs +11 -4
  21. package/.claude/helpers/handlers/session-restore-handler.cjs +21 -11
  22. package/.claude/helpers/handlers/task-handler.cjs +13 -5
  23. package/.claude/helpers/intelligence.cjs +7 -2
  24. package/.claude/helpers/loop-tracker.cjs +15 -3
  25. package/.claude/helpers/memory.cjs +6 -1
  26. package/.claude/helpers/router.cjs +5 -2
  27. package/.claude/helpers/session.cjs +2 -0
  28. package/.claude/helpers/statusline.cjs +10 -2
  29. package/.claude/helpers/utils/micro-agents.cjs +20 -4
  30. package/.claude/scheduled_tasks.lock +1 -1
  31. package/.claude/settings.json +92 -1
  32. package/.claude/skills/mastermind/_protocol.md +25 -15
  33. package/.claude/skills/mastermind/architect.md +3 -3
  34. package/.claude/skills/mastermind/autodev.md +4 -2
  35. package/.claude/skills/mastermind/idea.md +10 -0
  36. package/.claude/skills/mastermind/ops.md +3 -3
  37. package/.claude/skills/mastermind/runorg.md +153 -86
  38. package/package.json +20 -3
  39. package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
  40. package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
  41. package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
  42. package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
  43. package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
  44. package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
  45. package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
  46. package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
  47. package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
  48. package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
  49. package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
  50. package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
  51. package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
  52. package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
  53. package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
  54. package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
  55. package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
  56. package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
  57. package/packages/@monomind/cli/dist/src/commands/doctor.js +97 -20
  58. package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
  59. package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
  60. package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
  61. package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
  62. package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
  63. package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
  64. package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
  65. package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
  66. package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
  67. package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
  68. package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
  69. package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
  70. package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
  71. package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
  72. package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
  73. package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
  74. package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
  75. package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
  76. package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
  77. package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
  78. package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
  79. package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
  80. package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
  81. package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
  82. package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
  83. package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
  84. package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
  85. package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
  86. package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
  87. package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
  88. package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
  89. package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
  90. package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
  91. package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
  92. package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
  93. package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
  94. package/packages/@monomind/cli/dist/src/index.js +7 -3
  95. package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
  96. package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
  97. package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
  98. package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
  99. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
  100. package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
  101. package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
  102. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
  103. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
  104. package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
  105. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
  106. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
  107. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
  108. package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
  109. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
  110. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
  111. package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
  112. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
  113. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +148 -26
  114. package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
  115. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
  116. package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
  117. package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
  118. package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
  119. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
  120. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
  121. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
  122. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
  123. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
  124. package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
  125. package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
  126. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
  127. package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
  128. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
  129. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
  130. package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
  131. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
  132. package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
  133. package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
  134. package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
  135. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
  136. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
  137. package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
  138. package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
  139. package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
  140. package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
  141. package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
  142. package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
  143. package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
  144. package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
  145. package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
  146. package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
  147. package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
  148. package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
  149. package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
  150. package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
  151. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
  152. package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
  153. package/packages/@monomind/cli/dist/src/services/worker-daemon.js +53 -12
  154. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
  155. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
  156. package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
  157. package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
  158. package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +9 -3
  159. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
  160. package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
  161. package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
  162. package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
  163. package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
  164. package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
  165. package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
  166. package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
  167. package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
  168. package/packages/@monomind/cli/dist/src/update/index.js +18 -1
  169. package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
  170. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
  171. package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
  172. package/packages/@monomind/cli/package.json +2 -3
@@ -206,12 +206,26 @@ export function serializeRegistry(registry) {
206
206
  }
207
207
  /**
208
208
  * Deserialize registry from JSON
209
+ *
210
+ * Caps the input string length before parsing to prevent OOM on a malicious or
211
+ * oversized registry fetched from IPFS / Pinata. The in-flight body is already
212
+ * capped by readBodyWithLimit (50 MB), but deserializeRegistry is also called
213
+ * with locally-cached data, so we add a 10 MB guard here too.
214
+ *
215
+ * We also reject non-semver and suspiciously long version strings to prevent
216
+ * version fields being used as a side-channel for large-payload injection.
209
217
  */
210
218
  export function deserializeRegistry(json) {
219
+ const MAX_JSON_BYTES = 10 * 1024 * 1024; // 10 MB
220
+ if (typeof json !== 'string' || json.length > MAX_JSON_BYTES) {
221
+ throw new Error(`Registry JSON too large: ${typeof json === 'string' ? json.length : typeof json} bytes (max ${MAX_JSON_BYTES})`);
222
+ }
211
223
  const registry = JSON.parse(json);
212
- // Validate version
213
- if (!registry.version) {
214
- throw new Error('Invalid registry: missing version');
224
+ // Validate version — must be a non-empty semver-like string (e.g. "1.0.0").
225
+ if (!registry.version || typeof registry.version !== 'string'
226
+ || registry.version.length > 32
227
+ || !/^\d+\.\d+\.\d+/.test(registry.version)) {
228
+ throw new Error('Invalid registry: missing or malformed version');
215
229
  }
216
230
  return registry;
217
231
  }
@@ -266,9 +280,16 @@ export async function verifyRegistrySignature(registry) {
266
280
  */
267
281
  export function mergeRegistries(local, remote) {
268
282
  const merged = createRegistry(local.ipnsName);
283
+ // Cap combined input sizes to prevent OOM when merging a large remote registry.
284
+ // A remote registry fetched from IPFS could contain hundreds of thousands of
285
+ // entries; without a hard cap, patternMap grows unboundedly in memory.
286
+ const MAX_MERGE_PATTERNS = 10_000;
287
+ const MAX_MERGE_AUTHORS = 1_000;
269
288
  // Combine patterns, preferring newer versions
270
289
  const patternMap = new Map();
271
290
  for (const pattern of [...local.patterns, ...remote.patterns]) {
291
+ if (patternMap.size >= MAX_MERGE_PATTERNS && !patternMap.has(pattern.name))
292
+ continue;
272
293
  const existing = patternMap.get(pattern.name);
273
294
  if (!existing || new Date(pattern.lastUpdated) > new Date(existing.lastUpdated)) {
274
295
  patternMap.set(pattern.name, pattern);
@@ -278,6 +299,8 @@ export function mergeRegistries(local, remote) {
278
299
  // Combine authors
279
300
  const authorMap = new Map();
280
301
  for (const author of [...local.authors, ...remote.authors]) {
302
+ if (authorMap.size >= MAX_MERGE_AUTHORS && !authorMap.has(author.id))
303
+ continue;
281
304
  const existing = authorMap.get(author.id);
282
305
  if (!existing || author.patterns > existing.patterns) {
283
306
  authorMap.set(author.id, author);
@@ -294,8 +317,10 @@ export function mergeRegistries(local, remote) {
294
317
  * Generate pattern ID from name
295
318
  */
296
319
  export function generatePatternId(name) {
297
- const normalized = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
298
- const hash = crypto.createHash('sha256').update(name + Date.now()).digest('hex').slice(0, 8);
320
+ // Cap name before normalizing to prevent OOM from an extremely long input string
321
+ const safeName = typeof name === 'string' ? name.slice(0, 128) : '';
322
+ const normalized = safeName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
323
+ const hash = crypto.createHash('sha256').update(safeName + Date.now()).digest('hex').slice(0, 8);
299
324
  return `${normalized}-${hash}`;
300
325
  }
301
326
  //# sourceMappingURL=registry.js.map
@@ -2,6 +2,16 @@
2
2
  * Pattern Search Service
3
3
  * Search and filter patterns from decentralized registry
4
4
  */
5
+ /** Maximum query string length to prevent O(n*m) regex/includes DoS */
6
+ const MAX_QUERY_LEN = 256;
7
+ /** Hard cap on page size to prevent huge slice allocations */
8
+ const MAX_PAGE_LIMIT = 200;
9
+ /** Hard cap on offset to prevent absurdly large slice calculations */
10
+ const MAX_OFFSET = 10_000;
11
+ /** Hard cap on suggestion partial string length */
12
+ const MAX_PARTIAL_LEN = 128;
13
+ /** Hard cap on similar-pattern limit */
14
+ const MAX_SIMILAR_LIMIT = 50;
5
15
  /**
6
16
  * Search patterns in registry
7
17
  */
@@ -9,7 +19,7 @@ export function searchPatterns(registry, options = {}) {
9
19
  let patterns = [...registry.patterns];
10
20
  // Text search
11
21
  if (options.query) {
12
- const query = options.query.toLowerCase();
22
+ const query = String(options.query).slice(0, MAX_QUERY_LEN).toLowerCase();
13
23
  patterns = patterns.filter(p => p.name.toLowerCase().includes(query) ||
14
24
  p.displayName.toLowerCase().includes(query) ||
15
25
  p.description.toLowerCase().includes(query) ||
@@ -77,10 +87,12 @@ export function searchPatterns(registry, options = {}) {
77
87
  }
78
88
  return sortOrder === 'desc' ? -comparison : comparison;
79
89
  });
80
- // Pagination
90
+ // Pagination — cap limit and offset to prevent huge slice allocations
81
91
  const total = patterns.length;
82
- const limit = options.limit || 20;
83
- const offset = options.offset || 0;
92
+ const rawLimit = typeof options.limit === 'number' && Number.isFinite(options.limit) ? options.limit : 20;
93
+ const limit = Math.min(Math.max(1, Math.floor(rawLimit)), MAX_PAGE_LIMIT);
94
+ const rawOffset = typeof options.offset === 'number' && Number.isFinite(options.offset) ? options.offset : 0;
95
+ const offset = Math.min(Math.max(0, Math.floor(rawOffset)), MAX_OFFSET);
84
96
  const page = Math.floor(offset / limit) + 1;
85
97
  patterns = patterns.slice(offset, offset + limit);
86
98
  return {
@@ -144,6 +156,7 @@ export function getPatternsByCategory(registry, categoryId) {
144
156
  * Get similar patterns (by tags and category)
145
157
  */
146
158
  export function getSimilarPatterns(registry, pattern, limit = 5) {
159
+ limit = Math.min(Math.max(1, Math.floor(limit)), MAX_SIMILAR_LIMIT);
147
160
  const scores = new Map();
148
161
  for (const p of registry.patterns) {
149
162
  if (p.id === pattern.id)
@@ -203,7 +216,9 @@ export function getTagCloud(registry) {
203
216
  */
204
217
  export function getSearchSuggestions(registry, partial, limit = 10) {
205
218
  const suggestions = new Set();
206
- const query = partial.toLowerCase();
219
+ // Cap partial length to prevent O(n*m) DoS via enormous query strings
220
+ const query = String(partial).slice(0, MAX_PARTIAL_LEN).toLowerCase();
221
+ limit = Math.min(Math.max(1, Math.floor(limit)), MAX_SIMILAR_LIMIT);
207
222
  // Add matching pattern names
208
223
  for (const pattern of registry.patterns) {
209
224
  if (pattern.name.toLowerCase().includes(query)) {
@@ -4,7 +4,19 @@
4
4
  */
5
5
  import { createRequire } from 'module';
6
6
  import { execFileSync } from 'child_process';
7
- import * as semver from 'semver';
7
+ // Inline semver shim — avoids external dependency
8
+ const semver = {
9
+ valid: (v) => /^\d+\.\d+\.\d+/.test(v || '') ? v : null,
10
+ eq: (a, b) => a === b,
11
+ major: (v) => parseInt((v || '0').split('.')[0], 10),
12
+ minor: (v) => parseInt((v || '0').split('.')[1] || '0', 10),
13
+ patch: (v) => parseInt(((v || '0').split('.')[2] || '0').replace(/[^0-9].*/, ''), 10),
14
+ gt: (a, b) => {
15
+ const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
16
+ const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
17
+ return aMaj !== bMaj ? aMaj > bMaj : aMin !== bMin ? aMin > bMin : aPat > bPat;
18
+ },
19
+ };
8
20
  import { reserveCheck, recordCheck, getCachedVersions } from './rate-limiter.js';
9
21
  const require = createRequire(import.meta.url);
10
22
  const DEFAULT_CONFIG = {
@@ -45,6 +57,13 @@ const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
45
57
  function isValidNpmName(name) {
46
58
  return NPM_NAME_RE.test(name) && !name.includes('..') && name.length <= 214;
47
59
  }
60
+ // Cap registry response at 5 MB. The full npm registry payload for a package
61
+ // can include the entire `versions` object (dozens of version entries with
62
+ // dist/scripts/dependencies for each). A spoofed or compromised registry
63
+ // endpoint could stream an arbitrarily large body; AbortSignal.timeout(5000)
64
+ // only enforces a wall-clock deadline and does NOT cap bytes. Without this
65
+ // cap, fetchPackageInfo will buffer an unbounded body into memory (OOM).
66
+ const MAX_REGISTRY_RESPONSE_BYTES = 5 * 1024 * 1024; // 5 MB
48
67
  async function fetchPackageInfo(packageName) {
49
68
  if (!isValidNpmName(packageName))
50
69
  return null;
@@ -56,7 +75,42 @@ async function fetchPackageInfo(packageName) {
56
75
  if (!response.ok) {
57
76
  return null;
58
77
  }
59
- return (await response.json());
78
+ // Reject immediately if Content-Length header exceeds cap
79
+ const contentLength = response.headers.get('content-length');
80
+ if (contentLength) {
81
+ const declared = parseInt(contentLength, 10);
82
+ if (Number.isFinite(declared) && declared > MAX_REGISTRY_RESPONSE_BYTES) {
83
+ return null;
84
+ }
85
+ }
86
+ // Stream body and enforce byte cap — prevents OOM if the server streams
87
+ // a large body that evades the Content-Length check (missing/wrong header).
88
+ if (!response.body)
89
+ return null;
90
+ const reader = response.body.getReader();
91
+ const chunks = [];
92
+ let totalBytes = 0;
93
+ while (true) {
94
+ const { done, value } = await reader.read();
95
+ if (done)
96
+ break;
97
+ if (value) {
98
+ totalBytes += value.byteLength;
99
+ if (totalBytes > MAX_REGISTRY_RESPONSE_BYTES) {
100
+ await reader.cancel();
101
+ return null;
102
+ }
103
+ chunks.push(value);
104
+ }
105
+ }
106
+ const buf = new Uint8Array(totalBytes);
107
+ let offset = 0;
108
+ for (const chunk of chunks) {
109
+ buf.set(chunk, offset);
110
+ offset += chunk.byteLength;
111
+ }
112
+ const text = new TextDecoder('utf-8').decode(buf);
113
+ return JSON.parse(text);
60
114
  }
61
115
  catch {
62
116
  return null;
@@ -66,7 +120,8 @@ function getUpdateType(current, latest) {
66
120
  if (!semver.valid(current) || !semver.valid(latest)) {
67
121
  return 'none';
68
122
  }
69
- if (semver.eq(current, latest)) {
123
+ // Not an upgrade (equal or downgrade)
124
+ if (!semver.gt(latest, current)) {
70
125
  return 'none';
71
126
  }
72
127
  if (semver.major(latest) > semver.major(current)) {
@@ -75,10 +130,7 @@ function getUpdateType(current, latest) {
75
130
  if (semver.minor(latest) > semver.minor(current)) {
76
131
  return 'minor';
77
132
  }
78
- if (semver.patch(latest) > semver.patch(current)) {
79
- return 'patch';
80
- }
81
- return 'none';
133
+ return 'patch';
82
134
  }
83
135
  function shouldAutoUpdate(updateType, priority, config) {
84
136
  if (updateType === 'none')
@@ -6,11 +6,32 @@ import { execFile } from 'child_process';
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import * as os from 'os';
9
- import * as semver from 'semver';
9
+ import { validateUpdate } from './validator.js';
10
+ // Inline semver shim — avoids external dependency (semver is not in package.json)
11
+ const semver = {
12
+ valid: (v) => /^\d+\.\d+\.\d+/.test(v || '') ? v : null,
13
+ };
14
+ /**
15
+ * Validate a npm package name.
16
+ * Allows scoped (@scope/name) and unscoped names; rejects path-traversal,
17
+ * shell metacharacters, and names that are too long to be legitimate.
18
+ * See https://docs.npmjs.com/cli/v9/configuring-npm/package-json#name
19
+ */
20
+ function isValidPackageName(name) {
21
+ if (typeof name !== 'string' || name.length === 0 || name.length > 214)
22
+ return false;
23
+ // Scoped: @scope/name — both parts: lowercase alnum + hyphens + underscores + dots
24
+ if (name.startsWith('@')) {
25
+ return /^@[a-z0-9][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*$/.test(name);
26
+ }
27
+ // Unscoped: must not start with . or _ (legacy rule)
28
+ return /^[a-z0-9][a-z0-9_.-]*$/.test(name);
29
+ }
30
+ /** Max bytes we will read from the on-disk update history file. */
31
+ const MAX_HISTORY_FILE_BYTES = 1 * 1024 * 1024; // 1 MB
10
32
  function execFileAsync(cmd, args) {
11
33
  return new Promise((resolve, reject) => execFile(cmd, args, (err) => (err ? reject(err) : resolve())));
12
34
  }
13
- import { validateUpdate } from './validator.js';
14
35
  const HISTORY_FILE = path.join(os.homedir(), '.monomind', 'update-history.json');
15
36
  const MAX_HISTORY_ENTRIES = 100;
16
37
  function ensureDir() {
@@ -22,8 +43,29 @@ function ensureDir() {
22
43
  export function loadHistory() {
23
44
  try {
24
45
  if (fs.existsSync(HISTORY_FILE)) {
46
+ // Guard against a bloated or attacker-crafted history file causing OOM.
47
+ const stat = fs.statSync(HISTORY_FILE);
48
+ if (stat.size > MAX_HISTORY_FILE_BYTES) {
49
+ return [];
50
+ }
25
51
  const content = fs.readFileSync(HISTORY_FILE, 'utf-8');
26
- return JSON.parse(content);
52
+ const raw = JSON.parse(content);
53
+ if (!Array.isArray(raw))
54
+ return [];
55
+ // Sanitize each entry: reject any entry whose package name or version
56
+ // fails validation so that a tampered history file cannot inject
57
+ // arbitrary arguments into a subsequent npm install via rollbackUpdate().
58
+ return raw.filter((e) => {
59
+ if (typeof e !== 'object' || e === null)
60
+ return false;
61
+ if (typeof e.package !== 'string' || !isValidPackageName(e.package))
62
+ return false;
63
+ if (typeof e.fromVersion !== 'string' || !semver.valid(e.fromVersion))
64
+ return false;
65
+ if (typeof e.toVersion !== 'string' || !semver.valid(e.toVersion))
66
+ return false;
67
+ return true;
68
+ });
27
69
  }
28
70
  }
29
71
  catch {
@@ -68,6 +110,11 @@ export async function executeUpdate(update, installedPackages, dryRun = false) {
68
110
  // Execute npm install — use execFile to avoid shell injection
69
111
  const pkg = update.package;
70
112
  const version = update.latestVersion;
113
+ // Validate both package name and version before constructing the npm arg
114
+ // to prevent argument injection via a crafted UpdateCheckResult.
115
+ if (!isValidPackageName(pkg)) {
116
+ throw new Error(`Invalid package name: ${pkg}`);
117
+ }
71
118
  if (!semver.valid(version)) {
72
119
  throw new Error(`Invalid version: ${version}`);
73
120
  }
@@ -16,7 +16,24 @@ export { executeUpdate, executeMultipleUpdates, rollbackUpdate, getUpdateHistory
16
16
  import { checkForUpdates, DEFAULT_CONFIG, getInstalledVersion } from './checker.js';
17
17
  import { executeMultipleUpdates } from './executor.js';
18
18
  import { getCachedVersions } from './rate-limiter.js';
19
- import * as semver from 'semver';
19
+ // Inline semver shim — avoids external dependency (semver is not listed in package.json)
20
+ const semver = {
21
+ valid: (v) => /^\d+\.\d+\.\d+/.test(v || '') ? v : null,
22
+ gt: (a, b) => {
23
+ const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
24
+ const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
25
+ return aMaj !== bMaj ? aMaj > bMaj : aMin !== bMin ? aMin > bMin : aPat > bPat;
26
+ },
27
+ lte: (a, b) => {
28
+ const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
29
+ const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
30
+ if (aMaj !== bMaj)
31
+ return aMaj < bMaj;
32
+ if (aMin !== bMin)
33
+ return aMin < bMin;
34
+ return aPat <= bPat;
35
+ },
36
+ };
20
37
  /**
21
38
  * Synchronous — reads cached state from last check.
22
39
  * Returns a short inline string for the CLI version tagline, e.g.
@@ -20,6 +20,12 @@ export declare function shouldCheckForUpdates(intervalHours?: number): {
20
20
  * only after a successful reserveCheck, so that limit enforcement and
21
21
  * increment happen in the same synchronous turn (no await gap between
22
22
  * them), preventing two concurrent callers both seeing "allowed".
23
+ *
24
+ * IMPORTANT: performs a single loadState() → check → increment → saveState()
25
+ * cycle to eliminate the TOCTOU window that existed when this function
26
+ * delegated to shouldCheckForUpdates() (which called loadState() itself)
27
+ * and then called loadState() a second time to increment. Two callers
28
+ * sharing that gap could both see allowed=true and both increment.
23
29
  */
24
30
  export declare function reserveCheck(intervalHours?: number): {
25
31
  allowed: boolean;
@@ -8,6 +8,12 @@ import * as os from 'os';
8
8
  const STATE_FILE = path.join(os.homedir(), '.monomind', 'update-state.json');
9
9
  const DEFAULT_INTERVAL_HOURS = 24;
10
10
  const MAX_CHECKS_PER_DAY = 10;
11
+ // Hard cap on how many package version entries we persist. Prevents an
12
+ // attacker who can write to the state file from inflating it unboundedly,
13
+ // and protects recordCheck() from DoS via a huge incoming packageVersions map.
14
+ const MAX_PACKAGE_VERSIONS = 100;
15
+ // Hard cap on the state file size we are willing to read into memory.
16
+ const MAX_STATE_FILE_BYTES = 1 * 1024 * 1024; // 1 MB
11
17
  function ensureDir() {
12
18
  const dir = path.dirname(STATE_FILE);
13
19
  if (!fs.existsSync(dir)) {
@@ -25,8 +31,36 @@ function getDefaultState() {
25
31
  export function loadState() {
26
32
  try {
27
33
  if (fs.existsSync(STATE_FILE)) {
34
+ // Guard against oversized state files (DoS / OOM) before reading
35
+ const stat = fs.statSync(STATE_FILE);
36
+ if (stat.size > MAX_STATE_FILE_BYTES) {
37
+ // State file is unreasonably large — discard and start fresh
38
+ try {
39
+ fs.unlinkSync(STATE_FILE);
40
+ }
41
+ catch { /* ignore */ }
42
+ return getDefaultState();
43
+ }
28
44
  const content = fs.readFileSync(STATE_FILE, 'utf-8');
29
- const state = JSON.parse(content);
45
+ // Block prototype pollution via JSON.parse reviver
46
+ const state = JSON.parse(content, (key, value) => {
47
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype')
48
+ return undefined;
49
+ return value;
50
+ });
51
+ // Validate that packageVersions is a plain object (not an array/primitive)
52
+ if (!state.packageVersions || typeof state.packageVersions !== 'object' || Array.isArray(state.packageVersions)) {
53
+ state.packageVersions = {};
54
+ }
55
+ // Cap the number of package version entries to prevent bloat
56
+ const versionKeys = Object.keys(state.packageVersions);
57
+ if (versionKeys.length > MAX_PACKAGE_VERSIONS) {
58
+ const capped = {};
59
+ for (const k of versionKeys.slice(0, MAX_PACKAGE_VERSIONS)) {
60
+ capped[k] = state.packageVersions[k];
61
+ }
62
+ state.packageVersions = capped;
63
+ }
30
64
  // Reset counter if new day
31
65
  const today = new Date().toISOString().split('T')[0];
32
66
  if (state.date !== today) {
@@ -86,14 +120,41 @@ export function shouldCheckForUpdates(intervalHours = DEFAULT_INTERVAL_HOURS) {
86
120
  * only after a successful reserveCheck, so that limit enforcement and
87
121
  * increment happen in the same synchronous turn (no await gap between
88
122
  * them), preventing two concurrent callers both seeing "allowed".
123
+ *
124
+ * IMPORTANT: performs a single loadState() → check → increment → saveState()
125
+ * cycle to eliminate the TOCTOU window that existed when this function
126
+ * delegated to shouldCheckForUpdates() (which called loadState() itself)
127
+ * and then called loadState() a second time to increment. Two callers
128
+ * sharing that gap could both see allowed=true and both increment.
89
129
  */
90
130
  export function reserveCheck(intervalHours = DEFAULT_INTERVAL_HOURS) {
91
- const decision = shouldCheckForUpdates(intervalHours);
92
- if (!decision.allowed)
93
- return decision;
94
- // Increment immediately, before any async work, so concurrent callers
95
- // see an updated count on their next tick.
131
+ // Fast-path: environment gates that don't need file I/O
132
+ if (process.env.CI === 'true' || process.env.CONTINUOUS_INTEGRATION === 'true') {
133
+ return { allowed: false, reason: 'CI environment detected' };
134
+ }
135
+ if (process.env.MONOMIND_AUTO_UPDATE === 'false') {
136
+ return { allowed: false, reason: 'Auto-update disabled via environment' };
137
+ }
138
+ // Single load — check and increment in one synchronous cycle
96
139
  const state = loadState();
140
+ if (process.env.MONOMIND_FORCE_UPDATE !== 'true') {
141
+ // Daily limit
142
+ if (state.checksToday >= MAX_CHECKS_PER_DAY) {
143
+ return { allowed: false, reason: `Daily check limit (${MAX_CHECKS_PER_DAY}) reached` };
144
+ }
145
+ // Time interval
146
+ if (state.lastCheck) {
147
+ const hoursSinceLastCheck = (Date.now() - new Date(state.lastCheck).getTime()) / (1000 * 60 * 60);
148
+ if (hoursSinceLastCheck < intervalHours) {
149
+ const nextCheck = Math.ceil(intervalHours - hoursSinceLastCheck);
150
+ return {
151
+ allowed: false,
152
+ reason: `Last check was ${Math.floor(hoursSinceLastCheck)}h ago (next check in ~${nextCheck}h)`,
153
+ };
154
+ }
155
+ }
156
+ }
157
+ // Reserve the slot: increment and persist before any async work begins
97
158
  state.checksToday += 1;
98
159
  state.lastCheck = new Date().toISOString();
99
160
  saveState(state);
@@ -102,7 +163,18 @@ export function reserveCheck(intervalHours = DEFAULT_INTERVAL_HOURS) {
102
163
  export function recordCheck(packageVersions) {
103
164
  // Update only package versions; count/timestamp already incremented by reserveCheck
104
165
  const state = loadState();
105
- state.packageVersions = { ...state.packageVersions, ...packageVersions };
166
+ // Merge only string-valued keys to block prototype pollution and type confusion.
167
+ // Also enforce the total cap so a large incoming map cannot bloat the state file.
168
+ const FORBIDDEN = new Set(['__proto__', 'constructor', 'prototype']);
169
+ for (const [k, v] of Object.entries(packageVersions)) {
170
+ if (FORBIDDEN.has(k))
171
+ continue;
172
+ if (typeof k !== 'string' || typeof v !== 'string')
173
+ continue;
174
+ if (Object.keys(state.packageVersions).length >= MAX_PACKAGE_VERSIONS)
175
+ break;
176
+ state.packageVersions[k] = v;
177
+ }
106
178
  saveState(state);
107
179
  }
108
180
  export function getCachedVersions() {
@@ -2,9 +2,42 @@
2
2
  * Package validator for update compatibility
3
3
  * Ensures updates don't break the ecosystem
4
4
  */
5
- import * as semver from 'semver';
5
+ // Inline semver shim — avoids external dependency (semver is not listed in package.json)
6
+ const semver = {
7
+ valid: (v) => /^\d+\.\d+\.\d+/.test(v || '') ? v : null,
8
+ major: (v) => parseInt((v || '0').split('.')[0], 10),
9
+ gt: (a, b) => {
10
+ const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
11
+ const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
12
+ return aMaj !== bMaj ? aMaj > bMaj : aMin !== bMin ? aMin > bMin : aPat > bPat;
13
+ },
14
+ lt: (a, b) => {
15
+ const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
16
+ const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
17
+ return aMaj !== bMaj ? aMaj < bMaj : aMin !== bMin ? aMin < bMin : aPat < bPat;
18
+ },
19
+ };
20
+ // Maximum number of updates accepted in a single validateBulkUpdate call.
21
+ // Without this cap a caller can DoS the validator by passing thousands of
22
+ // update entries — each entry triggers validateUpdate which iterates over
23
+ // COMPATIBILITY_MATRIX and BREAKING_CHANGES.
24
+ const MAX_BULK_UPDATES = 50;
25
+ // Version strings must look like semver (major.minor.patch with optional pre-release)
26
+ // before we use them in string interpolation or comparisons.
27
+ const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/;
28
+ // Package names: scoped (@scope/name) or plain, no shell-special chars.
29
+ const PKG_NAME_RE = /^(@[a-zA-Z0-9][a-zA-Z0-9_.-]*\/)?[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
30
+ function isSafeVersion(v) {
31
+ return typeof v === 'string' && v.length <= 64 && SEMVER_RE.test(v);
32
+ }
33
+ function isSafePackageName(p) {
34
+ return typeof p === 'string' && p.length <= 200 && PKG_NAME_RE.test(p);
35
+ }
6
36
  // Known compatibility matrix between monomind packages
7
37
  const COMPATIBILITY_MATRIX = {
38
+ '@monomind/cli': {
39
+ '@monomind/security': { minVersion: '3.0.0-alpha.1' },
40
+ },
8
41
  '@monoes/monomindcli': {
9
42
  'monofence-ai': { minVersion: '1.0.0' },
10
43
  },
@@ -35,6 +68,18 @@ export function validateUpdate(packageName, fromVersion, toVersion, installedPac
35
68
  warnings: [],
36
69
  requiredPeerUpdates: [],
37
70
  };
71
+ // Guard inputs: reject untrusted or malformed strings before they flow into
72
+ // error messages or semver comparisons (which assume well-formed input).
73
+ if (!isSafePackageName(packageName)) {
74
+ result.valid = false;
75
+ result.incompatibilities.push('Invalid package name');
76
+ return result;
77
+ }
78
+ if (!isSafeVersion(fromVersion) || !isSafeVersion(toVersion)) {
79
+ result.valid = false;
80
+ result.incompatibilities.push('Invalid version string(s)');
81
+ return result;
82
+ }
38
83
  // Check if this is a major version bump
39
84
  if (semver.valid(fromVersion) && semver.valid(toVersion)) {
40
85
  const fromMajor = semver.major(fromVersion);
@@ -93,6 +138,12 @@ export function validateBulkUpdate(updates, currentPackages) {
93
138
  warnings: [],
94
139
  requiredPeerUpdates: [],
95
140
  };
141
+ // Cap the number of updates to prevent DoS via large arrays
142
+ if (!Array.isArray(updates) || updates.length > MAX_BULK_UPDATES) {
143
+ combinedResult.valid = false;
144
+ combinedResult.incompatibilities.push(`Too many updates: max ${MAX_BULK_UPDATES} allowed per call`);
145
+ return combinedResult;
146
+ }
96
147
  // Create a simulated state after all updates
97
148
  const simulatedPackages = { ...currentPackages };
98
149
  for (const update of updates) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monomindcli",
3
- "version": "1.11.13",
3
+ "version": "1.12.0",
4
4
  "type": "module",
5
5
  "description": "Monomind CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",
@@ -87,8 +87,7 @@
87
87
  "dependencies": {
88
88
  "ws": "^8.18.0",
89
89
  "@noble/ed25519": "^2.1.0",
90
- "@monoes/monograph": "^1.2.0",
91
- "semver": "^7.6.0"
90
+ "@monoes/monograph": "^1.2.0"
92
91
  },
93
92
  "optionalDependencies": {
94
93
  "sql.js": "^1.14.1",