claude-flow 3.5.74 → 3.5.76

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.5.74",
3
+ "version": "3.5.76",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -145,62 +145,87 @@ const searchCommand = {
145
145
  const queryResult = await generateEmbedding(query);
146
146
  const queryEmbedding = queryResult.embedding;
147
147
  // Get all entries with embeddings from database
148
- const entries = db.exec(`
149
- SELECT id, key, namespace, content, embedding, embedding_dimensions
150
- FROM memory_entries
151
- WHERE status = 'active'
152
- AND embedding IS NOT NULL
153
- ${namespace !== 'all' ? `AND namespace = '${namespace}'` : ''}
154
- LIMIT 1000
155
- `);
148
+ // Parameterized query to prevent SQL injection (CRIT-01)
149
+ const embeddingSql = namespace !== 'all'
150
+ ? `SELECT id, key, namespace, content, embedding, embedding_dimensions
151
+ FROM memory_entries
152
+ WHERE status = 'active' AND embedding IS NOT NULL AND namespace = ?
153
+ LIMIT 1000`
154
+ : `SELECT id, key, namespace, content, embedding, embedding_dimensions
155
+ FROM memory_entries
156
+ WHERE status = 'active' AND embedding IS NOT NULL
157
+ LIMIT 1000`;
158
+ const embeddingStmt = db.prepare(embeddingSql);
159
+ if (namespace !== 'all') {
160
+ embeddingStmt.bind([namespace]);
161
+ }
162
+ const entryRows = [];
163
+ while (embeddingStmt.step()) {
164
+ entryRows.push(embeddingStmt.get());
165
+ }
166
+ embeddingStmt.free();
156
167
  const results = [];
157
- if (entries[0]?.values) {
158
- for (const row of entries[0].values) {
159
- const [id, key, ns, content, embeddingJson] = row;
160
- if (!embeddingJson)
161
- continue;
162
- try {
163
- const embedding = JSON.parse(embeddingJson);
164
- // Calculate cosine similarity
165
- const similarity = cosineSimilarity(queryEmbedding, embedding);
166
- if (similarity >= threshold) {
167
- results.push({
168
- score: similarity,
169
- id: id.substring(0, 10),
170
- key: key || id.substring(0, 15),
171
- content: (content || '').substring(0, 45) + ((content || '').length > 45 ? '...' : ''),
172
- namespace: ns || 'default'
173
- });
174
- }
175
- }
176
- catch {
177
- // Skip entries with invalid embeddings
168
+ for (const row of entryRows) {
169
+ const [id, key, ns, content, embeddingJson] = row;
170
+ if (!embeddingJson)
171
+ continue;
172
+ try {
173
+ const embedding = JSON.parse(embeddingJson);
174
+ // Calculate cosine similarity
175
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
176
+ if (similarity >= threshold) {
177
+ results.push({
178
+ score: similarity,
179
+ id: id.substring(0, 10),
180
+ key: key || id.substring(0, 15),
181
+ content: (content || '').substring(0, 45) + ((content || '').length > 45 ? '...' : ''),
182
+ namespace: ns || 'default'
183
+ });
178
184
  }
179
185
  }
186
+ catch {
187
+ // Skip entries with invalid embeddings
188
+ }
180
189
  }
181
- // Also search entries without embeddings using keyword match
190
+ // Keyword search fallback with parameterized query (CRIT-01)
182
191
  if (results.length < limit) {
183
- const keywordEntries = db.exec(`
184
- SELECT id, key, namespace, content
185
- FROM memory_entries
186
- WHERE status = 'active'
187
- AND (content LIKE '%${query.replace(/'/g, "''")}%' OR key LIKE '%${query.replace(/'/g, "''")}%')
188
- ${namespace !== 'all' ? `AND namespace = '${namespace}'` : ''}
189
- LIMIT ${limit - results.length}
190
- `);
191
- if (keywordEntries[0]?.values) {
192
- for (const row of keywordEntries[0].values) {
193
- const [id, key, ns, content] = row;
194
- // Avoid duplicates
195
- if (!results.some(r => r.id === id.substring(0, 10))) {
196
- results.push({
197
- score: 0.5, // Keyword match base score
198
- id: id.substring(0, 10),
199
- key: key || id.substring(0, 15),
200
- content: (content || '').substring(0, 45) + ((content || '').length > 45 ? '...' : ''),
201
- namespace: ns || 'default'
202
- });
203
- }
192
+ const likePattern = `%${query}%`;
193
+ const remainingLimit = Math.max(0, limit - results.length);
194
+ const keywordSql = namespace !== 'all'
195
+ ? `SELECT id, key, namespace, content
196
+ FROM memory_entries
197
+ WHERE status = 'active'
198
+ AND (content LIKE ? OR key LIKE ?)
199
+ AND namespace = ?
200
+ LIMIT ?`
201
+ : `SELECT id, key, namespace, content
202
+ FROM memory_entries
203
+ WHERE status = 'active'
204
+ AND (content LIKE ? OR key LIKE ?)
205
+ LIMIT ?`;
206
+ const keywordStmt = db.prepare(keywordSql);
207
+ if (namespace !== 'all') {
208
+ keywordStmt.bind([likePattern, likePattern, namespace, remainingLimit]);
209
+ }
210
+ else {
211
+ keywordStmt.bind([likePattern, likePattern, remainingLimit]);
212
+ }
213
+ const keywordRows = [];
214
+ while (keywordStmt.step()) {
215
+ keywordRows.push(keywordStmt.get());
216
+ }
217
+ keywordStmt.free();
218
+ for (const row of keywordRows) {
219
+ const [id, key, ns, content] = row;
220
+ // Avoid duplicates
221
+ if (!results.some(r => r.id === id.substring(0, 10))) {
222
+ results.push({
223
+ score: 0.5, // Keyword match base score
224
+ id: id.substring(0, 10),
225
+ key: key || id.substring(0, 15),
226
+ content: (content || '').substring(0, 45) + ((content || '').length > 45 ? '...' : ''),
227
+ namespace: ns || 'default'
228
+ });
204
229
  }
205
230
  }
206
231
  }
@@ -208,8 +208,10 @@ async function spawnClaudeCodeInstance(swarmId, swarmName, objective, workers, f
208
208
  claudeArgs.push('--verbose');
209
209
  output.printInfo('Running in non-interactive mode');
210
210
  }
211
- // Add auto-permission flag unless explicitly disabled
212
- const skipPermissions = flags['dangerously-skip-permissions'] !== false && !flags['no-auto-permissions'];
211
+ // HIGH-02: Strict boolean check (=== true) instead of loose truthiness (!== false)
212
+ // to prevent undefined/null from being treated as "skip permissions".
213
+ // Behavior change: only explicit --dangerously-skip-permissions flag triggers skip.
214
+ const skipPermissions = flags['dangerously-skip-permissions'] === true && !flags['no-auto-permissions'];
213
215
  if (skipPermissions) {
214
216
  claudeArgs.push('--dangerously-skip-permissions');
215
217
  if (!isNonInteractive) {
@@ -19,18 +19,6 @@ export { swarmCommand } from './swarm.js';
19
19
  export { memoryCommand } from './memory.js';
20
20
  export { mcpCommand } from './mcp.js';
21
21
  export { hooksCommand } from './hooks.js';
22
- export { daemonCommand } from './daemon.js';
23
- export { doctorCommand } from './doctor.js';
24
- export { embeddingsCommand } from './embeddings.js';
25
- export { neuralCommand } from './neural.js';
26
- export { performanceCommand } from './performance.js';
27
- export { securityCommand } from './security.js';
28
- export { ruvectorCommand } from './ruvector/index.js';
29
- export { hiveMindCommand } from './hive-mind.js';
30
- export { guidanceCommand } from './guidance.js';
31
- export { applianceCommand } from './appliance.js';
32
- export { cleanupCommand } from './cleanup.js';
33
- export { autopilotCommand } from './autopilot.js';
34
22
  export declare function getConfigCommand(): Promise<Command | undefined>;
35
23
  export declare function getMigrateCommand(): Promise<Command | undefined>;
36
24
  export declare function getWorkflowCommand(): Promise<Command | undefined>;
@@ -62,7 +50,8 @@ export declare function getAutopilotCommand(): Promise<Command | undefined>;
62
50
  */
63
51
  export declare const commands: Command[];
64
52
  /**
65
- * Commands organized by category for help display
53
+ * Commands organized by category for help display (synchronous core only).
54
+ * @deprecated Use getCommandsByCategory() for full categorized listing.
66
55
  */
67
56
  export declare const commandsByCategory: {
68
57
  primary: Command[];
@@ -71,6 +60,11 @@ export declare const commandsByCategory: {
71
60
  analysis: Command[];
72
61
  management: Command[];
73
62
  };
63
+ /**
64
+ * Async version that loads all commands by category (PERF-03).
65
+ * Use this for help display and full command listings.
66
+ */
67
+ export declare function getCommandsByCategory(): Promise<Record<string, Command[]>>;
74
68
  /**
75
69
  * Command registry map for quick lookup
76
70
  * Supports both sync (core commands) and async (lazy-loaded) commands
@@ -99,6 +99,8 @@ async function loadCommand(name) {
99
99
  // Synchronous Imports for Core Commands (needed immediately at startup)
100
100
  // These are the most commonly used commands that need instant access
101
101
  // =============================================================================
102
+ // PERF-03: Only import core commands synchronously (~10 most-used).
103
+ // All other commands are lazy-loaded via commandLoaders on demand.
102
104
  import { initCommand } from './init.js';
103
105
  import { startCommand } from './start.js';
104
106
  import { statusCommand } from './status.js';
@@ -109,34 +111,7 @@ import { swarmCommand } from './swarm.js';
109
111
  import { memoryCommand } from './memory.js';
110
112
  import { mcpCommand } from './mcp.js';
111
113
  import { hooksCommand } from './hooks.js';
112
- import { daemonCommand } from './daemon.js';
113
- import { doctorCommand } from './doctor.js';
114
- import { embeddingsCommand } from './embeddings.js';
115
- import { neuralCommand } from './neural.js';
116
- import { performanceCommand } from './performance.js';
117
- import { securityCommand } from './security.js';
118
- import { ruvectorCommand } from './ruvector/index.js';
119
- import { hiveMindCommand } from './hive-mind.js';
120
- // Additional commands for categorized help display
121
- import { configCommand } from './config.js';
122
- import { completionsCommand } from './completions.js';
123
- import { migrateCommand } from './migrate.js';
124
- import { workflowCommand } from './workflow.js';
125
- import { analyzeCommand } from './analyze.js';
126
- import { routeCommand } from './route.js';
127
- import { progressCommand } from './progress.js';
128
- import { providersCommand } from './providers.js';
129
- import { pluginsCommand } from './plugins.js';
130
- import { deploymentCommand } from './deployment.js';
131
- import { claimsCommand } from './claims.js';
132
- import { issuesCommand } from './issues.js';
133
- import updateCommand from './update.js';
134
- import { processCommand } from './process.js';
135
- import { guidanceCommand } from './guidance.js';
136
- import { applianceCommand } from './appliance.js';
137
- import { cleanupCommand } from './cleanup.js';
138
- import { autopilotCommand } from './autopilot.js';
139
- // Pre-populate cache with core commands
114
+ // Pre-populate cache with core commands only
140
115
  loadedCommands.set('init', initCommand);
141
116
  loadedCommands.set('start', startCommand);
142
117
  loadedCommands.set('status', statusCommand);
@@ -147,21 +122,10 @@ loadedCommands.set('swarm', swarmCommand);
147
122
  loadedCommands.set('memory', memoryCommand);
148
123
  loadedCommands.set('mcp', mcpCommand);
149
124
  loadedCommands.set('hooks', hooksCommand);
150
- loadedCommands.set('daemon', daemonCommand);
151
- loadedCommands.set('doctor', doctorCommand);
152
- loadedCommands.set('embeddings', embeddingsCommand);
153
- loadedCommands.set('neural', neuralCommand);
154
- loadedCommands.set('performance', performanceCommand);
155
- loadedCommands.set('security', securityCommand);
156
- loadedCommands.set('ruvector', ruvectorCommand);
157
- loadedCommands.set('hive-mind', hiveMindCommand);
158
- loadedCommands.set('guidance', guidanceCommand);
159
- loadedCommands.set('cleanup', cleanupCommand);
160
- loadedCommands.set('autopilot', autopilotCommand);
161
125
  // =============================================================================
162
126
  // Exports (maintain backwards compatibility)
163
127
  // =============================================================================
164
- // Export synchronously loaded commands
128
+ // Export core commands (synchronous)
165
129
  export { initCommand } from './init.js';
166
130
  export { startCommand } from './start.js';
167
131
  export { statusCommand } from './status.js';
@@ -172,18 +136,6 @@ export { swarmCommand } from './swarm.js';
172
136
  export { memoryCommand } from './memory.js';
173
137
  export { mcpCommand } from './mcp.js';
174
138
  export { hooksCommand } from './hooks.js';
175
- export { daemonCommand } from './daemon.js';
176
- export { doctorCommand } from './doctor.js';
177
- export { embeddingsCommand } from './embeddings.js';
178
- export { neuralCommand } from './neural.js';
179
- export { performanceCommand } from './performance.js';
180
- export { securityCommand } from './security.js';
181
- export { ruvectorCommand } from './ruvector/index.js';
182
- export { hiveMindCommand } from './hive-mind.js';
183
- export { guidanceCommand } from './guidance.js';
184
- export { applianceCommand } from './appliance.js';
185
- export { cleanupCommand } from './cleanup.js';
186
- export { autopilotCommand } from './autopilot.js';
187
139
  // Lazy-loaded command re-exports (for backwards compatibility, but async-only)
188
140
  export async function getConfigCommand() { return loadCommand('config'); }
189
141
  export async function getMigrateCommand() { return loadCommand('migrate'); }
@@ -215,7 +167,7 @@ export async function getAutopilotCommand() { return loadCommand('autopilot'); }
215
167
  * Advanced commands loaded on-demand for faster startup
216
168
  */
217
169
  export const commands = [
218
- // Core commands (synchronously loaded)
170
+ // Core commands (synchronously loaded) — PERF-03
219
171
  initCommand,
220
172
  startCommand,
221
173
  statusCommand,
@@ -226,20 +178,10 @@ export const commands = [
226
178
  memoryCommand,
227
179
  mcpCommand,
228
180
  hooksCommand,
229
- daemonCommand,
230
- doctorCommand,
231
- embeddingsCommand,
232
- neuralCommand,
233
- performanceCommand,
234
- securityCommand,
235
- ruvectorCommand,
236
- hiveMindCommand,
237
- guidanceCommand,
238
- cleanupCommand,
239
- autopilotCommand,
240
181
  ];
241
182
  /**
242
- * Commands organized by category for help display
183
+ * Commands organized by category for help display (synchronous core only).
184
+ * @deprecated Use getCommandsByCategory() for full categorized listing.
243
185
  */
244
186
  export const commandsByCategory = {
245
187
  primary: [
@@ -254,41 +196,48 @@ export const commandsByCategory = {
254
196
  mcpCommand,
255
197
  hooksCommand,
256
198
  ],
257
- advanced: [
258
- neuralCommand,
259
- securityCommand,
260
- performanceCommand,
261
- embeddingsCommand,
262
- hiveMindCommand,
263
- ruvectorCommand,
264
- guidanceCommand,
265
- autopilotCommand,
266
- ],
267
- utility: [
268
- configCommand,
269
- doctorCommand,
270
- daemonCommand,
271
- completionsCommand,
272
- migrateCommand,
273
- workflowCommand,
274
- ],
275
- analysis: [
276
- analyzeCommand,
277
- routeCommand,
278
- progressCommand,
279
- ],
280
- management: [
281
- providersCommand,
282
- pluginsCommand,
283
- deploymentCommand,
284
- claimsCommand,
285
- issuesCommand,
286
- updateCommand,
287
- processCommand,
288
- applianceCommand,
289
- cleanupCommand,
290
- ],
199
+ advanced: [],
200
+ utility: [],
201
+ analysis: [],
202
+ management: [],
291
203
  };
204
+ /**
205
+ * Async version that loads all commands by category (PERF-03).
206
+ * Use this for help display and full command listings.
207
+ */
208
+ export async function getCommandsByCategory() {
209
+ const [daemonCmd, doctorCmd, embeddingsCmd, neuralCmd, performanceCmd, securityCmd, ruvectorCmd, hiveMindCmd, configCmd, completionsCmd, migrateCmd, workflowCmd, analyzeCmd, routeCmd, progressCmd, providersCmd, pluginsCmd, deploymentCmd, claimsCmd, issuesCmd, updateCmd, processCmd, guidanceCmd, applianceCmd, cleanupCmd, autopilotCmd,] = await Promise.all([
210
+ loadCommand('daemon'), loadCommand('doctor'), loadCommand('embeddings'), loadCommand('neural'),
211
+ loadCommand('performance'), loadCommand('security'), loadCommand('ruvector'), loadCommand('hive-mind'),
212
+ loadCommand('config'), loadCommand('completions'), loadCommand('migrate'), loadCommand('workflow'),
213
+ loadCommand('analyze'), loadCommand('route'), loadCommand('progress'), loadCommand('providers'),
214
+ loadCommand('plugins'), loadCommand('deployment'), loadCommand('claims'), loadCommand('issues'),
215
+ loadCommand('update'), loadCommand('process'), loadCommand('guidance'), loadCommand('appliance'),
216
+ loadCommand('cleanup'), loadCommand('autopilot'),
217
+ ]);
218
+ return {
219
+ primary: [
220
+ initCommand, startCommand, statusCommand, agentCommand,
221
+ swarmCommand, memoryCommand, taskCommand, sessionCommand,
222
+ mcpCommand, hooksCommand,
223
+ ],
224
+ advanced: [
225
+ neuralCmd, securityCmd, performanceCmd, embeddingsCmd,
226
+ hiveMindCmd, ruvectorCmd, guidanceCmd, autopilotCmd,
227
+ ].filter(Boolean),
228
+ utility: [
229
+ configCmd, doctorCmd, daemonCmd, completionsCmd,
230
+ migrateCmd, workflowCmd,
231
+ ].filter(Boolean),
232
+ analysis: [
233
+ analyzeCmd, routeCmd, progressCmd,
234
+ ].filter(Boolean),
235
+ management: [
236
+ providersCmd, pluginsCmd, deploymentCmd, claimsCmd,
237
+ issuesCmd, updateCmd, processCmd, applianceCmd, cleanupCmd,
238
+ ].filter(Boolean),
239
+ };
240
+ }
292
241
  /**
293
242
  * Command registry map for quick lookup
294
243
  * Supports both sync (core commands) and async (lazy-loaded) commands
@@ -319,14 +319,22 @@ const initAction = async (ctx) => {
319
319
  if (withEmbeddings) {
320
320
  output.writeln();
321
321
  output.printInfo('Initializing ONNX embedding subsystem...');
322
- const { execSync } = await import('child_process');
322
+ const { execFileSync: execFileInit } = await import('child_process');
323
+ // Validate embeddingModel: must match pattern org/model-name (CRIT-02)
324
+ if (!/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9._-]+$/.test(embeddingModel)) {
325
+ throw new Error(`Invalid embedding model name: ${embeddingModel}`);
326
+ }
323
327
  try {
324
328
  output.writeln(output.dim(` Model: ${embeddingModel}`));
325
329
  output.writeln(output.dim(' Hyperbolic: Enabled (Poincaré ball)'));
326
- execSync(`npx @claude-flow/cli@latest embeddings init --model ${embeddingModel} --no-download --force 2>/dev/null`, {
330
+ execFileInit('npx', [
331
+ '@claude-flow/cli@latest', 'embeddings', 'init',
332
+ '--model', embeddingModel,
333
+ '--no-download', '--force',
334
+ ], {
327
335
  stdio: 'pipe',
328
336
  cwd: ctx.cwd,
329
- timeout: 30000
337
+ timeout: 30000,
330
338
  });
331
339
  output.writeln(output.success(' ✓ Embeddings initialized'));
332
340
  output.writeln(output.dim(' Run "embeddings init --download" to download model'));
@@ -546,12 +554,20 @@ const wizardCommand = {
546
554
  if (enableEmbeddings) {
547
555
  output.writeln();
548
556
  output.printInfo('Initializing ONNX embedding subsystem...');
549
- const { execSync } = await import('child_process');
557
+ const { execFileSync } = await import('child_process');
558
+ // Validate embeddingModel: must match pattern org/model-name (CRIT-02)
559
+ if (!/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9._-]+$/.test(embeddingModel)) {
560
+ throw new Error(`Invalid embedding model name: ${embeddingModel}`);
561
+ }
550
562
  try {
551
- execSync(`npx @claude-flow/cli@latest embeddings init --model ${embeddingModel} --no-download --force 2>/dev/null`, {
563
+ execFileSync('npx', [
564
+ '@claude-flow/cli@latest', 'embeddings', 'init',
565
+ '--model', embeddingModel,
566
+ '--no-download', '--force',
567
+ ], {
552
568
  stdio: 'pipe',
553
569
  cwd: ctx.cwd,
554
- timeout: 30000
570
+ timeout: 30000,
555
571
  });
556
572
  output.writeln(output.success(' ✓ Embeddings configured'));
557
573
  embeddingsInitialized = true;
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { output } from '../../output.js';
6
6
  import { confirm, input } from '../../prompt.js';
7
+ import { validateSchemaName } from './pg-utils.js';
7
8
  /**
8
9
  * Get PostgreSQL connection config from context
9
10
  */
@@ -15,7 +16,7 @@ function getConnectionConfig(ctx) {
15
16
  user: ctx.flags.user || process.env.PGUSER || 'postgres',
16
17
  password: ctx.flags.password || process.env.PGPASSWORD || '',
17
18
  ssl: ctx.flags.ssl || process.env.PGSSLMODE === 'require',
18
- schema: ctx.flags.schema || 'claude_flow',
19
+ schema: validateSchemaName(ctx.flags.schema || 'claude_flow'),
19
20
  };
20
21
  }
21
22
  /**
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { output } from '../../output.js';
6
6
  import { confirm } from '../../prompt.js';
7
+ import { validateSchemaName } from './pg-utils.js';
7
8
  /**
8
9
  * Get PostgreSQL connection config from context
9
10
  */
@@ -15,7 +16,7 @@ function getConnectionConfig(ctx) {
15
16
  user: ctx.flags.user || process.env.PGUSER || 'postgres',
16
17
  password: ctx.flags.password || process.env.PGPASSWORD || '',
17
18
  ssl: ctx.flags.ssl || process.env.PGSSLMODE === 'require',
18
- schema: ctx.flags.schema || 'claude_flow',
19
+ schema: validateSchemaName(ctx.flags.schema || 'claude_flow'),
19
20
  };
20
21
  }
21
22
  /**
@@ -12,10 +12,18 @@
12
12
  import { output } from '../../output.js';
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
+ import { validateTimestamp } from './pg-utils.js';
15
16
  /**
16
17
  * Format a ruvector embedding array for PostgreSQL
18
+ * Validates each element is a finite number to prevent SQL injection via crafted arrays.
17
19
  */
18
20
  function formatEmbedding(embedding, dimensions = 384) {
21
+ // Validate every element is a finite number (prevents SQL injection via crafted JSON)
22
+ for (let i = 0; i < embedding.length; i++) {
23
+ if (typeof embedding[i] !== 'number' || !Number.isFinite(embedding[i])) {
24
+ throw new Error(`Invalid embedding value at index ${i}: expected finite number, got ${typeof embedding[i]}`);
25
+ }
26
+ }
19
27
  // Ensure correct dimensions by padding or truncating
20
28
  const padded = [...embedding];
21
29
  while (padded.length < dimensions) {
@@ -40,12 +48,19 @@ function generateInsertSQL(entry) {
40
48
  const value = typeof entry.value === 'string'
41
49
  ? escapeString(entry.value)
42
50
  : escapeString(JSON.stringify(entry.value));
43
- const namespace = entry.namespace || 'default';
51
+ const namespace = escapeString(entry.namespace || 'default');
44
52
  const metadata = entry.metadata ? escapeString(JSON.stringify(entry.metadata)) : '{}';
45
53
  let embeddingClause = 'NULL';
46
54
  if (entry.embedding && Array.isArray(entry.embedding) && entry.embedding.length > 0) {
47
55
  embeddingClause = formatEmbedding(entry.embedding);
48
56
  }
57
+ // DA-HIGH-3: Validate timestamps to prevent SQL injection via crafted JSON
58
+ const createdAt = entry.created_at
59
+ ? `'${escapeString(validateTimestamp(String(entry.created_at)))}'::timestamptz`
60
+ : 'NOW()';
61
+ const updatedAt = entry.updated_at
62
+ ? `'${escapeString(validateTimestamp(String(entry.updated_at)))}'::timestamptz`
63
+ : 'NOW()';
49
64
  return `INSERT INTO claude_flow.memory_entries (key, value, embedding, namespace, metadata, created_at, updated_at)
50
65
  VALUES (
51
66
  '${key}',
@@ -53,8 +68,8 @@ VALUES (
53
68
  ${embeddingClause},
54
69
  '${namespace}',
55
70
  '${metadata}'::jsonb,
56
- ${entry.created_at ? `'${entry.created_at}'::timestamptz` : 'NOW()'},
57
- ${entry.updated_at ? `'${entry.updated_at}'::timestamptz` : 'NOW()'}
71
+ ${createdAt},
72
+ ${updatedAt}
58
73
  )
59
74
  ON CONFLICT (key, namespace) DO UPDATE SET
60
75
  value = EXCLUDED.value,
@@ -288,12 +303,21 @@ export const importCommand = {
288
303
  output.writeln(output.dim('Command:'));
289
304
  output.writeln(output.dim(` docker exec -i ${containerName} psql -U claude -d claude_flow < ${tempFile}`));
290
305
  output.writeln();
291
- // Execute via child_process
292
- const { execSync } = await import('child_process');
306
+ // Execute via child_process (CRIT-02: use execFileSync to prevent command injection)
307
+ const { execFileSync } = await import('child_process');
308
+ // Validate containerName: alphanumeric, hyphens, underscores, dots only
309
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(containerName)) {
310
+ throw new Error(`Invalid container name: ${containerName}`);
311
+ }
293
312
  try {
294
- const result = execSync(`docker exec -i ${containerName} psql -U claude -d claude_flow < ${tempFile}`, {
313
+ const sqlContent = fs.readFileSync(tempFile, 'utf-8');
314
+ const result = execFileSync('docker', [
315
+ 'exec', '-i', containerName,
316
+ 'psql', '-U', 'claude', '-d', 'claude_flow',
317
+ ], {
295
318
  encoding: 'utf-8',
296
319
  timeout: 60000,
320
+ input: sqlContent,
297
321
  });
298
322
  if (verbose) {
299
323
  output.writeln(output.dim(result));
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { output } from '../../output.js';
6
6
  import { confirm, input } from '../../prompt.js';
7
+ import { validateSchemaName } from './pg-utils.js';
7
8
  /**
8
9
  * Get PostgreSQL connection config from context flags and environment
9
10
  */
@@ -15,7 +16,7 @@ function getConnectionConfig(ctx) {
15
16
  user: ctx.flags.user || process.env.PGUSER || 'postgres',
16
17
  password: ctx.flags.password || process.env.PGPASSWORD || '',
17
18
  ssl: ctx.flags.ssl || process.env.PGSSLMODE === 'require',
18
- schema: ctx.flags.schema || 'claude_flow',
19
+ schema: validateSchemaName(ctx.flags.schema || 'claude_flow'),
19
20
  };
20
21
  }
21
22
  /**
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { output } from '../../output.js';
6
6
  import { confirm } from '../../prompt.js';
7
+ import { validateSchemaName } from './pg-utils.js';
7
8
  /**
8
9
  * Available migrations
9
10
  */
@@ -141,7 +142,7 @@ function getConnectionConfig(ctx) {
141
142
  user: ctx.flags.user || process.env.PGUSER || 'postgres',
142
143
  password: ctx.flags.password || process.env.PGPASSWORD || '',
143
144
  ssl: ctx.flags.ssl || process.env.PGSSLMODE === 'require',
144
- schema: ctx.flags.schema || 'claude_flow',
145
+ schema: validateSchemaName(ctx.flags.schema || 'claude_flow'),
145
146
  };
146
147
  }
147
148
  /**
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { output } from '../../output.js';
6
6
  import { confirm } from '../../prompt.js';
7
+ import { validateSchemaName } from './pg-utils.js';
7
8
  /**
8
9
  * Get PostgreSQL connection config from context
9
10
  */
@@ -15,7 +16,7 @@ function getConnectionConfig(ctx) {
15
16
  user: ctx.flags.user || process.env.PGUSER || 'postgres',
16
17
  password: ctx.flags.password || process.env.PGPASSWORD || '',
17
18
  ssl: ctx.flags.ssl || process.env.PGSSLMODE === 'require',
18
- schema: ctx.flags.schema || 'claude_flow',
19
+ schema: validateSchemaName(ctx.flags.schema || 'claude_flow'),
19
20
  };
20
21
  }
21
22
  /**
@@ -0,0 +1,14 @@
1
+ /**
2
+ * PostgreSQL security utilities for RuVector commands.
3
+ * Prevents SQL injection via identifier interpolation.
4
+ *
5
+ * @module v3/cli/commands/ruvector/pg-utils
6
+ */
7
+ /**
8
+ * Validate a PostgreSQL schema name.
9
+ * Throws if the name contains characters that could enable SQL injection.
10
+ * Safe names are returned as-is (no quoting needed since they match the identifier pattern).
11
+ */
12
+ export declare function validateSchemaName(schema: string): string;
13
+ export declare function validateTimestamp(value: string): string;
14
+ //# sourceMappingURL=pg-utils.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * PostgreSQL security utilities for RuVector commands.
3
+ * Prevents SQL injection via identifier interpolation.
4
+ *
5
+ * @module v3/cli/commands/ruvector/pg-utils
6
+ */
7
+ /**
8
+ * Valid PostgreSQL identifier pattern.
9
+ * Allows only ASCII letters, digits, and underscores.
10
+ * Must start with a letter or underscore.
11
+ */
12
+ const VALID_PG_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
13
+ /**
14
+ * Validate a PostgreSQL schema name.
15
+ * Throws if the name contains characters that could enable SQL injection.
16
+ * Safe names are returned as-is (no quoting needed since they match the identifier pattern).
17
+ */
18
+ export function validateSchemaName(schema) {
19
+ if (!schema || schema.length === 0) {
20
+ throw new Error('Schema name must not be empty');
21
+ }
22
+ if (schema.length > 63) {
23
+ throw new Error(`Schema name too long (${schema.length} chars, max 63): "${schema}"`);
24
+ }
25
+ if (!VALID_PG_IDENTIFIER.test(schema)) {
26
+ throw new Error(`Invalid schema name: "${schema}". Must contain only letters, digits, and underscores, and start with a letter or underscore.`);
27
+ }
28
+ return schema;
29
+ }
30
+ /**
31
+ * Validate a PostgreSQL timestamp string.
32
+ * Only allows ISO 8601 format to prevent SQL injection via timestamp fields.
33
+ */
34
+ const VALID_TIMESTAMP = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/;
35
+ export function validateTimestamp(value) {
36
+ if (!VALID_TIMESTAMP.test(value)) {
37
+ throw new Error(`Invalid timestamp format: "${value}". Expected ISO 8601.`);
38
+ }
39
+ return value;
40
+ }
41
+ //# sourceMappingURL=pg-utils.js.map
@@ -3,6 +3,7 @@
3
3
  * Check connection and schema status
4
4
  */
5
5
  import { output } from '../../output.js';
6
+ import { validateSchemaName } from './pg-utils.js';
6
7
  /**
7
8
  * Get PostgreSQL connection config from context
8
9
  */
@@ -14,7 +15,7 @@ function getConnectionConfig(ctx) {
14
15
  user: ctx.flags.user || process.env.PGUSER || 'postgres',
15
16
  password: ctx.flags.password || process.env.PGPASSWORD || '',
16
17
  ssl: ctx.flags.ssl || process.env.PGSSLMODE === 'require',
17
- schema: ctx.flags.schema || 'claude_flow',
18
+ schema: validateSchemaName(ctx.flags.schema || 'claude_flow'),
18
19
  };
19
20
  }
20
21
  /**
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'url';
9
9
  import { dirname, join } from 'path';
10
10
  import { commandParser } from './parser.js';
11
11
  import { output } from './output.js';
12
- import { commands, commandsByCategory, getCommand, getCommandAsync, getCommandNames, hasCommand } from './commands/index.js';
12
+ import { commands, getCommandsByCategory, getCommand, getCommandAsync, getCommandNames, hasCommand } from './commands/index.js';
13
13
  import { suggestCommand } from './suggest.js';
14
14
  import { runStartupUpdateCheck } from './update/index.js';
15
15
  // Read version from package.json at runtime
@@ -109,7 +109,7 @@ export class CLI {
109
109
  process.exit(1);
110
110
  }
111
111
  else {
112
- this.showHelp();
112
+ await this.showHelp();
113
113
  }
114
114
  return;
115
115
  }
@@ -219,7 +219,7 @@ export class CLI {
219
219
  /**
220
220
  * Show main help
221
221
  */
222
- showHelp() {
222
+ async showHelp() {
223
223
  this.output.writeln();
224
224
  this.output.writeln(this.output.bold(`${this.name} v${this.version}`));
225
225
  this.output.writeln(this.output.dim(this.description));
@@ -227,9 +227,11 @@ export class CLI {
227
227
  this.output.writeln(this.output.bold('USAGE:'));
228
228
  this.output.writeln(` ${this.name} <command> [subcommand] [options]`);
229
229
  this.output.writeln();
230
+ // PERF-03: Load all commands by category (lazy-loaded on demand)
231
+ const categories = await getCommandsByCategory();
230
232
  // Primary Commands
231
233
  this.output.writeln(this.output.bold('PRIMARY COMMANDS:'));
232
- for (const cmd of commandsByCategory.primary) {
234
+ for (const cmd of categories.primary) {
233
235
  if (cmd.hidden)
234
236
  continue;
235
237
  const name = cmd.name.padEnd(12);
@@ -237,41 +239,49 @@ export class CLI {
237
239
  }
238
240
  this.output.writeln();
239
241
  // Advanced Commands
240
- this.output.writeln(this.output.bold('ADVANCED COMMANDS:'));
241
- for (const cmd of commandsByCategory.advanced) {
242
- if (cmd.hidden)
243
- continue;
244
- const name = cmd.name.padEnd(12);
245
- this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
242
+ if (categories.advanced.length > 0) {
243
+ this.output.writeln(this.output.bold('ADVANCED COMMANDS:'));
244
+ for (const cmd of categories.advanced) {
245
+ if (cmd.hidden)
246
+ continue;
247
+ const name = cmd.name.padEnd(12);
248
+ this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
249
+ }
250
+ this.output.writeln();
246
251
  }
247
- this.output.writeln();
248
252
  // Utility Commands
249
- this.output.writeln(this.output.bold('UTILITY COMMANDS:'));
250
- for (const cmd of commandsByCategory.utility) {
251
- if (cmd.hidden)
252
- continue;
253
- const name = cmd.name.padEnd(12);
254
- this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
253
+ if (categories.utility.length > 0) {
254
+ this.output.writeln(this.output.bold('UTILITY COMMANDS:'));
255
+ for (const cmd of categories.utility) {
256
+ if (cmd.hidden)
257
+ continue;
258
+ const name = cmd.name.padEnd(12);
259
+ this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
260
+ }
261
+ this.output.writeln();
255
262
  }
256
- this.output.writeln();
257
263
  // Analysis Commands
258
- this.output.writeln(this.output.bold('ANALYSIS COMMANDS:'));
259
- for (const cmd of commandsByCategory.analysis) {
260
- if (cmd.hidden)
261
- continue;
262
- const name = cmd.name.padEnd(12);
263
- this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
264
+ if (categories.analysis.length > 0) {
265
+ this.output.writeln(this.output.bold('ANALYSIS COMMANDS:'));
266
+ for (const cmd of categories.analysis) {
267
+ if (cmd.hidden)
268
+ continue;
269
+ const name = cmd.name.padEnd(12);
270
+ this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
271
+ }
272
+ this.output.writeln();
264
273
  }
265
- this.output.writeln();
266
274
  // Management Commands
267
- this.output.writeln(this.output.bold('MANAGEMENT COMMANDS:'));
268
- for (const cmd of commandsByCategory.management) {
269
- if (cmd.hidden)
270
- continue;
271
- const name = cmd.name.padEnd(12);
272
- this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
275
+ if (categories.management.length > 0) {
276
+ this.output.writeln(this.output.bold('MANAGEMENT COMMANDS:'));
277
+ for (const cmd of categories.management) {
278
+ if (cmd.hidden)
279
+ continue;
280
+ const name = cmd.name.padEnd(12);
281
+ this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
282
+ }
283
+ this.output.writeln();
273
284
  }
274
- this.output.writeln();
275
285
  this.output.writeln(this.output.bold('GLOBAL OPTIONS:'));
276
286
  for (const opt of this.parser.getGlobalOptions()) {
277
287
  const flags = opt.short ? `-${opt.short}, --${opt.name}` : ` --${opt.name}`;
@@ -570,12 +570,30 @@ export class MCPServerManager extends EventEmitter {
570
570
  return false;
571
571
  }
572
572
  // Verify it's actually a node process (guards against PID reuse)
573
+ // DA-CRIT-3: Use execFileSync to prevent command injection via PID values
573
574
  try {
574
- const { execSync } = require('child_process');
575
- const cmdline = execSync(`cat /proc/${pid}/cmdline 2>/dev/null || ps -p ${pid} -o comm= 2>/dev/null`, {
576
- encoding: 'utf8',
577
- timeout: 1000,
578
- }).trim();
575
+ const { execFileSync } = require('child_process');
576
+ const safePid = String(Math.floor(Math.abs(pid)));
577
+ let cmdline = '';
578
+ try {
579
+ // Try /proc on Linux
580
+ const { readFileSync } = require('fs');
581
+ cmdline = readFileSync(`/proc/${safePid}/cmdline`, 'utf8');
582
+ }
583
+ catch {
584
+ // Fall back to ps on macOS/other
585
+ try {
586
+ cmdline = execFileSync('ps', ['-p', safePid, '-o', 'comm='], {
587
+ encoding: 'utf8',
588
+ timeout: 1000,
589
+ }).trim();
590
+ }
591
+ catch {
592
+ // ps failed — fall through
593
+ }
594
+ }
595
+ if (!cmdline)
596
+ return true; // Can't inspect, fall back to kill check
579
597
  // Must be a node process to be our MCP server
580
598
  return cmdline.includes('node') || cmdline.includes('claude-flow') || cmdline.includes('npx');
581
599
  }
@@ -238,6 +238,8 @@ export class PluginManager {
238
238
  if (!plugin) {
239
239
  return { success: false, error: `Plugin ${packageName} is not installed` };
240
240
  }
241
+ // HIGH-04: Warn about unsandboxed plugin execution
242
+ console.warn(`[SECURITY] Plugin loaded without sandboxing: ${packageName}. Plugins run with full process access.`);
241
243
  plugin.enabled = true;
242
244
  await this.saveManifest();
243
245
  return { success: true };
@@ -394,7 +394,10 @@ export class LoRAAdapter {
394
394
  if (pipeline) {
395
395
  try {
396
396
  pipeline.saveCheckpoint(path);
397
- return true;
397
+ // Verify ruvllm actually wrote the file (some versions are no-op)
398
+ const fs = await import('fs');
399
+ if (fs.existsSync(path))
400
+ return true;
398
401
  }
399
402
  catch { /* fall through to JS fallback */ }
400
403
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.5.74",
3
+ "version": "3.5.76",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",