@evomap/evolver 1.89.20 → 1.91.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.
Files changed (69) hide show
  1. package/README.ja-JP.md +1 -1
  2. package/README.ko-KR.md +1 -1
  3. package/README.md +1 -1
  4. package/README.zh-CN.md +1 -1
  5. package/index.js +85 -1
  6. package/package.json +1 -1
  7. package/src/adapters/claudeCode.js +3 -3
  8. package/src/adapters/codex.js +2 -2
  9. package/src/adapters/kiro.js +2 -2
  10. package/src/adapters/opencode.js +2 -2
  11. package/src/evolve/guards.js +1 -1
  12. package/src/evolve/pipeline/collect.js +1 -1
  13. package/src/evolve/pipeline/dispatch.js +1 -1
  14. package/src/evolve/pipeline/enrich.js +1 -1
  15. package/src/evolve/pipeline/hub.js +1 -1
  16. package/src/evolve/pipeline/select.js +1 -1
  17. package/src/evolve/pipeline/signals.js +1 -1
  18. package/src/evolve/utils.js +1 -1
  19. package/src/evolve.js +1 -1
  20. package/src/gep/a2aProtocol.js +1 -1
  21. package/src/gep/antiAbuseTelemetry.js +1 -1
  22. package/src/gep/autoDistillConv.js +1 -1
  23. package/src/gep/autoDistillLlm.js +1 -1
  24. package/src/gep/candidateEval.js +1 -1
  25. package/src/gep/candidates.js +1 -1
  26. package/src/gep/contentHash.js +1 -1
  27. package/src/gep/conversationDistiller.js +1 -1
  28. package/src/gep/conversationSniffer.js +1 -1
  29. package/src/gep/crypto.js +1 -1
  30. package/src/gep/curriculum.js +1 -1
  31. package/src/gep/deviceId.js +1 -1
  32. package/src/gep/envFingerprint.js +1 -1
  33. package/src/gep/epigenetics.js +1 -1
  34. package/src/gep/execBridge.js +1 -1
  35. package/src/gep/explore.js +1 -1
  36. package/src/gep/hash.js +1 -1
  37. package/src/gep/hubFetch.js +1 -1
  38. package/src/gep/hubReview.js +1 -1
  39. package/src/gep/hubSearch.js +1 -1
  40. package/src/gep/hubVerify.js +1 -1
  41. package/src/gep/learningSignals.js +1 -1
  42. package/src/gep/memoryGraph.js +1 -1
  43. package/src/gep/memoryGraphAdapter.js +1 -1
  44. package/src/gep/mutation.js +1 -1
  45. package/src/gep/narrativeMemory.js +1 -1
  46. package/src/gep/openPRRegistry.js +1 -1
  47. package/src/gep/personality.js +1 -1
  48. package/src/gep/policyCheck.js +1 -1
  49. package/src/gep/prompt.js +1 -1
  50. package/src/gep/recallInject.js +1 -1
  51. package/src/gep/recallVerifier.js +1 -1
  52. package/src/gep/reflection.js +1 -1
  53. package/src/gep/savingsCore.js +1 -1
  54. package/src/gep/selector.js +1 -1
  55. package/src/gep/selfPR.js +83 -44
  56. package/src/gep/skillDistiller.js +1 -1
  57. package/src/gep/solidify.js +1 -1
  58. package/src/gep/strategy.js +1 -1
  59. package/src/gep/tokenSavings.js +1 -1
  60. package/src/gep/trajectoryExport.js +1 -1
  61. package/src/gep/workspaceKeychain.js +1 -1
  62. package/src/proxy/extensions/traceControl.js +1 -1
  63. package/src/proxy/inject.js +1 -1
  64. package/src/proxy/mailbox/store.js +14 -2
  65. package/src/proxy/sync/outbound.js +44 -3
  66. package/src/proxy/trace/extractor.js +1 -1
  67. package/src/proxy/trace/usage.js +1 -1
  68. package/src/solo/breaker.js +25 -0
  69. package/src/solo/gitGuard.js +65 -0
package/src/gep/selfPR.js CHANGED
@@ -9,11 +9,39 @@
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
11
  const crypto = require('crypto');
12
- const { execSync } = require('child_process');
12
+ const { execFileSync } = require('child_process');
13
13
  // 10 MB — prevents RangeError on large child process output (e.g. git log/diff
14
14
  // on large repos). See GHSA reports / issue #451.
15
15
  const MAX_EXEC_BUFFER = 10 * 1024 * 1024;
16
16
 
17
+ // SECURITY (Semgrep #285 detect-child-process): every git/gh invocation here
18
+ // goes through argv-form execFileSync (no shell), NEVER string concatenation.
19
+ // PR titles/branches/file paths can carry mutation rationale that is
20
+ // model-generated or hub-fetched (untrusted), and a `git commit -m "..."`
21
+ // shell string would let `$(...)` / backticks in that text execute. Passing
22
+ // each value as a discrete argv element removes the shell entirely, so no
23
+ // metacharacter can break out. Injectable for tests via __test.setExecFile.
24
+ let _execFileImpl = execFileSync;
25
+
26
+ // Run `git <args...>` with no shell. Returns { ok, out } / { ok:false, err }
27
+ // mirroring runGh so callers stay uniform.
28
+ function runGit(args, opts) {
29
+ const o = opts || {};
30
+ try {
31
+ const out = _execFileImpl('git', args, {
32
+ cwd: o.cwd,
33
+ timeout: o.timeoutMs || 10000,
34
+ encoding: 'utf8',
35
+ stdio: ['pipe', 'pipe', 'pipe'],
36
+ maxBuffer: MAX_EXEC_BUFFER,
37
+ env: o.env || process.env,
38
+ });
39
+ return { ok: true, out: String(out || '').trim() };
40
+ } catch (e) {
41
+ return { ok: false, out: '', err: String(e && e.stderr ? e.stderr : e.message || e).slice(0, 500) };
42
+ }
43
+ }
44
+
17
45
  const { getEvolutionDir, getRepoRoot, getEvolverInstallRoot } = require('./paths');
18
46
  const { fullLeakCheck, redactString } = require('./sanitize');
19
47
  const {
@@ -251,11 +279,14 @@ function buildPRTitle(mutation) {
251
279
  return '[Auto-Mutation] ' + rationale;
252
280
  }
253
281
 
282
+ // Run `gh <args...>` with no shell. `args` is an ARGV ARRAY, not a string, so
283
+ // repo names / branch / title / file paths are discrete arguments the shell
284
+ // never sees.
254
285
  function runGh(args, opts) {
255
286
  const timeoutMs = (opts && opts.timeoutMs) || SELF_PR_TIMEOUT_MS;
256
287
  const cwd = (opts && opts.cwd) || getRepoRoot();
257
288
  try {
258
- const result = execSync('gh ' + args, {
289
+ const result = _execFileImpl('gh', args, {
259
290
  cwd: cwd,
260
291
  timeout: timeoutMs,
261
292
  encoding: 'utf8',
@@ -272,21 +303,13 @@ function getGitDiff(changedFiles, repoRoot) {
272
303
  const parts = [];
273
304
  for (const f of changedFiles) {
274
305
  const before = parts.length;
275
- try {
276
- const result = execSync(
277
- 'git diff HEAD -- "' + f + '"',
278
- { cwd: repoRoot, timeout: 10000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: MAX_EXEC_BUFFER }
279
- );
280
- if (result && result.trim()) parts.push(result.trim());
281
- } catch (_) {}
306
+ // `f` is a discrete argv element after `--`, so a path with quotes or
307
+ // shell metacharacters cannot break out of the command.
308
+ const head = runGit(['diff', 'HEAD', '--', f], { cwd: repoRoot });
309
+ if (head.ok && head.out) parts.push(head.out);
282
310
  if (parts.length === before) {
283
- try {
284
- const result = execSync(
285
- 'git diff -- "' + f + '"',
286
- { cwd: repoRoot, timeout: 10000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: MAX_EXEC_BUFFER }
287
- );
288
- if (result && result.trim()) parts.push(result.trim());
289
- } catch (_) {}
311
+ const noHead = runGit(['diff', '--', f], { cwd: repoRoot });
312
+ if (noHead.ok && noHead.out) parts.push(noHead.out);
290
313
  }
291
314
  }
292
315
  return parts.join('\n');
@@ -359,7 +382,7 @@ async function maybeCreatePR({ capsule, event, mutation, gene, blastRadius }) {
359
382
  try {
360
383
  console.log('[SelfPR] Creating PR on ' + repo + ' branch ' + branch + '...');
361
384
 
362
- const forkCheck = runGh('repo view ' + repo + ' --json name', { timeoutMs: 15000 });
385
+ const forkCheck = runGh(['repo', 'view', repo, '--json', 'name'], { timeoutMs: 15000 });
363
386
  if (!forkCheck.ok) {
364
387
  console.warn('[SelfPR] Cannot access repo ' + repo + ': ' + (forkCheck.err || 'unknown'));
365
388
  return { attempted: false, reason: 'repo_access_failed' };
@@ -372,7 +395,7 @@ async function maybeCreatePR({ capsule, event, mutation, gene, blastRadius }) {
372
395
  fs.mkdirSync(tmpDir, { recursive: true });
373
396
 
374
397
  const cloneResult = runGh(
375
- 'repo clone ' + repo + ' "' + tmpDir + '" -- --depth 1',
398
+ ['repo', 'clone', repo, tmpDir, '--', '--depth', '1'],
376
399
  { timeoutMs: 60000 }
377
400
  );
378
401
  if (!cloneResult.ok) {
@@ -380,10 +403,9 @@ async function maybeCreatePR({ capsule, event, mutation, gene, blastRadius }) {
380
403
  return { attempted: false, reason: 'clone_failed' };
381
404
  }
382
405
 
383
- try {
384
- execSync('git checkout -b "' + branch + '"', { cwd: tmpDir, timeout: 10000, maxBuffer: MAX_EXEC_BUFFER });
385
- } catch (e) {
386
- console.warn('[SelfPR] Branch creation failed: ' + (e.message || e));
406
+ const checkoutResult = runGit(['checkout', '-b', branch], { cwd: tmpDir });
407
+ if (!checkoutResult.ok) {
408
+ console.warn('[SelfPR] Branch creation failed: ' + (checkoutResult.err || 'unknown'));
387
409
  return { attempted: false, reason: 'branch_failed' };
388
410
  }
389
411
 
@@ -397,26 +419,38 @@ async function maybeCreatePR({ capsule, event, mutation, gene, blastRadius }) {
397
419
  }
398
420
  }
399
421
 
400
- try {
401
- execSync('git add -A', { cwd: tmpDir, timeout: 10000, maxBuffer: MAX_EXEC_BUFFER });
402
- const statusOut = execSync('git status --porcelain', { cwd: tmpDir, timeout: 10000, encoding: 'utf8', maxBuffer: MAX_EXEC_BUFFER });
403
- if (!statusOut || !statusOut.trim()) {
404
- console.log('[SelfPR] No changes to commit in public repo clone.');
405
- return { attempted: false, reason: 'no_public_diff' };
406
- }
407
- execSync(
408
- 'git commit -m "' + title.replace(/"/g, '\\"') + '"',
409
- { cwd: tmpDir, timeout: 10000, env: Object.assign({}, process.env, { GIT_AUTHOR_NAME: 'evolver-bot', GIT_AUTHOR_EMAIL: 'evolver-bot@evomap.ai', GIT_COMMITTER_NAME: 'evolver-bot', GIT_COMMITTER_EMAIL: 'evolver-bot@evomap.ai' }) }
410
- );
411
- } catch (e) {
412
- console.warn('[SelfPR] Commit failed: ' + (e.message || e));
422
+ const addResult = runGit(['add', '-A'], { cwd: tmpDir });
423
+ if (!addResult.ok) {
424
+ console.warn('[SelfPR] Commit failed: ' + (addResult.err || 'git add failed'));
425
+ return { attempted: false, reason: 'commit_failed' };
426
+ }
427
+ const statusResult = runGit(['status', '--porcelain'], { cwd: tmpDir });
428
+ if (!statusResult.ok) {
429
+ console.warn('[SelfPR] Commit failed: ' + (statusResult.err || 'git status failed'));
430
+ return { attempted: false, reason: 'commit_failed' };
431
+ }
432
+ if (!statusResult.out) {
433
+ console.log('[SelfPR] No changes to commit in public repo clone.');
434
+ return { attempted: false, reason: 'no_public_diff' };
435
+ }
436
+ // title is passed as a discrete argv element, so its content (mutation
437
+ // rationale, possibly model-generated) is never shell-interpreted; no
438
+ // manual quote-escaping needed and `$(...)`/backticks stay literal.
439
+ const commitResult = runGit(['commit', '-m', title], {
440
+ cwd: tmpDir,
441
+ env: Object.assign({}, process.env, {
442
+ GIT_AUTHOR_NAME: 'evolver-bot', GIT_AUTHOR_EMAIL: 'evolver-bot@evomap.ai',
443
+ GIT_COMMITTER_NAME: 'evolver-bot', GIT_COMMITTER_EMAIL: 'evolver-bot@evomap.ai',
444
+ }),
445
+ });
446
+ if (!commitResult.ok) {
447
+ console.warn('[SelfPR] Commit failed: ' + (commitResult.err || 'git commit failed'));
413
448
  return { attempted: false, reason: 'commit_failed' };
414
449
  }
415
450
 
416
- try {
417
- execSync('git push origin "' + branch + '"', { cwd: tmpDir, timeout: 30000 });
418
- } catch (e) {
419
- console.warn('[SelfPR] Push failed: ' + (e.message || e));
451
+ const pushResult = runGit(['push', 'origin', branch], { cwd: tmpDir, timeoutMs: 30000 });
452
+ if (!pushResult.ok) {
453
+ console.warn('[SelfPR] Push failed: ' + (pushResult.err || 'git push failed'));
420
454
  return { attempted: false, reason: 'push_failed' };
421
455
  }
422
456
 
@@ -424,11 +458,8 @@ async function maybeCreatePR({ capsule, event, mutation, gene, blastRadius }) {
424
458
  fs.writeFileSync(bodyFile, body);
425
459
 
426
460
  const prResult = runGh(
427
- 'pr create --repo ' + repo +
428
- ' --head "' + branch + '"' +
429
- ' --title "' + title.replace(/"/g, '\\"') + '"' +
430
- ' --body-file "' + bodyFile + '"' +
431
- ' --label "auto-mutation"',
461
+ ['pr', 'create', '--repo', repo, '--head', branch,
462
+ '--title', title, '--body-file', bodyFile, '--label', 'auto-mutation'],
432
463
  { cwd: tmpDir, timeoutMs: 30000 }
433
464
  );
434
465
 
@@ -466,4 +497,12 @@ module.exports = {
466
497
  _loadObfuscatedFromManifest: loadObfuscatedFromManifest,
467
498
  _resetObfuscatedCache,
468
499
  _setManifestRetryTtlForTests,
500
+ // Argv-form exec surface (Semgrep #285): lets tests capture the exact
501
+ // (file, args) handed to execFileSync and assert no shell string is ever
502
+ // built, so a metacharacter-laden title/branch/path cannot inject.
503
+ __test: {
504
+ setExecFile(fn) { _execFileImpl = fn || execFileSync; },
505
+ runGit,
506
+ runGh,
507
+ },
469
508
  };