@evomap/evolver 1.85.3 → 1.86.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/package.json +4 -1
- package/scripts/check-changelog.js +166 -0
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/contentHash.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -1
- package/src/gep/hubFetch.js +1 -1
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/openPRRegistry.js +1 -1
- package/src/gep/paths.js +124 -31
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/workspaceKeychain.js +1 -0
package/src/gep/paths.js
CHANGED
|
@@ -299,32 +299,51 @@ function getEvomapPath(...segments) {
|
|
|
299
299
|
// claim a different workspace. workspace-id replaces that self-report
|
|
300
300
|
// with a secret that only the legitimate workspace's evolver knows
|
|
301
301
|
// (Bugbot PR #108 round-3 Agentic Security Review MEDIUM).
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
302
|
+
//
|
|
303
|
+
// Issue #111 Phase 1: optionally backs the secret with the OS keychain
|
|
304
|
+
// (`@napi-rs/keyring` optional dep) to close the same-uid readability
|
|
305
|
+
// gap. Mode is controlled by `EVOLVER_WORKSPACE_KEYCHAIN` (auto/force/
|
|
306
|
+
// off, default `auto`). FS file is RETAINED on successful keychain
|
|
307
|
+
// migration so bun-compiled binaries (which can't `require()` the
|
|
308
|
+
// addon yet — Phase 2) still see the same id when handing off to a
|
|
309
|
+
// node-CLI session in the same workspace.
|
|
310
|
+
|
|
311
|
+
// Read the FS-backed workspace-id at <workspace>/.evolver/workspace-id.
|
|
312
|
+
// Returns the id on a clean read, null on any error or missing file.
|
|
313
|
+
// Symlink rejection matches the pre-keychain hardening from PR #109.
|
|
314
|
+
function _readWorkspaceIdFromFs(file) {
|
|
315
|
+
const dir = path.dirname(file);
|
|
313
316
|
try {
|
|
314
317
|
const dirStat = fs.lstatSync(dir, { throwIfNoEntry: false });
|
|
315
318
|
if (dirStat && dirStat.isSymbolicLink()) return null;
|
|
316
319
|
const fileStat = fs.lstatSync(file, { throwIfNoEntry: false });
|
|
317
|
-
if (fileStat)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
320
|
+
if (!fileStat) return null;
|
|
321
|
+
if (fileStat.isSymbolicLink() || !fileStat.isFile()) return null;
|
|
322
|
+
const raw = fs.readFileSync(file, 'utf8').trim();
|
|
323
|
+
if (raw && /^[a-f0-9]{32,}$/i.test(raw)) return raw;
|
|
324
|
+
return null;
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Atomically create <workspace>/.evolver/workspace-id with the given id
|
|
331
|
+
// (or generate one if `id` is null). Returns the id that ended up on
|
|
332
|
+
// disk, or null on any unrecoverable error. EEXIST races re-read.
|
|
333
|
+
function _writeWorkspaceIdToFs(file, id) {
|
|
334
|
+
const dir = path.dirname(file);
|
|
325
335
|
try {
|
|
336
|
+
// Refuse to write if `.evolver` is a symlink. mkdirSync({recursive:true})
|
|
337
|
+
// happily traverses an existing symlinked directory and the subsequent
|
|
338
|
+
// open() lands the secret file in the attacker-controlled target —
|
|
339
|
+
// O_NOFOLLOW only guards the FINAL path component, not intermediate
|
|
340
|
+
// directories. The pre-refactor monolithic getWorkspaceId() returned
|
|
341
|
+
// null on a symlinked dir before reaching the write; preserve that
|
|
342
|
+
// here (Bugbot PR #121 round-1 HIGH; original guard PR #109 round-2 HIGH).
|
|
343
|
+
const dirStat = fs.lstatSync(dir, { throwIfNoEntry: false });
|
|
344
|
+
if (dirStat && dirStat.isSymbolicLink()) return null;
|
|
326
345
|
fs.mkdirSync(dir, { recursive: true });
|
|
327
|
-
const
|
|
346
|
+
const payload = id || require('crypto').randomBytes(16).toString('hex');
|
|
328
347
|
// Atomic create-and-fail-if-exists so we never overwrite an
|
|
329
348
|
// attacker-pre-placed file (TOCTOU between lstat and writeFileSync
|
|
330
349
|
// could otherwise race a symlink in). O_NOFOLLOW also refuses to
|
|
@@ -337,32 +356,106 @@ function getWorkspaceId() {
|
|
|
337
356
|
try {
|
|
338
357
|
fd = fs.openSync(file, flags, 0o600);
|
|
339
358
|
} catch (e) {
|
|
340
|
-
// EEXIST means another process beat us to it — re-read with the
|
|
341
|
-
// same symlink guards as above.
|
|
342
359
|
if (e && e.code === 'EEXIST') {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
try {
|
|
346
|
-
const raw = fs.readFileSync(file, 'utf8').trim();
|
|
347
|
-
if (raw && /^[a-f0-9]{32,}$/i.test(raw)) return raw;
|
|
348
|
-
} catch { /* unreadable */ }
|
|
349
|
-
return null;
|
|
360
|
+
// Another process beat us — re-read with the same symlink guards.
|
|
361
|
+
return _readWorkspaceIdFromFs(file);
|
|
350
362
|
}
|
|
351
363
|
// ELOOP / EMLINK from O_NOFOLLOW hitting a symlink — refuse.
|
|
352
364
|
return null;
|
|
353
365
|
}
|
|
354
366
|
try {
|
|
355
|
-
fs.writeSync(fd,
|
|
367
|
+
fs.writeSync(fd, payload + '\n', 0, 'utf8');
|
|
356
368
|
} finally {
|
|
357
369
|
fs.closeSync(fd);
|
|
358
370
|
}
|
|
359
371
|
try { fs.chmodSync(file, 0o600); } catch { /* best-effort */ }
|
|
360
|
-
return
|
|
372
|
+
return payload;
|
|
361
373
|
} catch {
|
|
362
374
|
return null;
|
|
363
375
|
}
|
|
364
376
|
}
|
|
365
377
|
|
|
378
|
+
function getWorkspaceId() {
|
|
379
|
+
if (process.env.EVOLVER_WORKSPACE_ID) return String(process.env.EVOLVER_WORKSPACE_ID);
|
|
380
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
381
|
+
const dir = path.join(workspaceRoot, '.evolver');
|
|
382
|
+
const file = path.join(dir, 'workspace-id');
|
|
383
|
+
|
|
384
|
+
let mode = 'off';
|
|
385
|
+
let keychain = null;
|
|
386
|
+
try {
|
|
387
|
+
keychain = require('./workspaceKeychain');
|
|
388
|
+
mode = keychain.getMode();
|
|
389
|
+
} catch {
|
|
390
|
+
// workspaceKeychain.js missing — degrade silently to FS-only.
|
|
391
|
+
mode = 'off';
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (mode !== 'off' && keychain) {
|
|
395
|
+
const addonAvailable = keychain.loadAddon() !== null;
|
|
396
|
+
if (mode === 'force' && !addonAvailable) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
'EVOLVER_WORKSPACE_KEYCHAIN=force but @napi-rs/keyring is not installed. ' +
|
|
399
|
+
'Install it (`npm i @napi-rs/keyring`) or set EVOLVER_WORKSPACE_KEYCHAIN=auto/off.'
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (addonAvailable) {
|
|
403
|
+
const hit = keychain.readFromKeychain(workspaceRoot);
|
|
404
|
+
if (hit.available && hit.id) return hit.id;
|
|
405
|
+
|
|
406
|
+
// `force` must NEVER fall back to FS read/write — that would
|
|
407
|
+
// silently re-introduce same-uid plaintext exposure of the
|
|
408
|
+
// workspace secret, which is exactly what `force` exists to
|
|
409
|
+
// prevent (Bugbot PR #121 round-2 MEDIUM Agentic Security).
|
|
410
|
+
// Generate a fresh id and write it ONLY to the keychain; if
|
|
411
|
+
// that write fails, throw rather than mirror to FS.
|
|
412
|
+
if (mode === 'force') {
|
|
413
|
+
if (hit.available) {
|
|
414
|
+
// Keychain reachable but empty — mint and write keychain-only.
|
|
415
|
+
const newId = require('crypto').randomBytes(16).toString('hex');
|
|
416
|
+
if (!keychain.writeToKeychain(workspaceRoot, newId)) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
'EVOLVER_WORKSPACE_KEYCHAIN=force: keychain write failed; ' +
|
|
419
|
+
'refusing to fall back to filesystem secret.'
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
return newId;
|
|
423
|
+
}
|
|
424
|
+
// Addon loaded but read claims unavailable (e.g. locked
|
|
425
|
+
// keyring on Linux, no D-Bus session). Refuse rather than
|
|
426
|
+
// silently degrade.
|
|
427
|
+
throw new Error(
|
|
428
|
+
'EVOLVER_WORKSPACE_KEYCHAIN=force: keychain reports unavailable ' +
|
|
429
|
+
'(locked keyring / no session?); refusing to fall back to filesystem.'
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// mode === 'auto', keychain miss — try to migrate an existing
|
|
434
|
+
// FS secret in.
|
|
435
|
+
const fsId = _readWorkspaceIdFromFs(file);
|
|
436
|
+
if (fsId) {
|
|
437
|
+
keychain.writeToKeychain(workspaceRoot, fsId); // best-effort
|
|
438
|
+
return fsId;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// No secret anywhere — generate, write FS atomically, then
|
|
442
|
+
// mirror to keychain. FS write is the source of truth for the
|
|
443
|
+
// value (race-resistant via O_EXCL); keychain is the upgrade.
|
|
444
|
+
const newId = _writeWorkspaceIdToFs(file, null);
|
|
445
|
+
if (!newId) return null;
|
|
446
|
+
keychain.writeToKeychain(workspaceRoot, newId); // best-effort
|
|
447
|
+
return newId;
|
|
448
|
+
}
|
|
449
|
+
// mode === 'auto' && addon unavailable → fall through to FS.
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// FS-only path (mode === 'off' or auto-fallback). Identical to the
|
|
453
|
+
// pre-#111 implementation in observable behavior.
|
|
454
|
+
const existing = _readWorkspaceIdFromFs(file);
|
|
455
|
+
if (existing) return existing;
|
|
456
|
+
return _writeWorkspaceIdToFs(file, null);
|
|
457
|
+
}
|
|
458
|
+
|
|
366
459
|
module.exports = {
|
|
367
460
|
getRepoRoot,
|
|
368
461
|
getEvolverInstallRoot,
|