@evomap/evolver 1.70.0-beta.3 → 1.70.0-beta.5

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.
@@ -1,3 +1,3 @@
1
- {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-25T03:26:52.453Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
2
- {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-25T03:26:54.382Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
3
- {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-25T03:26:56.266Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
1
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:31:51.337Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
2
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:31:53.506Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
3
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-27T05:31:55.280Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
package/index.js CHANGED
@@ -847,6 +847,23 @@ async function main() {
847
847
  const data = await resp.json();
848
848
  const outFlag = args.find(a => typeof a === 'string' && a.startsWith('--out='));
849
849
  const safeId = String(data.skill_id || skillId).replace(/[^a-zA-Z0-9_\-\.]/g, '_');
850
+ // Reject safeId values that would either stay inside cwd instead of
851
+ // descending into skills/, or escape cwd entirely. The sanitizing regex
852
+ // above permits `.`, so `..` / `.` / empty survive it; `path.join('.',
853
+ // 'skills', '..')` collapses to `.` which turns the download directory
854
+ // into the user's working directory and lets Hub-supplied bundled_files
855
+ // overwrite `index.js`, `package.json`, etc. See GHSA-cfcj-hqpf-hccf.
856
+ if (
857
+ safeId === '' ||
858
+ safeId === '.' ||
859
+ safeId === '..' ||
860
+ safeId.includes('/') ||
861
+ safeId.includes('\\') ||
862
+ safeId.includes('\0')
863
+ ) {
864
+ console.error('[fetch] Hub returned an invalid skill_id: ' + JSON.stringify(safeId));
865
+ process.exit(1);
866
+ }
850
867
  let outDir;
851
868
  if (outFlag) {
852
869
  const rawOut = outFlag.slice('--out='.length);
@@ -869,7 +886,16 @@ async function main() {
869
886
  }
870
887
  outDir = resolvedOut;
871
888
  } else {
872
- outDir = path.join('.', 'skills', safeId);
889
+ // Defense in depth: apply the same traversal check to the default
890
+ // branch so any remaining path-smuggling shape in `safeId` is caught.
891
+ const candidate = path.resolve(process.cwd(), 'skills', safeId);
892
+ const skillsRoot = path.resolve(process.cwd(), 'skills');
893
+ const rel = path.relative(skillsRoot, candidate);
894
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
895
+ console.error('[fetch] Hub-provided skill_id escapes skills/ directory: ' + JSON.stringify(safeId));
896
+ process.exit(1);
897
+ }
898
+ outDir = candidate;
873
899
  }
874
900
 
875
901
  if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
@@ -885,12 +911,24 @@ async function main() {
885
911
  '.yml', '.yaml',
886
912
  ]);
887
913
  const MAX_SKILL_FILE_BYTES = 512 * 1024;
914
+ // Even with outDir locked to skills/, a legitimate-looking skill can
915
+ // ship a bundled file named `package.json`, `index.js`, or any other
916
+ // top-level project artifact whose name collides with something the
917
+ // user may later copy back up. Prefix-guard the resolved path so every
918
+ // write stays strictly within the resolved outDir (no trailing `/..`
919
+ // in basename, no absolute path smuggling) and never points at cwd.
920
+ const resolvedOutDir = path.resolve(outDir);
921
+ const resolvedCwd = path.resolve(process.cwd());
888
922
 
889
923
  const bundled = Array.isArray(data.bundled_files) ? data.bundled_files : [];
890
924
  const skippedFiles = [];
891
925
  for (const file of bundled) {
892
926
  if (!file || !file.name || typeof file.content !== 'string') continue;
893
927
  const safeName = path.basename(file.name);
928
+ if (!safeName || safeName === '.' || safeName === '..') {
929
+ skippedFiles.push(String(file.name));
930
+ continue;
931
+ }
894
932
  const ext = path.extname(safeName).toLowerCase();
895
933
  if (!ALLOWED_SKILL_EXTENSIONS.has(ext)) {
896
934
  console.warn('[fetch] Skipped skill file with disallowed extension: ' + safeName);
@@ -902,7 +940,23 @@ async function main() {
902
940
  skippedFiles.push(safeName);
903
941
  continue;
904
942
  }
905
- fs.writeFileSync(path.join(outDir, safeName), file.content, 'utf8');
943
+ const destPath = path.resolve(resolvedOutDir, safeName);
944
+ const relToOut = path.relative(resolvedOutDir, destPath);
945
+ if (relToOut.startsWith('..') || path.isAbsolute(relToOut)) {
946
+ console.warn('[fetch] Skipped bundled file whose resolved path escapes outDir: ' + safeName);
947
+ skippedFiles.push(safeName);
948
+ continue;
949
+ }
950
+ // Never let a bundled write touch the evolver's own cwd -- this is
951
+ // the concrete attack shape from GHSA-cfcj-hqpf-hccf (fetch default
952
+ // branch writing to `./index.js`). outDir should always be under
953
+ // skills/ now, but belt-and-braces keep the guarantee explicit.
954
+ if (path.dirname(destPath) === resolvedCwd) {
955
+ console.warn('[fetch] Skipped bundled file that would land in cwd: ' + safeName);
956
+ skippedFiles.push(safeName);
957
+ continue;
958
+ }
959
+ fs.writeFileSync(destPath, file.content, 'utf8');
906
960
  }
907
961
 
908
962
  console.log('[fetch] Skill downloaded to: ' + outDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.70.0-beta.3",
3
+ "version": "1.70.0-beta.5",
4
4
  "description": "A GEP-powered self-evolution engine for AI agents. Features automated log analysis and Genome Evolution Protocol (GEP) for auditable, reusable evolution assets.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,6 +1,8 @@
1
- // Usage: node scripts/validate-suite.js [test-glob-pattern]
1
+ // Usage: node scripts/validate-suite.js [test-glob-pattern | test-file]
2
2
  // Runs the evolver test suite -- repo root is derived from script location, no shell glob needed.
3
- const { execSync } = require('child_process');
3
+ // Accepts either a directory glob pattern (e.g. `test/*.test.js`) or a concrete test file path.
4
+ // See community PR #514.
5
+ const { execFileSync } = require('child_process');
4
6
  const path = require('path');
5
7
  const fs = require('fs');
6
8
 
@@ -8,10 +10,22 @@ const EVOLVER_REPO_ROOT = path.join(__dirname, '..');
8
10
  const pattern = process.argv[2] || 'test/*.test.js';
9
11
 
10
12
  function expandTestGlob(repoRoot, pat) {
11
- const dir = pat.replace(/\/\*\.test\.js$/, '');
13
+ const fullPattern = path.isAbsolute(pat) ? pat : path.join(repoRoot, pat);
14
+ if (fs.existsSync(fullPattern) && fs.statSync(fullPattern).isFile()) {
15
+ return fullPattern.endsWith('.test.js') ? [fullPattern] : [];
16
+ }
17
+
18
+ const dir = path.dirname(pat);
19
+ const basenamePattern = path.basename(pat);
12
20
  const fullDir = path.isAbsolute(dir) ? dir : path.join(repoRoot, dir);
21
+ if (!fs.existsSync(fullDir) || !fs.statSync(fullDir).isDirectory()) return [];
22
+
23
+ const escaped = basenamePattern
24
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
25
+ .replace(/\*/g, '.*');
26
+ const matcher = new RegExp('^' + escaped + '$');
13
27
  return fs.readdirSync(fullDir)
14
- .filter(f => f.endsWith('.test.js'))
28
+ .filter(f => f.endsWith('.test.js') && matcher.test(f))
15
29
  .map(f => path.join(fullDir, f))
16
30
  .sort();
17
31
  }
@@ -22,7 +36,6 @@ if (files.length === 0) {
22
36
  process.exit(1);
23
37
  }
24
38
 
25
- const cmd = 'node --test ' + files.join(' ');
26
39
  const env = Object.assign({}, process.env, {
27
40
  NODE_ENV: 'test',
28
41
  EVOLVER_REPO_ROOT,
@@ -30,9 +43,11 @@ const env = Object.assign({}, process.env, {
30
43
  });
31
44
  delete env.EVOLVE_BRIDGE;
32
45
  delete env.OPENCLAW_WORKSPACE;
46
+ // Clear NODE_TEST_CONTEXT so nested runs from within node --test work.
47
+ delete env.NODE_TEST_CONTEXT;
33
48
 
34
49
  try {
35
- const output = execSync(cmd, {
50
+ const output = execFileSync(process.execPath, ['--test', ...files], {
36
51
  cwd: EVOLVER_REPO_ROOT,
37
52
  stdio: ['pipe', 'pipe', 'pipe'],
38
53
  timeout: 180000,