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.
- 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 +13 -6
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +21 -11
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/intelligence.cjs +7 -2
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory.cjs +6 -1
- package/.claude/helpers/router.cjs +5 -2
- package/.claude/helpers/session.cjs +2 -0
- package/.claude/helpers/statusline.cjs +10 -2
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- 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 +97 -20
- 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 +148 -26
- 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 +53 -12
- 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 +9 -3
- 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
|
@@ -68,6 +68,11 @@ export class CdpClient {
|
|
|
68
68
|
reject(new Error('CDP not connected'));
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
+
// Cap in-flight commands to prevent unbounded Map growth
|
|
72
|
+
if (this.pendingCommands.size >= 1000) {
|
|
73
|
+
reject(new Error('CDP command queue full (>1000 in-flight commands)'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
71
76
|
const id = this.nextId++;
|
|
72
77
|
const cmd = { id, method, params };
|
|
73
78
|
if (sessionId)
|
|
@@ -88,7 +93,12 @@ export class CdpClient {
|
|
|
88
93
|
if (!this.eventListeners.has(event)) {
|
|
89
94
|
this.eventListeners.set(event, new Set());
|
|
90
95
|
}
|
|
91
|
-
this.eventListeners.get(event)
|
|
96
|
+
const listeners = this.eventListeners.get(event);
|
|
97
|
+
// Cap listeners per event to prevent unbounded Set growth
|
|
98
|
+
if (listeners.size >= 100) {
|
|
99
|
+
throw new Error(`CDP event listener limit reached for event: ${event}`);
|
|
100
|
+
}
|
|
101
|
+
listeners.add(fn);
|
|
92
102
|
return () => this.eventListeners.get(event)?.delete(fn);
|
|
93
103
|
}
|
|
94
104
|
once(event, sessionId) {
|
|
@@ -120,16 +130,24 @@ export class CdpClient {
|
|
|
120
130
|
return this.connected;
|
|
121
131
|
}
|
|
122
132
|
}
|
|
133
|
+
const CDP_RESPONSE_SIZE_LIMIT = 10 * 1024 * 1024; // 10 MB
|
|
134
|
+
async function readCdpJson(res) {
|
|
135
|
+
const text = await res.text();
|
|
136
|
+
if (text.length > CDP_RESPONSE_SIZE_LIMIT) {
|
|
137
|
+
throw new Error(`CDP response too large: ${text.length} bytes`);
|
|
138
|
+
}
|
|
139
|
+
return JSON.parse(text);
|
|
140
|
+
}
|
|
123
141
|
export async function fetchTargets(port) {
|
|
124
142
|
const res = await fetch(`http://127.0.0.1:${port}/json/list`);
|
|
125
143
|
if (!res.ok)
|
|
126
144
|
throw new Error(`Failed to fetch targets: ${res.statusText}`);
|
|
127
|
-
return res
|
|
145
|
+
return readCdpJson(res);
|
|
128
146
|
}
|
|
129
147
|
export async function fetchNewTarget(port, url) {
|
|
130
148
|
const res = await fetch(`http://127.0.0.1:${port}/json/new?${url}`);
|
|
131
149
|
if (!res.ok)
|
|
132
150
|
throw new Error(`Failed to create target: ${res.statusText}`);
|
|
133
|
-
return res
|
|
151
|
+
return readCdpJson(res);
|
|
134
152
|
}
|
|
135
153
|
//# sourceMappingURL=cdp.js.map
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { writeFile } from 'fs/promises';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { tmpdir } from 'os';
|
|
2
|
+
import { join, resolve, relative, isAbsolute } from 'path';
|
|
3
|
+
import { tmpdir, homedir } from 'os';
|
|
4
|
+
const MAX_CONCURRENT_SESSIONS = 100;
|
|
5
|
+
const MAX_REQUESTS_PER_SESSION = 5_000;
|
|
4
6
|
const _sessions = new Map();
|
|
7
|
+
/** Validate output path is within cwd or home dir to prevent path traversal. */
|
|
8
|
+
function safeOutputPath(p) {
|
|
9
|
+
const resolved = resolve(p);
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const home = homedir();
|
|
12
|
+
const relCwd = relative(cwd, resolved);
|
|
13
|
+
const relHome = relative(home, resolved);
|
|
14
|
+
if ((!relCwd.startsWith('..') && !isAbsolute(relCwd)) ||
|
|
15
|
+
(!relHome.startsWith('..') && !isAbsolute(relHome))) {
|
|
16
|
+
return resolved;
|
|
17
|
+
}
|
|
18
|
+
// Reject out-of-scope paths — fall back to tmpdir
|
|
19
|
+
return join(tmpdir(), `monomind-har-${Date.now()}.har`);
|
|
20
|
+
}
|
|
5
21
|
export async function startHarRecording(client, sessionId) {
|
|
6
22
|
if (_sessions.has(sessionId))
|
|
7
23
|
throw new Error('HAR recording already in progress');
|
|
24
|
+
if (_sessions.size >= MAX_CONCURRENT_SESSIONS) {
|
|
25
|
+
throw new Error(`HAR recording limit reached (max ${MAX_CONCURRENT_SESSIONS} concurrent sessions)`);
|
|
26
|
+
}
|
|
8
27
|
const requests = new Map();
|
|
9
28
|
const startTime = Date.now();
|
|
10
29
|
const startWallMs = startTime;
|
|
@@ -17,6 +36,9 @@ export async function startHarRecording(client, sessionId) {
|
|
|
17
36
|
const offReq = client.on('Network.requestWillBeSent', (params, sid) => {
|
|
18
37
|
if (sid !== sessionId)
|
|
19
38
|
return;
|
|
39
|
+
// Cap to prevent unbounded memory growth on high-traffic pages
|
|
40
|
+
if (requests.size >= MAX_REQUESTS_PER_SESSION)
|
|
41
|
+
return;
|
|
20
42
|
const p = params;
|
|
21
43
|
requests.set(p.requestId, {
|
|
22
44
|
id: p.requestId,
|
|
@@ -78,9 +100,9 @@ export async function stopHarRecording(client, sessionId, outputPath, captureRes
|
|
|
78
100
|
}
|
|
79
101
|
}
|
|
80
102
|
const har = buildHar(Array.from(state.requests.values()), state.startTime);
|
|
81
|
-
const
|
|
82
|
-
await writeFile(
|
|
83
|
-
return
|
|
103
|
+
const safePath = outputPath ? safeOutputPath(outputPath) : join(tmpdir(), `monomind-har-${Date.now()}.har`);
|
|
104
|
+
await writeFile(safePath, JSON.stringify(har, null, 2));
|
|
105
|
+
return safePath;
|
|
84
106
|
}
|
|
85
107
|
export function getHarStatus(sessionId) {
|
|
86
108
|
const state = _sessions.get(sessionId);
|
|
@@ -19,7 +19,7 @@ function updateSwarmActivityMetrics(agentCountDelta) {
|
|
|
19
19
|
timestamp: new Date().toISOString(),
|
|
20
20
|
swarm: { active: false, agent_count: 0, coordination_active: false },
|
|
21
21
|
};
|
|
22
|
-
if (fs.existsSync(activityPath)) {
|
|
22
|
+
if (fs.existsSync(activityPath) && fs.statSync(activityPath).size <= 10 * 1024 * 1024) {
|
|
23
23
|
data = JSON.parse(fs.readFileSync(activityPath, 'utf-8'));
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
@@ -113,8 +113,8 @@ const spawnCommand = {
|
|
|
113
113
|
{ command: 'monomind agent spawn -t researcher --task "Research React 19"', description: 'Spawn researcher with task' }
|
|
114
114
|
],
|
|
115
115
|
action: async (ctx) => {
|
|
116
|
-
let agentType = ctx.flags.type;
|
|
117
|
-
let agentName = ctx.flags.name;
|
|
116
|
+
let agentType = ctx.flags.type?.slice(0, 64) ?? '';
|
|
117
|
+
let agentName = ctx.flags.name?.slice(0, 128) ?? '';
|
|
118
118
|
// Interactive mode if type not specified
|
|
119
119
|
if (!agentType && ctx.interactive) {
|
|
120
120
|
agentType = await select({
|
|
@@ -123,7 +123,7 @@ const spawnCommand = {
|
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
// Semantic routing: if --type absent but --task provided, use RouteLayer
|
|
126
|
-
const taskDescription = ctx.flags.task;
|
|
126
|
+
const taskDescription = ctx.flags.task?.slice(0, 2048);
|
|
127
127
|
if (!agentType && taskDescription) {
|
|
128
128
|
try {
|
|
129
129
|
// Builds a RouteLayer with a real local embedding model + headless
|
|
@@ -453,7 +453,10 @@ const metricsCommand = {
|
|
|
453
453
|
const files = readdirSync(agentsDir).filter(f => f.endsWith('.json'));
|
|
454
454
|
for (const file of files) {
|
|
455
455
|
try {
|
|
456
|
-
const
|
|
456
|
+
const agentFilePath = join(agentsDir, file);
|
|
457
|
+
if (statSync(agentFilePath).size > 512 * 1024)
|
|
458
|
+
continue; // skip files > 512 KB
|
|
459
|
+
const data = JSON.parse(readFileSync(agentFilePath, 'utf-8'));
|
|
457
460
|
totalAgents++;
|
|
458
461
|
const agType = data.type || 'unknown';
|
|
459
462
|
if (!typeCounts[agType])
|
|
@@ -475,7 +478,7 @@ const metricsCommand = {
|
|
|
475
478
|
}
|
|
476
479
|
// Read swarm activity for additional state
|
|
477
480
|
const activityFile = join(swarmDir, 'swarm-activity.json');
|
|
478
|
-
if (existsSync(activityFile)) {
|
|
481
|
+
if (existsSync(activityFile) && statSync(activityFile).size <= 10 * 1024 * 1024) {
|
|
479
482
|
try {
|
|
480
483
|
const activity = JSON.parse(readFileSync(activityFile, 'utf-8'));
|
|
481
484
|
if (activity.totalAgents && totalAgents === 0)
|
|
@@ -801,9 +804,9 @@ const logsCommand = {
|
|
|
801
804
|
{ command: 'monomind agent logs -l error --since 1h', description: 'Show errors from last hour' }
|
|
802
805
|
],
|
|
803
806
|
action: async (ctx) => {
|
|
804
|
-
const agentId = ctx.args[0] || ctx.flags.id;
|
|
807
|
+
const agentId = ((ctx.args[0] || ctx.flags.id) ?? '').slice(0, 128);
|
|
805
808
|
const tail = ctx.flags.tail;
|
|
806
|
-
const level = ctx.flags.level;
|
|
809
|
+
const level = ctx.flags.level?.slice(0, 32);
|
|
807
810
|
if (!agentId) {
|
|
808
811
|
output.printError('Agent ID is required. Use --id or -i');
|
|
809
812
|
return { success: false, exitCode: 1 };
|
|
@@ -28,6 +28,19 @@ async function getASTAnalyzer() {
|
|
|
28
28
|
async function getGraphAnalyzer() {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Write analysis output to a file, constraining the path to the current working
|
|
33
|
+
* directory to prevent path traversal attacks via --output /etc/cron.d/x or
|
|
34
|
+
* similar. Throws if the resolved path escapes cwd.
|
|
35
|
+
*/
|
|
36
|
+
async function safeWriteOutputFile(outputFile, data) {
|
|
37
|
+
const projectRoot = path.resolve(process.cwd());
|
|
38
|
+
const fullPath = path.resolve(process.cwd(), outputFile);
|
|
39
|
+
if (!fullPath.startsWith(projectRoot + path.sep) && fullPath !== projectRoot) {
|
|
40
|
+
throw new Error(`Output path must resolve within the project directory: ${projectRoot}`);
|
|
41
|
+
}
|
|
42
|
+
await writeFile(fullPath, data);
|
|
43
|
+
}
|
|
31
44
|
// Diff subcommand
|
|
32
45
|
const diffCommand = {
|
|
33
46
|
name: 'diff',
|
|
@@ -557,7 +570,7 @@ const astCommand = {
|
|
|
557
570
|
if (formatType === 'json') {
|
|
558
571
|
const jsonOutput = { files: results, totals };
|
|
559
572
|
if (outputFile) {
|
|
560
|
-
await
|
|
573
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
561
574
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
562
575
|
}
|
|
563
576
|
else {
|
|
@@ -649,7 +662,7 @@ const astCommand = {
|
|
|
649
662
|
}
|
|
650
663
|
}
|
|
651
664
|
if (outputFile) {
|
|
652
|
-
await
|
|
665
|
+
await safeWriteOutputFile(outputFile, JSON.stringify({ files: results, totals }, null, 2));
|
|
653
666
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
654
667
|
}
|
|
655
668
|
return { success: true, data: { files: results, totals } };
|
|
@@ -750,7 +763,7 @@ const complexityAstCommand = {
|
|
|
750
763
|
if (formatType === 'json') {
|
|
751
764
|
const jsonOutput = { files: results, summary: { total: results.length, flagged: flaggedCount, avgComplexity, threshold } };
|
|
752
765
|
if (outputFile) {
|
|
753
|
-
await
|
|
766
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
754
767
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
755
768
|
}
|
|
756
769
|
else {
|
|
@@ -800,7 +813,7 @@ const complexityAstCommand = {
|
|
|
800
813
|
output.writeln(output.dim(` ... and ${results.length - 15} more files`));
|
|
801
814
|
}
|
|
802
815
|
if (outputFile) {
|
|
803
|
-
await
|
|
816
|
+
await safeWriteOutputFile(outputFile, JSON.stringify({ files: results, summary: { total: results.length, flagged: flaggedCount, avgComplexity, threshold } }, null, 2));
|
|
804
817
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
805
818
|
}
|
|
806
819
|
return { success: true, data: { files: results, flaggedCount } };
|
|
@@ -907,7 +920,7 @@ const symbolsCommand = {
|
|
|
907
920
|
symbols.sort((a, b) => a.file.localeCompare(b.file) || a.name.localeCompare(b.name));
|
|
908
921
|
if (formatType === 'json') {
|
|
909
922
|
if (outputFile) {
|
|
910
|
-
await
|
|
923
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(symbols, null, 2));
|
|
911
924
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
912
925
|
}
|
|
913
926
|
else {
|
|
@@ -941,7 +954,7 @@ const symbolsCommand = {
|
|
|
941
954
|
output.writeln(output.dim(` ... and ${symbols.length - 30} more symbols`));
|
|
942
955
|
}
|
|
943
956
|
if (outputFile) {
|
|
944
|
-
await
|
|
957
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(symbols, null, 2));
|
|
945
958
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
946
959
|
}
|
|
947
960
|
return { success: true, data: symbols };
|
|
@@ -1043,7 +1056,7 @@ const importsCommand = {
|
|
|
1043
1056
|
fileImports: Object.fromEntries(fileImports),
|
|
1044
1057
|
};
|
|
1045
1058
|
if (outputFile) {
|
|
1046
|
-
await
|
|
1059
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
1047
1060
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
1048
1061
|
}
|
|
1049
1062
|
else {
|
|
@@ -1081,7 +1094,7 @@ const importsCommand = {
|
|
|
1081
1094
|
output.writeln(output.dim(` ... and ${sortedImports.length - 20} more imports`));
|
|
1082
1095
|
}
|
|
1083
1096
|
if (outputFile) {
|
|
1084
|
-
await
|
|
1097
|
+
await safeWriteOutputFile(outputFile, JSON.stringify({
|
|
1085
1098
|
imports: Object.fromEntries(sortedImports),
|
|
1086
1099
|
fileImports: Object.fromEntries(fileImports),
|
|
1087
1100
|
}, null, 2));
|
|
@@ -1378,7 +1391,8 @@ const boundariesCommand = {
|
|
|
1378
1391
|
],
|
|
1379
1392
|
action: async (ctx) => {
|
|
1380
1393
|
const targetDir = ctx.args[0] || ctx.cwd;
|
|
1381
|
-
const
|
|
1394
|
+
const rawPartitions = ctx.flags.partitions || 2;
|
|
1395
|
+
const numPartitions = Number.isFinite(rawPartitions) ? Math.max(1, Math.min(rawPartitions, 100)) : 2;
|
|
1382
1396
|
const outputFile = ctx.flags.output;
|
|
1383
1397
|
const format = ctx.flags.format || 'text';
|
|
1384
1398
|
output.printInfo(`Analyzing code boundaries in: ${output.highlight(targetDir)}`);
|
|
@@ -1406,7 +1420,7 @@ const boundariesCommand = {
|
|
|
1406
1420
|
circularDependencies: result.circularDependencies,
|
|
1407
1421
|
};
|
|
1408
1422
|
if (outputFile) {
|
|
1409
|
-
await
|
|
1423
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
1410
1424
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
1411
1425
|
}
|
|
1412
1426
|
else {
|
|
@@ -1420,7 +1434,7 @@ const boundariesCommand = {
|
|
|
1420
1434
|
highlightCycles: true,
|
|
1421
1435
|
});
|
|
1422
1436
|
if (outputFile) {
|
|
1423
|
-
await
|
|
1437
|
+
await safeWriteOutputFile(outputFile, dotOutput);
|
|
1424
1438
|
output.printSuccess(`DOT graph written to ${outputFile}`);
|
|
1425
1439
|
output.writeln(output.dim('Visualize with: dot -Tpng -o graph.png ' + outputFile));
|
|
1426
1440
|
}
|
|
@@ -1480,7 +1494,7 @@ const boundariesCommand = {
|
|
|
1480
1494
|
}
|
|
1481
1495
|
}
|
|
1482
1496
|
if (outputFile) {
|
|
1483
|
-
await
|
|
1497
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(result, null, 2));
|
|
1484
1498
|
output.printSuccess(`Full results written to ${outputFile}`);
|
|
1485
1499
|
}
|
|
1486
1500
|
return { success: true, data: result };
|
|
@@ -1558,7 +1572,7 @@ const modulesCommand = {
|
|
|
1558
1572
|
statistics: result.statistics,
|
|
1559
1573
|
};
|
|
1560
1574
|
if (outputFile) {
|
|
1561
|
-
await
|
|
1575
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
1562
1576
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
1563
1577
|
}
|
|
1564
1578
|
else {
|
|
@@ -1573,7 +1587,7 @@ const modulesCommand = {
|
|
|
1573
1587
|
highlightCycles: true,
|
|
1574
1588
|
});
|
|
1575
1589
|
if (outputFile) {
|
|
1576
|
-
await
|
|
1590
|
+
await safeWriteOutputFile(outputFile, dotOutput);
|
|
1577
1591
|
output.printSuccess(`DOT graph written to ${outputFile}`);
|
|
1578
1592
|
output.writeln(output.dim('Visualize with: dot -Tpng -o modules.png ' + outputFile));
|
|
1579
1593
|
}
|
|
@@ -1614,7 +1628,7 @@ const modulesCommand = {
|
|
|
1614
1628
|
}
|
|
1615
1629
|
}
|
|
1616
1630
|
if (outputFile) {
|
|
1617
|
-
await
|
|
1631
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(result, null, 2));
|
|
1618
1632
|
output.printSuccess(`Full results written to ${outputFile}`);
|
|
1619
1633
|
}
|
|
1620
1634
|
return { success: true, data: result };
|
|
@@ -1682,7 +1696,8 @@ const dependenciesCommand = {
|
|
|
1682
1696
|
const format = ctx.flags.format || 'text';
|
|
1683
1697
|
const include = (ctx.flags.include || '.ts,.tsx,.js,.jsx,.mjs,.cjs').split(',');
|
|
1684
1698
|
const exclude = (ctx.flags.exclude || 'node_modules,dist,build,.git').split(',');
|
|
1685
|
-
const
|
|
1699
|
+
const rawDepth = ctx.flags.depth || 10;
|
|
1700
|
+
const maxDepth = Number.isFinite(rawDepth) ? Math.max(1, Math.min(rawDepth, 50)) : 10;
|
|
1686
1701
|
output.printInfo(`Building dependency graph for: ${output.highlight(targetDir)}`);
|
|
1687
1702
|
output.writeln();
|
|
1688
1703
|
const spinner = output.createSpinner({ text: 'Scanning files...', spinner: 'dots' });
|
|
@@ -1711,7 +1726,7 @@ const dependenciesCommand = {
|
|
|
1711
1726
|
circularDependencies: circularDeps,
|
|
1712
1727
|
};
|
|
1713
1728
|
if (outputFile) {
|
|
1714
|
-
await
|
|
1729
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
1715
1730
|
output.printSuccess(`Graph written to ${outputFile}`);
|
|
1716
1731
|
}
|
|
1717
1732
|
else {
|
|
@@ -1733,7 +1748,7 @@ const dependenciesCommand = {
|
|
|
1733
1748
|
highlightCycles: true,
|
|
1734
1749
|
});
|
|
1735
1750
|
if (outputFile) {
|
|
1736
|
-
await
|
|
1751
|
+
await safeWriteOutputFile(outputFile, dotOutput);
|
|
1737
1752
|
output.printSuccess(`DOT graph written to ${outputFile}`);
|
|
1738
1753
|
output.writeln(output.dim('Visualize with: dot -Tpng -o deps.png ' + outputFile));
|
|
1739
1754
|
}
|
|
@@ -1791,7 +1806,7 @@ const dependenciesCommand = {
|
|
|
1791
1806
|
metadata: graph.metadata,
|
|
1792
1807
|
circularDependencies: circularDeps,
|
|
1793
1808
|
};
|
|
1794
|
-
await
|
|
1809
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(fullOutput, null, 2));
|
|
1795
1810
|
output.printSuccess(`Full results written to ${outputFile}`);
|
|
1796
1811
|
}
|
|
1797
1812
|
return { success: true };
|
|
@@ -1865,7 +1880,7 @@ const circularCommand = {
|
|
|
1865
1880
|
if (format === 'json') {
|
|
1866
1881
|
const jsonOutput = { cycles: filtered, total: cycles.length, filtered: filtered.length };
|
|
1867
1882
|
if (outputFile) {
|
|
1868
|
-
await
|
|
1883
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
1869
1884
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
1870
1885
|
}
|
|
1871
1886
|
else {
|
|
@@ -1909,7 +1924,7 @@ const circularCommand = {
|
|
|
1909
1924
|
}
|
|
1910
1925
|
}
|
|
1911
1926
|
if (outputFile) {
|
|
1912
|
-
await
|
|
1927
|
+
await safeWriteOutputFile(outputFile, JSON.stringify({ cycles: filtered }, null, 2));
|
|
1913
1928
|
output.printSuccess(`Results written to ${outputFile}`);
|
|
1914
1929
|
}
|
|
1915
1930
|
return { success: true, data: { cycles: filtered } };
|
|
@@ -151,8 +151,11 @@ const configCommand = {
|
|
|
151
151
|
state.maxIterations = validateNumber(maxIter, 1, 1000, state.maxIterations);
|
|
152
152
|
if (timeout)
|
|
153
153
|
state.timeoutMinutes = validateNumber(timeout, 1, 1440, state.timeoutMinutes);
|
|
154
|
-
if (sources)
|
|
155
|
-
|
|
154
|
+
if (sources) {
|
|
155
|
+
// Cap total sources string length before splitting to prevent O(n) split on huge input
|
|
156
|
+
const cappedSources = sources.length > 512 ? sources.slice(0, 512) : sources;
|
|
157
|
+
state.taskSources = validateTaskSources(cappedSources.split(',').map(s => s.trim()).filter(Boolean));
|
|
158
|
+
}
|
|
156
159
|
saveState(state);
|
|
157
160
|
appendLog({ ts: Date.now(), event: 'config-updated', maxIterations: state.maxIterations, timeoutMinutes: state.timeoutMinutes, taskSources: state.taskSources });
|
|
158
161
|
output.writeln(`Config updated: maxIterations=${state.maxIterations}, timeout=${state.timeoutMinutes}min, sources=${state.taskSources.join(',')}`);
|
|
@@ -248,12 +251,17 @@ const historyCommand = {
|
|
|
248
251
|
{ name: 'json', type: 'boolean', description: 'Output as JSON' },
|
|
249
252
|
],
|
|
250
253
|
action: async (ctx) => {
|
|
251
|
-
const
|
|
254
|
+
const rawQuery = (ctx.flags?.query || '');
|
|
252
255
|
const limit = validateNumber(ctx.flags?.limit, 1, 100, 10);
|
|
253
|
-
if (!
|
|
256
|
+
if (!rawQuery) {
|
|
254
257
|
output.writeln('Usage: autopilot history --query "search terms" [--limit N]');
|
|
255
258
|
return { success: false, message: 'Missing --query' };
|
|
256
259
|
}
|
|
260
|
+
if (rawQuery.length > 1024) {
|
|
261
|
+
output.writeln('Error: --query too long (max 1024 characters)');
|
|
262
|
+
return { success: false, message: 'Query too long' };
|
|
263
|
+
}
|
|
264
|
+
const query = rawQuery;
|
|
257
265
|
const learning = await tryLoadLearning();
|
|
258
266
|
if (!learning) {
|
|
259
267
|
output.writeln('Learning not available. No history to search.');
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module v1/cli/commands/benchmark
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
|
-
import { writeFileSync, renameSync, readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { writeFileSync, renameSync, readFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { BenchmarkRunner } from '../benchmarks/benchmark-runner.js';
|
|
11
11
|
// ============================================================================
|
|
@@ -63,9 +63,12 @@ const neuralCommand = {
|
|
|
63
63
|
{ command: 'monomind benchmark neural -d 768 -n 5000', description: 'Higher dimension, more vectors' },
|
|
64
64
|
],
|
|
65
65
|
action: async (ctx) => {
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
66
|
+
const iterationsRaw = parseInt(ctx.flags.iterations || '100', 10);
|
|
67
|
+
const iterations = Number.isFinite(iterationsRaw) ? Math.max(1, Math.min(iterationsRaw, 10_000)) : 100;
|
|
68
|
+
const dimensionRaw = parseInt(ctx.flags.dimension || '384', 10);
|
|
69
|
+
const dimension = Number.isFinite(dimensionRaw) ? Math.max(1, Math.min(dimensionRaw, 4096)) : 384;
|
|
70
|
+
const numVectorsRaw = parseInt(ctx.flags.vectors || '1000', 10);
|
|
71
|
+
const numVectors = Number.isFinite(numVectorsRaw) ? Math.max(1, Math.min(numVectorsRaw, 100_000)) : 1000;
|
|
69
72
|
const outputFormat = ctx.flags.output || 'text';
|
|
70
73
|
output.writeln();
|
|
71
74
|
output.writeln(output.bold('Neural Operations Benchmark'));
|
|
@@ -228,7 +231,8 @@ const memoryCommand = {
|
|
|
228
231
|
{ command: 'monomind benchmark memory', description: 'Run memory benchmarks' },
|
|
229
232
|
],
|
|
230
233
|
action: async (ctx) => {
|
|
231
|
-
const
|
|
234
|
+
const iterationsRaw = parseInt(ctx.flags.iterations || '100', 10);
|
|
235
|
+
const iterations = Number.isFinite(iterationsRaw) ? Math.max(1, Math.min(iterationsRaw, 10_000)) : 100;
|
|
232
236
|
const outputFormat = ctx.flags.output || 'text';
|
|
233
237
|
output.writeln();
|
|
234
238
|
output.writeln(output.bold('Memory Operations Benchmark'));
|
|
@@ -384,7 +388,15 @@ const allCommand = {
|
|
|
384
388
|
if (!existsSync(resultsDir)) {
|
|
385
389
|
mkdirSync(resultsDir, { recursive: true });
|
|
386
390
|
}
|
|
387
|
-
|
|
391
|
+
// Path traversal guard: resolve within resultsDir regardless of whether saveFile is absolute
|
|
392
|
+
const { resolve: resolvePath, basename } = await import('node:path');
|
|
393
|
+
const safeName = basename(saveFile);
|
|
394
|
+
const savePath = resolvePath(resultsDir, safeName);
|
|
395
|
+
const resolvedResultsDir = resolvePath(resultsDir);
|
|
396
|
+
if (!savePath.startsWith(resolvedResultsDir + '/') && savePath !== resolvedResultsDir) {
|
|
397
|
+
output.writeln(output.error(`Save path must be within ${resultsDir}`));
|
|
398
|
+
return { success: false, message: 'Invalid save path' };
|
|
399
|
+
}
|
|
388
400
|
const saveTmp2 = savePath + '.tmp';
|
|
389
401
|
writeFileSync(saveTmp2, JSON.stringify({
|
|
390
402
|
timestamp: new Date().toISOString(),
|
|
@@ -416,14 +428,30 @@ const regressionCommand = {
|
|
|
416
428
|
{ command: 'monomind benchmark regression -b agent-spawn -a output.txt --pin-baseline', description: 'Evaluate and pin results as new baseline' },
|
|
417
429
|
],
|
|
418
430
|
action: async (ctx) => {
|
|
419
|
-
const
|
|
431
|
+
const suiteDirRaw = ctx.flags.suite || '.monomind/benchmarks/definitions';
|
|
420
432
|
const benchmarkId = ctx.flags['benchmark-id'];
|
|
421
433
|
const agentOutputFile = ctx.flags['agent-output'];
|
|
422
434
|
const pinBaseline = ctx.flags['pin-baseline'] === true;
|
|
423
435
|
const outputFormat = ctx.flags.output || 'text';
|
|
436
|
+
// Validate benchmarkId to prevent path traversal in baseline file names
|
|
437
|
+
if (benchmarkId !== undefined) {
|
|
438
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(benchmarkId)) {
|
|
439
|
+
output.writeln(output.error('Invalid benchmark-id: must contain only alphanumeric, dash, or underscore characters (max 128).'));
|
|
440
|
+
return { success: false, message: 'Invalid benchmark-id' };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
424
443
|
const runner = new BenchmarkRunner();
|
|
425
444
|
const baselinesDir = join(process.cwd(), '.monomind', 'benchmarks', 'baselines');
|
|
426
|
-
|
|
445
|
+
// Path traversal guard for suiteDir
|
|
446
|
+
const { resolve: resolvePath2 } = await import('node:path');
|
|
447
|
+
const projectRoot = resolvePath2(process.cwd());
|
|
448
|
+
const resolvedSuiteDir = resolvePath2(process.cwd(), suiteDirRaw);
|
|
449
|
+
if (!resolvedSuiteDir.startsWith(projectRoot + '/') && resolvedSuiteDir !== projectRoot) {
|
|
450
|
+
output.writeln(output.error(`Suite directory must be within the project: ${projectRoot}`));
|
|
451
|
+
return { success: false, message: 'Invalid suite directory' };
|
|
452
|
+
}
|
|
453
|
+
const suiteDir = suiteDirRaw;
|
|
454
|
+
const definitions = runner.loadBenchmarks(resolvedSuiteDir);
|
|
427
455
|
if (definitions.length === 0) {
|
|
428
456
|
output.writeln(output.dim(`No benchmark definitions found in ${suiteDir}`));
|
|
429
457
|
output.writeln(output.dim('Create JSON files there to define quality benchmarks.'));
|
|
@@ -446,6 +474,15 @@ const regressionCommand = {
|
|
|
446
474
|
output.writeln(output.error(`Agent output file not found: ${agentOutputFile}`));
|
|
447
475
|
return { success: false, message: 'Agent output file not found' };
|
|
448
476
|
}
|
|
477
|
+
const MAX_AGENT_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
478
|
+
try {
|
|
479
|
+
const agentOutputStat = statSync(agentOutputFile);
|
|
480
|
+
if (agentOutputStat.size > MAX_AGENT_OUTPUT_BYTES) {
|
|
481
|
+
output.writeln(output.error(`Agent output file too large: ${agentOutputFile} (max 10 MB)`));
|
|
482
|
+
return { success: false, message: 'Agent output file too large' };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
catch { /* existsSync already passed; ignore stat failure */ }
|
|
449
486
|
const agentOutput = readFileSync(agentOutputFile, 'utf-8');
|
|
450
487
|
const targetDefs = benchmarkId
|
|
451
488
|
? definitions.filter((d) => d.benchmarkId === benchmarkId)
|
|
@@ -473,8 +510,14 @@ const regressionCommand = {
|
|
|
473
510
|
output.writeln();
|
|
474
511
|
}
|
|
475
512
|
// Baseline comparison
|
|
513
|
+
const MAX_BASELINE_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
476
514
|
const baselinePath = join(baselinesDir, `${benchmarkId ?? 'all'}.json`);
|
|
477
515
|
if (existsSync(baselinePath)) {
|
|
516
|
+
const baselineStat = statSync(baselinePath);
|
|
517
|
+
if (baselineStat.size > MAX_BASELINE_BYTES) {
|
|
518
|
+
output.writeln(output.error(`Baseline file too large (max 5 MB)`));
|
|
519
|
+
return { success: false, message: 'Baseline file too large' };
|
|
520
|
+
}
|
|
478
521
|
const baseline = JSON.parse(readFileSync(baselinePath, 'utf-8'));
|
|
479
522
|
const hasRegression = runner.detectRegression(results, baseline);
|
|
480
523
|
if (hasRegression) {
|
|
@@ -214,13 +214,16 @@ const waitCommand = {
|
|
|
214
214
|
const { client, sessionId } = await ensureConnected(_port);
|
|
215
215
|
const browser = await getBrowser();
|
|
216
216
|
if (ctx.flags.ms) {
|
|
217
|
-
|
|
217
|
+
const rawMs = ctx.flags.ms;
|
|
218
|
+
const waitMs = Number.isFinite(rawMs) ? Math.max(0, Math.min(rawMs, 60_000)) : 0; // cap at 60s
|
|
219
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
218
220
|
output.printSuccess(`Waited ${ctx.flags.ms}ms`);
|
|
219
221
|
return { success: true };
|
|
220
222
|
}
|
|
221
223
|
if (ctx.flags.fn) {
|
|
222
224
|
const expr = ctx.flags.fn;
|
|
223
|
-
const
|
|
225
|
+
const rawTimeout = ctx.flags.timeout ?? 30000;
|
|
226
|
+
const timeout = Number.isFinite(rawTimeout) ? Math.max(100, Math.min(rawTimeout, 300_000)) : 30000; // cap at 5min
|
|
224
227
|
const interval = 200;
|
|
225
228
|
const deadline = Date.now() + timeout;
|
|
226
229
|
while (Date.now() < deadline) {
|
|
@@ -35,7 +35,7 @@ function safeParseJson(content) {
|
|
|
35
35
|
function loadClaimsConfig() {
|
|
36
36
|
const configPaths = getClaimsConfigPaths();
|
|
37
37
|
for (const configPath of configPaths) {
|
|
38
|
-
if (fs.existsSync(configPath)) {
|
|
38
|
+
if (fs.existsSync(configPath) && fs.statSync(configPath).size <= 1024 * 1024) {
|
|
39
39
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
40
40
|
return { config: safeParseJson(content), path: configPath };
|
|
41
41
|
}
|
|
@@ -153,13 +153,19 @@ const checkCommand = {
|
|
|
153
153
|
{ command: 'monomind claims check -c admin:delete -u user123', description: 'Check user permission' },
|
|
154
154
|
],
|
|
155
155
|
action: async (ctx) => {
|
|
156
|
-
const claim = ctx.flags.claim;
|
|
157
|
-
const user = ctx.flags.user || 'current';
|
|
158
|
-
const resource = ctx.flags.resource;
|
|
156
|
+
const claim = (ctx.flags.claim || '').slice(0, 256);
|
|
157
|
+
const user = (ctx.flags.user || 'current').slice(0, 128);
|
|
158
|
+
const resource = (ctx.flags.resource || '').slice(0, 256);
|
|
159
159
|
if (!claim) {
|
|
160
160
|
output.printError('Claim is required');
|
|
161
161
|
return { success: false, exitCode: 1 };
|
|
162
162
|
}
|
|
163
|
+
// Block prototype-polluting user or resource keys.
|
|
164
|
+
const PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
165
|
+
if (PROTO_KEYS.has(user)) {
|
|
166
|
+
output.printError(`Forbidden user key: "${user}"`);
|
|
167
|
+
return { success: false, exitCode: 1 };
|
|
168
|
+
}
|
|
163
169
|
output.writeln();
|
|
164
170
|
output.writeln(output.bold('Claim Check'));
|
|
165
171
|
output.writeln(output.dim('─'.repeat(40)));
|
|
@@ -189,7 +195,7 @@ const checkCommand = {
|
|
|
189
195
|
defaultClaims: ['swarm:create', 'swarm:status', 'agent:spawn', 'agent:list', 'memory:read', 'memory:write', 'task:create'],
|
|
190
196
|
};
|
|
191
197
|
for (const configPath of claimsConfigPaths) {
|
|
192
|
-
if (fs.existsSync(configPath)) {
|
|
198
|
+
if (fs.existsSync(configPath) && fs.statSync(configPath).size <= 1024 * 1024) {
|
|
193
199
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
194
200
|
claimsConfig = { ...claimsConfig, ...safeParseJson(content) };
|
|
195
201
|
policySource = configPath;
|
|
@@ -283,9 +289,9 @@ const grantCommand = {
|
|
|
283
289
|
{ command: 'monomind claims grant -c agent:spawn -r developer', description: 'Grant to role' },
|
|
284
290
|
],
|
|
285
291
|
action: async (ctx) => {
|
|
286
|
-
const claim = ctx.flags.claim;
|
|
287
|
-
const user = ctx.flags.user;
|
|
288
|
-
const role = ctx.flags.role;
|
|
292
|
+
const claim = (ctx.flags.claim || '').slice(0, 256);
|
|
293
|
+
const user = (ctx.flags.user || '').slice(0, 128);
|
|
294
|
+
const role = (ctx.flags.role || '').slice(0, 64);
|
|
289
295
|
if (!claim) {
|
|
290
296
|
output.printError('Claim is required');
|
|
291
297
|
return { success: false, exitCode: 1 };
|
|
@@ -294,6 +300,12 @@ const grantCommand = {
|
|
|
294
300
|
output.printError('Either user or role is required');
|
|
295
301
|
return { success: false, exitCode: 1 };
|
|
296
302
|
}
|
|
303
|
+
// Block prototype-polluting user or role keys.
|
|
304
|
+
const PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
305
|
+
if ((user && PROTO_KEYS.has(user)) || (role && PROTO_KEYS.has(role))) {
|
|
306
|
+
output.printError('Forbidden user or role key');
|
|
307
|
+
return { success: false, exitCode: 1 };
|
|
308
|
+
}
|
|
297
309
|
try {
|
|
298
310
|
const { config, path: configPath } = loadClaimsConfig();
|
|
299
311
|
if (user) {
|
|
@@ -343,9 +355,9 @@ const revokeCommand = {
|
|
|
343
355
|
{ command: 'monomind claims revoke -c admin:* -r guest', description: 'Revoke from role' },
|
|
344
356
|
],
|
|
345
357
|
action: async (ctx) => {
|
|
346
|
-
const claim = ctx.flags.claim;
|
|
347
|
-
const user = ctx.flags.user;
|
|
348
|
-
const role = ctx.flags.role;
|
|
358
|
+
const claim = (ctx.flags.claim || '').slice(0, 256);
|
|
359
|
+
const user = (ctx.flags.user || '').slice(0, 128);
|
|
360
|
+
const role = (ctx.flags.role || '').slice(0, 64);
|
|
349
361
|
if (!claim) {
|
|
350
362
|
output.printError('Claim is required');
|
|
351
363
|
return { success: false, exitCode: 1 };
|
|
@@ -354,6 +366,12 @@ const revokeCommand = {
|
|
|
354
366
|
output.printError('Either user or role is required');
|
|
355
367
|
return { success: false, exitCode: 1 };
|
|
356
368
|
}
|
|
369
|
+
// Block prototype-polluting user or role keys.
|
|
370
|
+
const PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
371
|
+
if ((user && PROTO_KEYS.has(user)) || (role && PROTO_KEYS.has(role))) {
|
|
372
|
+
output.printError('Forbidden user or role key');
|
|
373
|
+
return { success: false, exitCode: 1 };
|
|
374
|
+
}
|
|
357
375
|
try {
|
|
358
376
|
const { config, path: configPath } = loadClaimsConfig();
|
|
359
377
|
let removed = false;
|