()
| 419 | } |
| 420 | |
| 421 | function getWorkspaceId() { |
| 422 | if (process.env.EVOLVER_WORKSPACE_ID) return String(process.env.EVOLVER_WORKSPACE_ID); |
| 423 | const workspaceRoot = getWorkspaceRoot(); |
| 424 | const dir = path.join(workspaceRoot, '.evolver'); |
| 425 | const file = path.join(dir, 'workspace-id'); |
| 426 | |
| 427 | let mode = 'off'; |
| 428 | let keychain = null; |
| 429 | try { |
| 430 | keychain = require('./workspaceKeychain'); |
| 431 | mode = keychain.getMode(); |
| 432 | } catch { |
| 433 | // workspaceKeychain.js missing — degrade silently to FS-only. |
| 434 | mode = 'off'; |
| 435 | } |
| 436 | |
| 437 | if (mode !== 'off' && keychain) { |
| 438 | const addonAvailable = keychain.loadAddon() !== null; |
| 439 | if (mode === 'force' && !addonAvailable) { |
| 440 | throw new Error( |
| 441 | 'EVOLVER_WORKSPACE_KEYCHAIN=force but @napi-rs/keyring is not installed. ' + |
| 442 | 'Install it (`npm i @napi-rs/keyring`) or set EVOLVER_WORKSPACE_KEYCHAIN=auto/off.' |
| 443 | ); |
| 444 | } |
| 445 | if (addonAvailable) { |
| 446 | const hit = keychain.readFromKeychain(workspaceRoot); |
| 447 | if (hit.available && hit.id) return hit.id; |
| 448 | |
| 449 | // `force` must NEVER fall back to FS read/write — that would |
| 450 | // silently re-introduce same-uid plaintext exposure of the |
| 451 | // workspace secret, which is exactly what `force` exists to |
| 452 | // prevent (Bugbot PR #121 round-2 MEDIUM Agentic Security). |
| 453 | // Generate a fresh id and write it ONLY to the keychain; if |
| 454 | // that write fails, throw rather than mirror to FS. |
| 455 | if (mode === 'force') { |
| 456 | if (hit.available) { |
| 457 | // Keychain reachable but empty — mint and write keychain-only. |
| 458 | const newId = require('crypto').randomBytes(16).toString('hex'); |
| 459 | if (!keychain.writeToKeychain(workspaceRoot, newId)) { |
| 460 | throw new Error( |
| 461 | 'EVOLVER_WORKSPACE_KEYCHAIN=force: keychain write failed; ' + |
| 462 | 'refusing to fall back to filesystem secret.' |
| 463 | ); |
| 464 | } |
| 465 | return newId; |
| 466 | } |
| 467 | // Addon loaded but read claims unavailable (e.g. locked |
| 468 | // keyring on Linux, no D-Bus session). Refuse rather than |
| 469 | // silently degrade. |
| 470 | throw new Error( |
| 471 | 'EVOLVER_WORKSPACE_KEYCHAIN=force: keychain reports unavailable ' + |
| 472 | '(locked keyring / no session?); refusing to fall back to filesystem.' |
| 473 | ); |
| 474 | } |
| 475 | |
| 476 | // mode === 'auto', keychain miss — try to migrate an existing |
| 477 | // FS secret in. |
| 478 | const fsId = _readWorkspaceIdFromFs(file); |
nothing calls this directly
no test coverage detected