End-to-end opt-in encryption for the .claude-flow/terminals/store.json
store. Terminal command history routinely carries pasted credentials
(API keys, DB passwords), so this is the highest-stakes of the three
high-tier targets.
Mechanics — same shape as Phase 2 (session-tools) so reviewers can
diff the patterns:
src/mcp-tools/terminal-tools.ts
- loadTerminalStore: readFileSync → readFileMaybeEncrypted; the
magic-byte sniff handles legacy plaintext + post-migration
encrypted in one read path
- saveTerminalStore: passes { encrypt: true } unconditionally;
vault env-gate decides whether bytes are actually encrypted
- Dropped the now-unused readFileSync import
Tests (__tests__/terminal-encryption.test.ts, 7 cases):
encryption disabled (legacy plaintext)
- writes plaintext JSON (first byte '{', not 'R')
- round-trips create → list
encryption enabled (RFE1 wire format)
- on-disk blob starts with the RFE1 magic
- plaintext name is NOT present anywhere in the on-disk bytes —
stricter than the equivalent session test because terminal stores
carry pasted credentials and an encryption regression here is a
direct CVE
- round-trips create → list
migration
- legacy plaintext store written before the gate flipped is still
readable AFTER the gate flips on (sniff path)
mixed: same store, plaintext-then-encrypted
- first save plaintext (gate off), second save encrypted (gate on);
saveTerminalStore rewrites the whole file → on-disk becomes
encrypted bytes containing BOTH sessions. List enumerates both.
Verified:
tsc --noEmit clean (0 errors)
Encryption-track suite (vault + fs-secure + session + terminal):
67/67 passing
Full vitest suite: 1924/1924 passing, 46 skipped, 0 failures
(was 1917; +7 from this commit)
Phase 4 wires the seven memory-initializer DB writes — the sql.js
SQLite blob containing memory entries + 384-dim embeddings. Different
shape (whole-Buffer encryption vs JSON.stringify) so it lands as its
own iteration.