total-recall-brain 3.28.0 → 3.28.2

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.
@@ -47,11 +47,31 @@ function canUseMacSandbox(profile) {
47
47
  return macSandboxProbe;
48
48
  }
49
49
 
50
+ /**
51
+ * Strip quoted argument payloads.
52
+ *
53
+ * TR dispatches CLI agents with an argv array (`spawn(binary, args)`), so a
54
+ * quoted argument is ONE argument and a `|` inside it is inert data — there is no
55
+ * shell to interpret it. Prompts routinely embed vault fact bodies, and two facts
56
+ * documenting the legitimate `curl … | bash` install line made every deliberation
57
+ * run fail with a bogus "Piping download tool to shell blocked" security
58
+ * exception. Strip quoted payloads before the shell-syntax checks so prompt text
59
+ * is not mistaken for a pipeline.
60
+ */
61
+ function stripQuotedArgs(line) {
62
+ return line.replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/'(?:\\.|[^'\\])*'/g, "''");
63
+ }
64
+
50
65
  /**
51
66
  * Command Sanitizer / Whitelist Execution Validator
52
67
  * Blocks high-risk actions (destructive deletes, reverse shells, shell pipes)
68
+ *
69
+ * @param {string} commandLine
70
+ * @param {{ argvSpawn?: boolean }} [opts] `argvSpawn: true` means the caller
71
+ * passes the command as an argument vector rather than to a shell, so quoted
72
+ * content cannot act as shell syntax.
53
73
  */
54
- export function validateCommand(commandLine) {
74
+ export function validateCommand(commandLine, { argvSpawn = false } = {}) {
55
75
  if (typeof commandLine !== 'string') return true;
56
76
  const trimmed = commandLine.trim();
57
77
 
@@ -68,16 +88,20 @@ export function validateCommand(commandLine) {
68
88
  }
69
89
  }
70
90
 
91
+ // Shell syntax only exists when a shell runs the line. Under an argv spawn the
92
+ // quoted payload is a single inert argument, so scan the unquoted remainder.
93
+ const scanned = argvSpawn ? stripQuotedArgs(trimmed) : trimmed;
94
+
71
95
  // Piping download tools directly into shell
72
96
  // Split strings to bypass static security grep audit false positives while keeping full safety checks
73
97
  const pipeToShellRegex = new RegExp("(?:cu" + "rl|wget|fetch)\\s+.*\\s*\\|\\s*(?:ba" + "sh|sh|zsh)", "i");
74
- const isPipeToShell = pipeToShellRegex.test(trimmed);
98
+ const isPipeToShell = pipeToShellRegex.test(scanned);
75
99
  if (isPipeToShell) {
76
100
  throw new Error(`Security Exception: Piping download tool to shell blocked: "${commandLine}"`);
77
101
  }
78
102
 
79
103
  // Reverse shells and network exfiltration tools
80
- const isReverseShell = /\b(?:nc|netcat)\s+-/i.test(trimmed) || /bash\s+-i/i.test(trimmed) || /sh\s+-i/i.test(trimmed) || /\/dev\/(?:tcp|udp)\//i.test(trimmed);
104
+ const isReverseShell = /\b(?:nc|netcat)\s+-/i.test(scanned) || /bash\s+-i/i.test(scanned) || /sh\s+-i/i.test(scanned) || /\/dev\/(?:tcp|udp)\//i.test(scanned);
81
105
  if (isReverseShell) {
82
106
  throw new Error(`Security Exception: Potential reverse shell blocked: "${commandLine}"`);
83
107
  }
@@ -302,24 +302,35 @@ export async function maybeEnqueueIntegrationResearch(brainDir, key, meta = {},
302
302
  /**
303
303
  * Retire obsolete key-name scrape queue items (old Automated API Integration Build: KEY topics).
304
304
  */
305
- export function cancelBogusApiIntegrationQueueItems(overrideBrainDir) {
306
- return import('./research-queue.mjs').then(({ loadQueue, updateQueueItem }) => {
307
- const items = loadQueue(overrideBrainDir);
308
- let cancelled = 0;
309
- for (const item of items) {
310
- if (item.status !== 'pending' && item.status !== 'in_progress') continue;
311
- if (!/^Automated API Integration Build:/i.test(String(item.topic || ''))) continue;
312
- updateQueueItem(
313
- item.id,
314
- {
315
- status: 'failed',
316
- notes: `${item.notes || ''}\n\n[auto-cancelled] Obsolete key-name scrape job; re-add the secret to trigger AI provider inference.`,
317
- research_phase: 'cancelled',
318
- },
319
- overrideBrainDir,
320
- );
321
- cancelled += 1;
322
- }
323
- return { cancelled };
324
- });
305
+ export async function cancelBogusApiIntegrationQueueItems(overrideBrainDir) {
306
+ const { loadQueue, updateQueueItem } = await import('./research-queue.mjs');
307
+ const items = loadQueue(overrideBrainDir);
308
+ let cancelled = 0;
309
+ for (const item of items) {
310
+ if (item.status !== 'pending' && item.status !== 'in_progress') continue;
311
+ if (!/^Automated API Integration Build:/i.test(String(item.topic || ''))) continue;
312
+ updateQueueItem(
313
+ item.id,
314
+ {
315
+ status: 'failed',
316
+ notes: `${item.notes || ''}\n\n[auto-cancelled] Obsolete key-name scrape job; re-add the secret to trigger AI provider inference.`,
317
+ research_phase: 'cancelled',
318
+ },
319
+ overrideBrainDir,
320
+ );
321
+ cancelled += 1;
322
+ }
323
+
324
+ // The same fixture pattern also landed in the research AGENDA — a different
325
+ // file with a different owner. Walking only the queue is why the designated
326
+ // remediation never removed the 29 agenda topics it was written for.
327
+ let cancelledAgendaTopics = 0;
328
+ try {
329
+ const { cancelBogusAgendaTopics } = await import('./fact-seeker.mjs');
330
+ ({ cancelled: cancelledAgendaTopics } = cancelBogusAgendaTopics());
331
+ } catch {
332
+ // Best-effort: a missing or unreadable agenda must not block the queue half.
333
+ }
334
+
335
+ return { cancelled, cancelledAgendaTopics };
325
336
  }
@@ -488,6 +488,8 @@ const SKILL_SCAN_REL = [
488
488
  path.join('.cursor', 'skills'),
489
489
  path.join('.codex', 'skills'),
490
490
  path.join('.gemini', 'skills'),
491
+ path.join('.cline', 'skills'),
492
+ path.join('.clinerules', 'skills'),
491
493
  ];
492
494
 
493
495
  /**
@@ -299,6 +299,10 @@
299
299
  "method": "GET",
300
300
  "path": "/api/mesh/access/proposals"
301
301
  },
302
+ {
303
+ "method": "POST",
304
+ "path": "/api/mesh/dispatch"
305
+ },
302
306
  {
303
307
  "method": "GET",
304
308
  "path": "/api/mesh/election/history"
@@ -351,6 +355,18 @@
351
355
  "method": "POST",
352
356
  "path": "/api/mesh/preauthkey"
353
357
  },
358
+ {
359
+ "method": "GET",
360
+ "path": "/api/mesh/presence"
361
+ },
362
+ {
363
+ "method": "GET",
364
+ "path": "/api/mesh/presence/local"
365
+ },
366
+ {
367
+ "method": "POST",
368
+ "path": "/api/mesh/presence/resolve"
369
+ },
354
370
  {
355
371
  "method": "POST",
356
372
  "path": "/api/mesh/register-node"
@@ -423,6 +439,38 @@
423
439
  "method": "GET",
424
440
  "path": "/api/pairing"
425
441
  },
442
+ {
443
+ "method": "GET",
444
+ "path": "/api/plugins"
445
+ },
446
+ {
447
+ "method": "DELETE",
448
+ "path": "/api/plugins/:id"
449
+ },
450
+ {
451
+ "method": "GET",
452
+ "path": "/api/plugins/:id"
453
+ },
454
+ {
455
+ "method": "POST",
456
+ "path": "/api/plugins/:id/rate"
457
+ },
458
+ {
459
+ "method": "GET",
460
+ "path": "/api/plugins/:id/readme"
461
+ },
462
+ {
463
+ "method": "POST",
464
+ "path": "/api/plugins/:id/run"
465
+ },
466
+ {
467
+ "method": "GET",
468
+ "path": "/api/plugins/catalog"
469
+ },
470
+ {
471
+ "method": "POST",
472
+ "path": "/api/plugins/install"
473
+ },
426
474
  {
427
475
  "method": "GET",
428
476
  "path": "/api/proposals"