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
|
@@ -71,15 +71,36 @@ export const taskTools = [
|
|
|
71
71
|
handler: async (input) => {
|
|
72
72
|
const store = loadTaskStore();
|
|
73
73
|
const taskId = `task-${Date.now()}-${randomBytes(4).toString('hex')}`;
|
|
74
|
+
// Cap all string fields: they are persisted verbatim to the task JSON store.
|
|
75
|
+
const MAX_TASK_TYPE_LEN = 128;
|
|
76
|
+
const MAX_TASK_DESC_LEN = 64 * 1024; // 64 KB — realistic task descriptions
|
|
77
|
+
const MAX_TASK_ASSIGNEE_LEN = 256;
|
|
78
|
+
const MAX_TASK_ASSIGNEES = 100;
|
|
79
|
+
const MAX_TASK_TAG_LEN = 128;
|
|
80
|
+
const MAX_TASK_TAGS = 50;
|
|
81
|
+
const rawTaskType = input.type;
|
|
82
|
+
const taskType = typeof rawTaskType === 'string' && rawTaskType.length > MAX_TASK_TYPE_LEN
|
|
83
|
+
? rawTaskType.slice(0, MAX_TASK_TYPE_LEN) : rawTaskType;
|
|
84
|
+
const rawTaskDesc = input.description;
|
|
85
|
+
const taskDesc = typeof rawTaskDesc === 'string' && rawTaskDesc.length > MAX_TASK_DESC_LEN
|
|
86
|
+
? rawTaskDesc.slice(0, MAX_TASK_DESC_LEN) : rawTaskDesc;
|
|
87
|
+
const rawAssignTo = input.assignTo || [];
|
|
88
|
+
const assignedTo = Array.isArray(rawAssignTo)
|
|
89
|
+
? rawAssignTo.slice(0, MAX_TASK_ASSIGNEES).map(a => typeof a === 'string' && a.length > MAX_TASK_ASSIGNEE_LEN ? a.slice(0, MAX_TASK_ASSIGNEE_LEN) : a)
|
|
90
|
+
: [];
|
|
91
|
+
const rawTags = input.tags || [];
|
|
92
|
+
const tags = Array.isArray(rawTags)
|
|
93
|
+
? rawTags.slice(0, MAX_TASK_TAGS).map(t => typeof t === 'string' && t.length > MAX_TASK_TAG_LEN ? t.slice(0, MAX_TASK_TAG_LEN) : t)
|
|
94
|
+
: [];
|
|
74
95
|
const task = {
|
|
75
96
|
taskId,
|
|
76
|
-
type:
|
|
77
|
-
description:
|
|
97
|
+
type: taskType,
|
|
98
|
+
description: taskDesc,
|
|
78
99
|
priority: input.priority || 'normal',
|
|
79
100
|
status: 'pending',
|
|
80
101
|
progress: 0,
|
|
81
|
-
assignedTo
|
|
82
|
-
tags
|
|
102
|
+
assignedTo,
|
|
103
|
+
tags,
|
|
83
104
|
createdAt: new Date().toISOString(),
|
|
84
105
|
startedAt: null,
|
|
85
106
|
completedAt: null,
|
|
@@ -172,8 +193,13 @@ export const taskTools = [
|
|
|
172
193
|
}
|
|
173
194
|
// Sort by creation date (newest first)
|
|
174
195
|
tasks.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
175
|
-
// Apply limit
|
|
176
|
-
|
|
196
|
+
// Apply limit — cap to 1 000 to prevent returning the entire task store
|
|
197
|
+
// in one response, which could cause OOM on large deployments.
|
|
198
|
+
const MAX_TASK_LIMIT = 1_000;
|
|
199
|
+
const rawLimit = typeof input.limit === 'number' ? input.limit : 50;
|
|
200
|
+
const limit = Number.isFinite(rawLimit) && rawLimit > 0
|
|
201
|
+
? Math.min(Math.floor(rawLimit), MAX_TASK_LIMIT)
|
|
202
|
+
: 50;
|
|
177
203
|
tasks = tasks.slice(0, limit);
|
|
178
204
|
return {
|
|
179
205
|
tasks: tasks.map(t => ({
|
|
@@ -225,7 +251,7 @@ export const taskTools = [
|
|
|
225
251
|
const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json');
|
|
226
252
|
try {
|
|
227
253
|
let agentStore = { agents: {} };
|
|
228
|
-
if (existsSync(agentStorePath)) {
|
|
254
|
+
if (existsSync(agentStorePath) && statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
|
|
229
255
|
const agentRaw = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
|
|
230
256
|
if (agentRaw && typeof agentRaw === 'object' && !Object.prototype.hasOwnProperty.call(agentRaw, '__proto__')) {
|
|
231
257
|
agentStore = agentRaw;
|
|
@@ -298,7 +324,15 @@ export const taskTools = [
|
|
|
298
324
|
task.progress = Math.min(100, Math.max(0, input.progress));
|
|
299
325
|
}
|
|
300
326
|
if (input.assignTo) {
|
|
301
|
-
|
|
327
|
+
// Cap array and element lengths — task_create already does this;
|
|
328
|
+
// task_update must apply the same guards so the on-disk store
|
|
329
|
+
// cannot be inflated via the update path.
|
|
330
|
+
const MAX_TASK_ASSIGNEE_LEN = 256;
|
|
331
|
+
const MAX_TASK_ASSIGNEES = 100;
|
|
332
|
+
const rawAssignTo = input.assignTo;
|
|
333
|
+
task.assignedTo = Array.isArray(rawAssignTo)
|
|
334
|
+
? rawAssignTo.slice(0, MAX_TASK_ASSIGNEES).map(a => typeof a === 'string' && a.length > MAX_TASK_ASSIGNEE_LEN ? a.slice(0, MAX_TASK_ASSIGNEE_LEN) : a)
|
|
335
|
+
: task.assignedTo;
|
|
302
336
|
}
|
|
303
337
|
saveTaskStore(store);
|
|
304
338
|
return {
|
|
@@ -343,7 +377,7 @@ export const taskTools = [
|
|
|
343
377
|
const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json');
|
|
344
378
|
let agentStore = { agents: {} };
|
|
345
379
|
try {
|
|
346
|
-
if (existsSync(agentStorePath)) {
|
|
380
|
+
if (existsSync(agentStorePath) && statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
|
|
347
381
|
agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
|
|
348
382
|
}
|
|
349
383
|
}
|
|
@@ -426,7 +460,15 @@ export const taskTools = [
|
|
|
426
460
|
if (task) {
|
|
427
461
|
task.status = 'cancelled';
|
|
428
462
|
task.completedAt = new Date().toISOString();
|
|
429
|
-
|
|
463
|
+
// Cap reason: persisted verbatim to the task store on disk.
|
|
464
|
+
// Without a cap an attacker can inflate the store with an arbitrarily
|
|
465
|
+
// large cancellation reason string.
|
|
466
|
+
const MAX_CANCEL_REASON_LEN = 1024;
|
|
467
|
+
const rawReason = input.reason;
|
|
468
|
+
const cancelReason = typeof rawReason === 'string' && rawReason.length > MAX_CANCEL_REASON_LEN
|
|
469
|
+
? rawReason.slice(0, MAX_CANCEL_REASON_LEN)
|
|
470
|
+
: (rawReason || 'Cancelled by user');
|
|
471
|
+
task.result = { cancelReason };
|
|
430
472
|
saveTaskStore(store);
|
|
431
473
|
return {
|
|
432
474
|
success: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getProjectCwd } from './types.js';
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
4
5
|
import { execSync } from 'node:child_process';
|
|
5
6
|
import { randomBytes } from 'node:crypto';
|
|
6
7
|
// Storage paths
|
|
@@ -19,10 +20,11 @@ function ensureTerminalDir() {
|
|
|
19
20
|
mkdirSync(dir, { recursive: true });
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
const MAX_TERMINAL_STORE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
22
24
|
function loadTerminalStore() {
|
|
23
25
|
try {
|
|
24
26
|
const path = getTerminalPath();
|
|
25
|
-
if (existsSync(path)) {
|
|
27
|
+
if (existsSync(path) && statSync(path).size <= MAX_TERMINAL_STORE_BYTES) {
|
|
26
28
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -74,14 +76,40 @@ export const terminalTools = [
|
|
|
74
76
|
safeEnv[k] = String(v);
|
|
75
77
|
}
|
|
76
78
|
}
|
|
79
|
+
// Validate workingDir: must exist, be a directory, and not escape to
|
|
80
|
+
// system-sensitive paths. Fall back to project cwd if invalid.
|
|
81
|
+
let resolvedWorkingDir = getProjectCwd();
|
|
82
|
+
if (input.workingDir && typeof input.workingDir === 'string') {
|
|
83
|
+
const candidate = resolve(input.workingDir);
|
|
84
|
+
const projectCwd = getProjectCwd();
|
|
85
|
+
const home = homedir();
|
|
86
|
+
// Allow paths under project cwd or user home directory only.
|
|
87
|
+
const isUnderProject = candidate === projectCwd || candidate.startsWith(projectCwd + '/') || candidate.startsWith(projectCwd + '\\');
|
|
88
|
+
const isUnderHome = candidate === home || candidate.startsWith(home + '/') || candidate.startsWith(home + '\\');
|
|
89
|
+
if ((isUnderProject || isUnderHome) && existsSync(candidate)) {
|
|
90
|
+
try {
|
|
91
|
+
if (statSync(candidate).isDirectory()) {
|
|
92
|
+
resolvedWorkingDir = candidate;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Leave resolvedWorkingDir as default
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
77
100
|
const id = `term-${Date.now()}-${randomBytes(4).toString('hex')}`;
|
|
101
|
+
// Cap session name: stored in terminal JSON store on disk.
|
|
102
|
+
const MAX_TERMINAL_NAME_LEN = 256;
|
|
103
|
+
const rawTerminalName = input.name || `Terminal ${Object.keys(store.sessions).length + 1}`;
|
|
104
|
+
const terminalName = typeof rawTerminalName === 'string' && rawTerminalName.length > MAX_TERMINAL_NAME_LEN
|
|
105
|
+
? rawTerminalName.slice(0, MAX_TERMINAL_NAME_LEN) : rawTerminalName;
|
|
78
106
|
const session = {
|
|
79
107
|
id,
|
|
80
|
-
name:
|
|
108
|
+
name: terminalName,
|
|
81
109
|
status: 'active',
|
|
82
110
|
createdAt: new Date().toISOString(),
|
|
83
111
|
lastActivity: new Date().toISOString(),
|
|
84
|
-
workingDir:
|
|
112
|
+
workingDir: resolvedWorkingDir,
|
|
85
113
|
history: [],
|
|
86
114
|
env: safeEnv,
|
|
87
115
|
};
|
|
@@ -114,7 +142,13 @@ export const terminalTools = [
|
|
|
114
142
|
handler: async (input) => {
|
|
115
143
|
const store = loadTerminalStore();
|
|
116
144
|
const sessionId = input.sessionId;
|
|
117
|
-
|
|
145
|
+
// Cap command: the metacharacter regex check at line 220 is O(n), and the
|
|
146
|
+
// raw command is stored verbatim in session history (up to 200 entries).
|
|
147
|
+
// A realistic shell command is well under 64 KB; cap there.
|
|
148
|
+
const MAX_TERMINAL_COMMAND_LEN = 64 * 1024;
|
|
149
|
+
const rawCommand = input.command;
|
|
150
|
+
const command = typeof rawCommand === 'string' && rawCommand.length > MAX_TERMINAL_COMMAND_LEN
|
|
151
|
+
? rawCommand.slice(0, MAX_TERMINAL_COMMAND_LEN) : rawCommand;
|
|
118
152
|
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
119
153
|
// Reject inherited keys (incl. toString/hasOwnProperty/etc.) so a tampered
|
|
120
154
|
// store.json can't redirect bracket access into Object.prototype.
|
|
@@ -59,7 +59,13 @@ export const transferTools = [
|
|
|
59
59
|
handler: async (input) => {
|
|
60
60
|
try {
|
|
61
61
|
const { detectPII } = await import('../transfer/anonymization/index.js');
|
|
62
|
-
|
|
62
|
+
// detectPII runs multiple PII regexes over the entire string — O(n × patterns).
|
|
63
|
+
// Cap to 1 MB to prevent ReDoS-style DoS from oversized content.
|
|
64
|
+
const MAX_PII_CONTENT_LEN = 1024 * 1024; // 1 MB
|
|
65
|
+
const rawContent = input.content;
|
|
66
|
+
const content = typeof rawContent === 'string' && rawContent.length > MAX_PII_CONTENT_LEN
|
|
67
|
+
? rawContent.slice(0, MAX_PII_CONTENT_LEN) : rawContent;
|
|
68
|
+
const result = detectPII(content);
|
|
63
69
|
return createResult(result);
|
|
64
70
|
}
|
|
65
71
|
catch (error) {
|
|
@@ -88,7 +94,13 @@ export const transferTools = [
|
|
|
88
94
|
handler: async (input) => {
|
|
89
95
|
try {
|
|
90
96
|
const { resolveIPNS } = await import('../transfer/ipfs/client.js');
|
|
91
|
-
|
|
97
|
+
// Cap IPNS name length — uncapped strings may trigger O(n) path parsing
|
|
98
|
+
// inside the IPFS client or be reflected in error messages.
|
|
99
|
+
const MAX_IPNS_NAME_LEN = 512;
|
|
100
|
+
const rawName = input.name;
|
|
101
|
+
const name = typeof rawName === 'string' && rawName.length > MAX_IPNS_NAME_LEN
|
|
102
|
+
? rawName.slice(0, MAX_IPNS_NAME_LEN) : rawName;
|
|
103
|
+
const result = await resolveIPNS(name);
|
|
92
104
|
return createResult({ success: true, cid: result });
|
|
93
105
|
}
|
|
94
106
|
catch (error) {
|
|
@@ -132,7 +144,18 @@ export const transferTools = [
|
|
|
132
144
|
handler: async (input) => {
|
|
133
145
|
try {
|
|
134
146
|
const store = await getPatternStore();
|
|
135
|
-
|
|
147
|
+
// Cap query and limit to prevent DoS via large string or result set.
|
|
148
|
+
const MAX_TRANSFER_QUERY_LEN = 4 * 1024;
|
|
149
|
+
const MAX_TRANSFER_LIMIT = 500;
|
|
150
|
+
const inp = input;
|
|
151
|
+
const cappedInput = {
|
|
152
|
+
...inp,
|
|
153
|
+
query: typeof inp.query === 'string' && inp.query.length > MAX_TRANSFER_QUERY_LEN
|
|
154
|
+
? inp.query.slice(0, MAX_TRANSFER_QUERY_LEN) : inp.query,
|
|
155
|
+
limit: typeof inp.limit === 'number' && Number.isFinite(inp.limit)
|
|
156
|
+
? Math.min(Math.floor(Math.max(inp.limit, 1)), MAX_TRANSFER_LIMIT) : inp.limit,
|
|
157
|
+
};
|
|
158
|
+
const results = store.search(cappedInput);
|
|
136
159
|
return createResult(results);
|
|
137
160
|
}
|
|
138
161
|
catch (error) {
|
|
@@ -296,7 +319,17 @@ export const transferTools = [
|
|
|
296
319
|
if (!result.success || !result.registry) {
|
|
297
320
|
return createResult({ error: result.error || 'Failed to discover registry' }, true);
|
|
298
321
|
}
|
|
299
|
-
|
|
322
|
+
// Cap query and limit before forwarding to searchPlugins.
|
|
323
|
+
const MAX_PLUGIN_QUERY_LEN = 4 * 1024;
|
|
324
|
+
const MAX_PLUGIN_LIMIT = 500;
|
|
325
|
+
const rawOpts = input;
|
|
326
|
+
const opts = {
|
|
327
|
+
...rawOpts,
|
|
328
|
+
query: typeof rawOpts.query === 'string' && rawOpts.query.length > MAX_PLUGIN_QUERY_LEN
|
|
329
|
+
? rawOpts.query.slice(0, MAX_PLUGIN_QUERY_LEN) : rawOpts.query,
|
|
330
|
+
limit: typeof rawOpts.limit === 'number' && Number.isFinite(rawOpts.limit)
|
|
331
|
+
? Math.min(Math.floor(Math.max(rawOpts.limit, 1)), MAX_PLUGIN_LIMIT) : rawOpts.limit,
|
|
332
|
+
};
|
|
300
333
|
const searchResult = searchPlugins(result.registry, opts);
|
|
301
334
|
return createResult(searchResult);
|
|
302
335
|
}
|
|
@@ -98,7 +98,12 @@ export const workflowTools = [
|
|
|
98
98
|
handler: async (input) => {
|
|
99
99
|
const store = loadWorkflowStore();
|
|
100
100
|
const template = input.template;
|
|
101
|
-
|
|
101
|
+
// Cap task description to prevent DoS via unbounded string stored in
|
|
102
|
+
// workflow record and serialised to disk.
|
|
103
|
+
const MAX_WORKFLOW_TASK_LEN = 16 * 1024;
|
|
104
|
+
const rawTask = input.task;
|
|
105
|
+
const task = typeof rawTask === 'string' && rawTask.length > MAX_WORKFLOW_TASK_LEN
|
|
106
|
+
? rawTask.slice(0, MAX_WORKFLOW_TASK_LEN) : rawTask;
|
|
102
107
|
const options = input.options || {};
|
|
103
108
|
const dryRun = options.dryRun;
|
|
104
109
|
// Build workflow from template or inline
|
|
@@ -192,7 +197,19 @@ export const workflowTools = [
|
|
|
192
197
|
handler: async (input) => {
|
|
193
198
|
const store = loadWorkflowStore();
|
|
194
199
|
const workflowId = `workflow-${Date.now()}-${randomBytes(6).toString('hex')}`;
|
|
195
|
-
|
|
200
|
+
// Cap string fields before storing to disk.
|
|
201
|
+
const MAX_WF_NAME_LEN = 512;
|
|
202
|
+
const MAX_WF_DESC_LEN = 16 * 1024;
|
|
203
|
+
const MAX_WF_STEPS = 500;
|
|
204
|
+
const rawWfName = input.name;
|
|
205
|
+
const wfName = typeof rawWfName === 'string' && rawWfName.length > MAX_WF_NAME_LEN
|
|
206
|
+
? rawWfName.slice(0, MAX_WF_NAME_LEN) : rawWfName;
|
|
207
|
+
const rawWfDesc = input.description;
|
|
208
|
+
const wfDesc = typeof rawWfDesc === 'string' && rawWfDesc.length > MAX_WF_DESC_LEN
|
|
209
|
+
? rawWfDesc.slice(0, MAX_WF_DESC_LEN) : rawWfDesc;
|
|
210
|
+
const rawSteps = input.steps || [];
|
|
211
|
+
const cappedSteps = rawSteps.slice(0, MAX_WF_STEPS);
|
|
212
|
+
const steps = cappedSteps.map((s, i) => ({
|
|
196
213
|
stepId: `step-${i + 1}`,
|
|
197
214
|
name: s.name || `Step ${i + 1}`,
|
|
198
215
|
type: s.type || 'task',
|
|
@@ -201,8 +218,8 @@ export const workflowTools = [
|
|
|
201
218
|
}));
|
|
202
219
|
const workflow = {
|
|
203
220
|
workflowId,
|
|
204
|
-
name:
|
|
205
|
-
description:
|
|
221
|
+
name: wfName,
|
|
222
|
+
description: wfDesc,
|
|
206
223
|
steps,
|
|
207
224
|
status: steps.length > 0 ? 'ready' : 'draft',
|
|
208
225
|
currentStep: 0,
|
|
@@ -355,8 +372,14 @@ export const workflowTools = [
|
|
|
355
372
|
}
|
|
356
373
|
// Sort by creation date (newest first)
|
|
357
374
|
workflows.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
358
|
-
// Apply limit
|
|
359
|
-
|
|
375
|
+
// Apply limit — cap to 1 000 to prevent returning the full (potentially
|
|
376
|
+
// large) in-memory workflow store in one response, which could cause OOM
|
|
377
|
+
// or excessive serialisation latency.
|
|
378
|
+
const MAX_WORKFLOW_LIMIT = 1_000;
|
|
379
|
+
const rawLimit = typeof input.limit === 'number' ? input.limit : 20;
|
|
380
|
+
const limit = Number.isFinite(rawLimit) && rawLimit > 0
|
|
381
|
+
? Math.min(Math.floor(rawLimit), MAX_WORKFLOW_LIMIT)
|
|
382
|
+
: 20;
|
|
360
383
|
const totalCount = workflows.length;
|
|
361
384
|
workflows = workflows.slice(0, limit);
|
|
362
385
|
return {
|
|
@@ -145,7 +145,12 @@ export class EWCConsolidator {
|
|
|
145
145
|
for (const newPattern of newPatterns) {
|
|
146
146
|
if (!newPattern.embedding || newPattern.embedding.length === 0)
|
|
147
147
|
continue;
|
|
148
|
-
|
|
148
|
+
// Cap pattern ID length: unbounded IDs fill both the Map key and the
|
|
149
|
+
// modifiedPatterns/protectedPatterns result arrays without any limit.
|
|
150
|
+
const patternId = typeof newPattern.id === 'string'
|
|
151
|
+
? newPattern.id.slice(0, 256)
|
|
152
|
+
: String(newPattern.id).slice(0, 256);
|
|
153
|
+
const existingPattern = this.patterns.get(patternId);
|
|
149
154
|
if (existingPattern) {
|
|
150
155
|
// Calculate EWC penalty for updating existing pattern
|
|
151
156
|
const penalty = this.getPenalty(existingPattern.weights, newPattern.embedding, fisher);
|
|
@@ -153,19 +158,19 @@ export class EWCConsolidator {
|
|
|
153
158
|
const importanceScore = this.calculateImportance(existingPattern);
|
|
154
159
|
if (importanceScore > this.config.importanceThreshold && penalty > this.config.lambda) {
|
|
155
160
|
// Protect high-importance patterns with high penalty
|
|
156
|
-
result.protectedPatterns.push(
|
|
161
|
+
result.protectedPatterns.push(patternId);
|
|
157
162
|
// Apply constrained update: blend old and new based on importance
|
|
158
163
|
const blendFactor = 1 - importanceScore;
|
|
159
164
|
const blendedWeights = this.blendWeights(existingPattern.weights, newPattern.embedding, blendFactor, fisher);
|
|
160
165
|
existingPattern.weights = blendedWeights;
|
|
161
166
|
existingPattern.lastUpdated = Date.now();
|
|
162
|
-
result.modifiedPatterns.push(
|
|
167
|
+
result.modifiedPatterns.push(patternId);
|
|
163
168
|
}
|
|
164
169
|
else {
|
|
165
170
|
// Low importance or low penalty: allow full update
|
|
166
171
|
existingPattern.weights = newPattern.embedding.slice(0, this.config.dimensions);
|
|
167
172
|
existingPattern.lastUpdated = Date.now();
|
|
168
|
-
result.modifiedPatterns.push(
|
|
173
|
+
result.modifiedPatterns.push(patternId);
|
|
169
174
|
}
|
|
170
175
|
// Update Fisher diagonal for this pattern
|
|
171
176
|
existingPattern.fisherDiagonal = fisher;
|
|
@@ -174,7 +179,7 @@ export class EWCConsolidator {
|
|
|
174
179
|
else {
|
|
175
180
|
// New pattern: add directly
|
|
176
181
|
const weights = {
|
|
177
|
-
id:
|
|
182
|
+
id: patternId,
|
|
178
183
|
weights: newPattern.embedding.slice(0, this.config.dimensions),
|
|
179
184
|
fisherDiagonal: fisher,
|
|
180
185
|
importance: 0.5,
|
|
@@ -184,8 +189,8 @@ export class EWCConsolidator {
|
|
|
184
189
|
type: newPattern.type,
|
|
185
190
|
description: newPattern.description
|
|
186
191
|
};
|
|
187
|
-
this.patterns.set(
|
|
188
|
-
result.modifiedPatterns.push(
|
|
192
|
+
this.patterns.set(patternId, weights);
|
|
193
|
+
result.modifiedPatterns.push(patternId);
|
|
189
194
|
}
|
|
190
195
|
result.patternsConsolidated++;
|
|
191
196
|
}
|
|
@@ -209,7 +214,10 @@ export class EWCConsolidator {
|
|
|
209
214
|
return result;
|
|
210
215
|
}
|
|
211
216
|
catch (error) {
|
|
212
|
-
|
|
217
|
+
// Sanitize: strip filesystem paths and cap length so internal error
|
|
218
|
+
// messages are not reflected verbatim into CallerResult.error.
|
|
219
|
+
const rawMsg = error instanceof Error ? error.message : String(error);
|
|
220
|
+
result.error = rawMsg.replace(/\/[^\s:]+(\/|(?=\s|:|$))/g, '<path>/').slice(0, 500);
|
|
213
221
|
result.duration = performance.now() - startTime;
|
|
214
222
|
return result;
|
|
215
223
|
}
|
|
@@ -513,6 +521,10 @@ export class EWCConsolidator {
|
|
|
513
521
|
if (!fs.existsSync(this.config.storagePath)) {
|
|
514
522
|
throw new Error('No persisted state found');
|
|
515
523
|
}
|
|
524
|
+
const fileSize = fs.statSync(this.config.storagePath).size;
|
|
525
|
+
if (fileSize > 50 * 1024 * 1024) {
|
|
526
|
+
throw new Error(`EWC state file too large (${fileSize} bytes); refusing to load`);
|
|
527
|
+
}
|
|
516
528
|
const content = fs.readFileSync(this.config.storagePath, 'utf-8');
|
|
517
529
|
const state = JSON.parse(content);
|
|
518
530
|
// Validate version
|
|
@@ -551,8 +563,12 @@ export class EWCConsolidator {
|
|
|
551
563
|
this.patterns.set(id, pattern);
|
|
552
564
|
}
|
|
553
565
|
}
|
|
554
|
-
// Restore history
|
|
555
|
-
|
|
566
|
+
// Restore history — cap to last 100 entries so a crafted state file cannot
|
|
567
|
+
// bloat the in-memory array (each entry is a small object, but the array is
|
|
568
|
+
// summed on every getConsolidationStats() call which is O(n)).
|
|
569
|
+
this.consolidationHistory = Array.isArray(state.consolidationHistory)
|
|
570
|
+
? state.consolidationHistory.slice(-100)
|
|
571
|
+
: [];
|
|
556
572
|
// Update config from persisted values, clamped to a sensible range to
|
|
557
573
|
// prevent negative/NaN lambda from inverting the regularization sign.
|
|
558
574
|
if (state.config && typeof state.config.lambda === 'number'
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module v1/cli/intelligence
|
|
12
12
|
*/
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, renameSync } from 'node:fs';
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
// ============================================================================
|
|
@@ -332,7 +332,7 @@ class LocalReasoningBank {
|
|
|
332
332
|
loadFromDisk() {
|
|
333
333
|
try {
|
|
334
334
|
const path = getPatternsPath();
|
|
335
|
-
if (existsSync(path)) {
|
|
335
|
+
if (existsSync(path) && statSync(path).size <= 50 * 1024 * 1024) {
|
|
336
336
|
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
337
337
|
if (Array.isArray(data)) {
|
|
338
338
|
// Validate each persisted pattern. The patterns file is part of the
|
|
@@ -393,9 +393,10 @@ class LocalReasoningBank {
|
|
|
393
393
|
renameSync(tmp, path);
|
|
394
394
|
this.dirty = false;
|
|
395
395
|
}
|
|
396
|
-
catch
|
|
396
|
+
catch {
|
|
397
397
|
// Log but don't throw - persistence failures shouldn't break training
|
|
398
|
-
|
|
398
|
+
// Do not reflect raw error to avoid leaking internal paths
|
|
399
|
+
console.error('Failed to persist patterns');
|
|
399
400
|
}
|
|
400
401
|
}
|
|
401
402
|
/**
|
|
@@ -548,7 +549,7 @@ let globalStats = {
|
|
|
548
549
|
function loadPersistedStats() {
|
|
549
550
|
try {
|
|
550
551
|
const path = getStatsPath();
|
|
551
|
-
if (existsSync(path)) {
|
|
552
|
+
if (existsSync(path) && statSync(path).size <= 10 * 1024 * 1024) {
|
|
552
553
|
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
553
554
|
if (data && typeof data === 'object') {
|
|
554
555
|
globalStats.trajectoriesRecorded = data.trajectoriesRecorded ?? 0;
|
|
@@ -599,7 +600,7 @@ async function _doInitializeIntelligence(config) {
|
|
|
599
600
|
// Seed neural learned patterns from @monomind/neural's LearningBridge flush.
|
|
600
601
|
// This is the A→B bridge reader: connects the automatic learning loop to routing.
|
|
601
602
|
const neuralPatternsPath = join(getDataDir(), 'patterns.json');
|
|
602
|
-
if (existsSync(neuralPatternsPath)) {
|
|
603
|
+
if (existsSync(neuralPatternsPath) && statSync(neuralPatternsPath).size <= 50 * 1024 * 1024) {
|
|
603
604
|
try {
|
|
604
605
|
const { generateEmbedding: genEmb } = await import('./memory-initializer.js').catch(() => ({ generateEmbedding: null }));
|
|
605
606
|
const raw = readFileSync(neuralPatternsPath, 'utf-8');
|
|
@@ -804,6 +805,10 @@ export async function recordTrajectory(steps, verdict) {
|
|
|
804
805
|
}
|
|
805
806
|
}
|
|
806
807
|
export async function findSimilarPatterns(query, options) {
|
|
808
|
+
// Cap query length to prevent OOM via embedding generation on unbounded input
|
|
809
|
+
if (typeof query === 'string' && query.length > 2000) {
|
|
810
|
+
query = query.slice(0, 2000);
|
|
811
|
+
}
|
|
807
812
|
if (!reasoningBank) {
|
|
808
813
|
const init = await initializeIntelligence();
|
|
809
814
|
if (!init.success)
|
|
@@ -943,9 +948,11 @@ export function benchmarkAdaptation(iterations = 1000) {
|
|
|
943
948
|
if (!sonaCoordinator) {
|
|
944
949
|
return { totalMs: 0, avgMs: 0, minMs: 0, maxMs: 0, targetMet: false };
|
|
945
950
|
}
|
|
951
|
+
// Cap iterations to prevent OOM/CPU exhaustion from unbounded caller input
|
|
952
|
+
const safeIterations = Math.min(Math.max(1, iterations >>> 0), 100_000);
|
|
946
953
|
const times = [];
|
|
947
954
|
const testEmbedding = Array.from({ length: 384 }, () => Math.random());
|
|
948
|
-
for (let i = 0; i <
|
|
955
|
+
for (let i = 0; i < safeIterations; i++) {
|
|
949
956
|
const start = performance.now();
|
|
950
957
|
sonaCoordinator.recordSignal({
|
|
951
958
|
type: 'test',
|
|
@@ -956,7 +963,7 @@ export function benchmarkAdaptation(iterations = 1000) {
|
|
|
956
963
|
times.push(performance.now() - start);
|
|
957
964
|
}
|
|
958
965
|
const totalMs = times.reduce((a, b) => a + b, 0);
|
|
959
|
-
const avgMs = totalMs /
|
|
966
|
+
const avgMs = totalMs / safeIterations;
|
|
960
967
|
const minMs = Math.min(...times);
|
|
961
968
|
const maxMs = Math.max(...times);
|
|
962
969
|
return {
|
|
@@ -980,21 +987,75 @@ function loadSonaRoutingPatterns() {
|
|
|
980
987
|
const sonaPath = join(process.cwd(), '.swarm', 'sona-patterns.json');
|
|
981
988
|
if (!existsSync(sonaPath))
|
|
982
989
|
return [];
|
|
990
|
+
if (statSync(sonaPath).size > 10 * 1024 * 1024)
|
|
991
|
+
return [];
|
|
983
992
|
const raw = JSON.parse(readFileSync(sonaPath, 'utf-8'));
|
|
984
993
|
const persisted = raw;
|
|
985
|
-
if (!persisted.patterns || typeof persisted.patterns !== 'object')
|
|
994
|
+
if (!persisted.patterns || typeof persisted.patterns !== 'object' || Array.isArray(persisted.patterns))
|
|
986
995
|
return [];
|
|
987
996
|
const now = Date.now();
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
997
|
+
const results = [];
|
|
998
|
+
// Cap total entries to prevent DoS via an unbounded patterns map.
|
|
999
|
+
// sona-patterns.json is written by the SONA optimizer but could be
|
|
1000
|
+
// replaced by a malicious IPFS bundle — validate every field before use.
|
|
1001
|
+
const MAX_SONA_ENTRIES = 500;
|
|
1002
|
+
let entryCount = 0;
|
|
1003
|
+
for (const [key, p] of Object.entries(persisted.patterns)) {
|
|
1004
|
+
// Prototype pollution guard — skip __proto__ / constructor / prototype keys.
|
|
1005
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
|
|
1006
|
+
continue;
|
|
1007
|
+
if (typeof key !== 'string' || key.length === 0 || key.length > 256)
|
|
1008
|
+
continue;
|
|
1009
|
+
if (entryCount++ >= MAX_SONA_ENTRIES)
|
|
1010
|
+
break;
|
|
1011
|
+
if (!p || typeof p !== 'object')
|
|
1012
|
+
continue;
|
|
1013
|
+
// Validate keywords: must be a bounded array of short strings.
|
|
1014
|
+
const rawKw = p.keywords;
|
|
1015
|
+
let keywords;
|
|
1016
|
+
if (rawKw === undefined) {
|
|
1017
|
+
keywords = [key];
|
|
1018
|
+
}
|
|
1019
|
+
else if (Array.isArray(rawKw) && rawKw.length <= 64
|
|
1020
|
+
&& rawKw.every(k => typeof k === 'string' && k.length > 0 && k.length <= 128)) {
|
|
1021
|
+
keywords = rawKw;
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
continue; // malformed keywords — skip entry
|
|
1025
|
+
}
|
|
1026
|
+
// Validate agent string.
|
|
1027
|
+
const agent = p.agent;
|
|
1028
|
+
if (agent !== undefined && (typeof agent !== 'string' || agent.length === 0 || agent.length > 128))
|
|
1029
|
+
continue;
|
|
1030
|
+
// Validate confidence is a finite number in [0, 1].
|
|
1031
|
+
const rawConf = p.confidence;
|
|
1032
|
+
const confidence = rawConf !== undefined ? rawConf : 0.5;
|
|
1033
|
+
if (typeof confidence !== 'number' || !Number.isFinite(confidence) || confidence < 0 || confidence > 1)
|
|
1034
|
+
continue;
|
|
1035
|
+
// Validate usage counts are safe integers.
|
|
1036
|
+
const sc = p.successCount ?? 0;
|
|
1037
|
+
const fc = p.failureCount ?? 0;
|
|
1038
|
+
if (typeof sc !== 'number' || !Number.isFinite(sc) || sc < 0 || sc > 1e9)
|
|
1039
|
+
continue;
|
|
1040
|
+
if (typeof fc !== 'number' || !Number.isFinite(fc) || fc < 0 || fc > 1e9)
|
|
1041
|
+
continue;
|
|
1042
|
+
// Validate createdAt is a reasonable epoch ms value.
|
|
1043
|
+
const rawTs = p.createdAt;
|
|
1044
|
+
const createdAt = (rawTs !== undefined && typeof rawTs === 'number' && Number.isFinite(rawTs) && rawTs >= 0 && rawTs <= 9.9e12)
|
|
1045
|
+
? rawTs
|
|
1046
|
+
: now;
|
|
1047
|
+
results.push({
|
|
1048
|
+
id: `sona:${key}`,
|
|
1049
|
+
type: agent ?? 'routing',
|
|
1050
|
+
content: keywords.join(' '),
|
|
1051
|
+
confidence,
|
|
1052
|
+
usageCount: sc + fc,
|
|
1053
|
+
embedding: [],
|
|
1054
|
+
createdAt,
|
|
1055
|
+
lastUsedAt: now,
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
return results;
|
|
998
1059
|
}
|
|
999
1060
|
catch {
|
|
1000
1061
|
return [];
|
|
@@ -404,8 +404,12 @@ async function getOrBuildHnswIndex(db) {
|
|
|
404
404
|
_hnswIndexBuilt = true; // Lock prevents concurrent re-build
|
|
405
405
|
try {
|
|
406
406
|
const memPkg = await import('@monoes/memory');
|
|
407
|
-
if (!memPkg?.HNSWIndex)
|
|
407
|
+
if (!memPkg?.HNSWIndex) {
|
|
408
|
+
// Release the lock so a later retry can succeed if the package becomes available.
|
|
409
|
+
_hnswIndexBuilt = false;
|
|
410
|
+
_hnswBuildFailedAt = Date.now();
|
|
408
411
|
return null;
|
|
412
|
+
}
|
|
409
413
|
const rows = db.prepare(`SELECT id, embedding FROM memory_entries WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) AND embedding IS NOT NULL`).all(Date.now());
|
|
410
414
|
const valid = [];
|
|
411
415
|
for (const row of rows) {
|
|
@@ -447,7 +451,22 @@ export async function bridgeStoreEntry(options) {
|
|
|
447
451
|
if (!ctx)
|
|
448
452
|
return null;
|
|
449
453
|
try {
|
|
450
|
-
const
|
|
454
|
+
const rawKey = options.key;
|
|
455
|
+
const rawValue = options.value;
|
|
456
|
+
// SECURITY: defensive caps so no caller — current or future — can pass an
|
|
457
|
+
// unbounded string directly into embedder.embed (hash fallback is O(n)) or
|
|
458
|
+
// inflate the AgentDB row beyond practical limits.
|
|
459
|
+
// Memory-tools already validates to 1 MB; these caps are a last-resort
|
|
460
|
+
// backstop for any internal caller that forgets to pre-truncate.
|
|
461
|
+
const BRIDGE_MAX_KEY_LEN = 4 * 1024; // 4 KB — generous for any realistic key
|
|
462
|
+
const BRIDGE_MAX_VALUE_LEN = 1024 * 1024; // 1 MB — matches memory-tools validator
|
|
463
|
+
const key = typeof rawKey === 'string' && rawKey.length > BRIDGE_MAX_KEY_LEN
|
|
464
|
+
? rawKey.slice(0, BRIDGE_MAX_KEY_LEN) : rawKey;
|
|
465
|
+
const value = typeof rawValue === 'string' && rawValue.length > BRIDGE_MAX_VALUE_LEN
|
|
466
|
+
? rawValue.slice(0, BRIDGE_MAX_VALUE_LEN) : rawValue;
|
|
467
|
+
const namespace = options.namespace ?? 'default';
|
|
468
|
+
const rawTags = options.tags ?? [];
|
|
469
|
+
const ttl = options.ttl;
|
|
451
470
|
// SECURITY: cap tags array length and per-tag length. Without these, any
|
|
452
471
|
// memory_store caller (every spawned agent) could submit
|
|
453
472
|
// tags: new Array(1e5).fill("x".repeat(1e4)) → ~1GB persisted blob,
|