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.
- package/.claude/agents/generated/channel-intelligence-director.md +87 -0
- package/.claude/agents/generated/chief-growth-officer.md +88 -0
- package/.claude/agents/generated/content-seo-strategist.md +90 -0
- package/.claude/agents/generated/developer-community-strategist.md +91 -0
- package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
- package/.claude/agents/generated/social-media-strategist.md +91 -0
- package/.claude/agents/generated/video-visual-strategist.md +90 -0
- package/.claude/commands/mastermind/master.md +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +13 -4
- package/.claude/helpers/control-start.cjs +5 -0
- package/.claude/helpers/event-logger.cjs +114 -0
- package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
- package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
- package/.claude/helpers/handlers/compact-handler.cjs +2 -0
- package/.claude/helpers/handlers/edit-handler.cjs +1 -1
- package/.claude/helpers/handlers/gates-handler.cjs +3 -0
- package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
- package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
- package/.claude/helpers/handlers/route-handler.cjs +24 -10
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/hook-handler.cjs +40 -0
- package/.claude/helpers/intelligence.cjs +130 -53
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory-palace.cjs +461 -0
- package/.claude/helpers/memory.cjs +138 -14
- package/.claude/helpers/metrics-db.mjs +87 -0
- package/.claude/helpers/router.cjs +300 -42
- package/.claude/helpers/session.cjs +89 -30
- package/.claude/helpers/statusline.cjs +148 -4
- package/.claude/helpers/toggle-statusline.cjs +73 -0
- package/.claude/helpers/token-tracker.cjs +934 -0
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- package/.claude/helpers/utils/monograph.cjs +39 -4
- package/.claude/helpers/utils/telemetry.cjs +3 -3
- package/.claude/scheduled_tasks.lock +1 -1
- package/.claude/settings.json +92 -1
- package/.claude/skills/mastermind/_protocol.md +25 -15
- package/.claude/skills/mastermind/architect.md +3 -3
- package/.claude/skills/mastermind/autodev.md +4 -2
- package/.claude/skills/mastermind/idea.md +10 -0
- package/.claude/skills/mastermind/ops.md +3 -3
- package/.claude/skills/mastermind/runorg.md +153 -86
- package/package.json +20 -3
- package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
- package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
- package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
- package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
- package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
- package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
- package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
- package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
- package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
- package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
- package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
- package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
- package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
- package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
- package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
- package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
- package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
- package/packages/@monomind/cli/dist/src/commands/doctor.js +192 -23
- package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
- package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
- package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
- package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
- package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
- package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
- package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
- package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
- package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
- package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
- package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
- package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
- package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
- package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
- package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
- package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
- package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
- package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
- package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
- package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
- package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
- package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
- package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
- package/packages/@monomind/cli/dist/src/index.js +7 -3
- package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
- package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
- package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
- package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
- package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
- package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
- package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +476 -62
- package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
- package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
- package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
- package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
- package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
- package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
- package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
- package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
- package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
- package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
- package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
- package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
- package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
- package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
- package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
- package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
- package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
- package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
- package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
- package/packages/@monomind/cli/dist/src/services/worker-daemon.js +348 -17
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
- package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
- package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
- package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +8 -2
- package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
- package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
- package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
- package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
- package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
- package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
- package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
- package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
- package/packages/@monomind/cli/dist/src/update/index.js +18 -1
- package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
- package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
- 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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
563
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
288
|
-
const
|
|
289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
150
|
-
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
|
-
|
|
216
|
-
|
|
217
|
-
if (key
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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 || {});
|