NexusTerm: an SSH session manager for Linux

Connection tree with interleaved folders and hosts, tabbed terminals
grouped by host, a pinned local shell, TOFU host key verification, and
credentials held by the OS keyring.

Terminal bytes travel on a per-session MessagePort with credit-based flow
control, so one session running `cat` on a large file cannot stall the
others. Transports reconnect on their own with jittered backoff, keeping
the scrollback and the tab while replacing only the connection beneath.

156 tests, including integration tests against a real sshd and a
migration exercised against a database built in the old shape.
This commit is contained in:
ylhan
2026-09-17 03:12:24 +08:00
commit 430d78b9f0
88 changed files with 19793 additions and 0 deletions
+148
View File
@@ -0,0 +1,148 @@
import { afterEach, describe, expect, it } from 'vitest'
import { closeDatabase, openDatabase } from '../src/main/db/database'
import { Vault } from '../src/main/security/vault'
import { SECRET_CONTEXT } from '../src/main/security/secretBox'
import { createTempDb, fakeKeyring, insecureKeyring, type TempDb } from './helpers'
let temp: TempDb | null = null
afterEach(() => {
temp?.cleanup()
temp = null
})
describe('vault', () => {
it('generates a master key on first run', () => {
temp = createTempDb()
const vault = new Vault(temp.db, fakeKeyring())
const status = vault.initialize()
expect(status).toMatchObject({ state: 'unlocked', createdNow: true })
expect(vault.isUnlocked).toBe(true)
})
it('reloads the existing master key on later runs', () => {
temp = createTempDb()
const first = new Vault(temp.db, fakeKeyring())
first.initialize()
const sealed = first.sealSecret('persisted-secret', SECRET_CONTEXT.credential)
// A second vault over the same database must recover the same key, or every
// stored credential becomes undecryptable on restart.
const second = new Vault(temp.db, fakeKeyring())
const status = second.initialize()
expect(status).toMatchObject({ state: 'unlocked', createdNow: false })
expect(second.unsealSecret(sealed, SECRET_CONTEXT.credential)).toBe('persisted-secret')
})
it('stores the master key wrapped, never in the clear', () => {
temp = createTempDb()
const vault = new Vault(temp.db, fakeKeyring())
vault.initialize()
const row = temp.db
.prepare('SELECT ciphertext FROM master_key_meta WHERE id = 1')
.get() as { ciphertext: Buffer }
// The keyring prefix proves wrap() was actually called rather than the raw
// key being written straight to the row.
expect(row.ciphertext.toString('utf8').startsWith('WRAPPED:')).toBe(true)
})
it('refuses to operate when the keyring is not trustworthy', () => {
temp = createTempDb()
const vault = new Vault(temp.db, insecureKeyring())
const status = vault.initialize()
expect(status).toMatchObject({ state: 'unavailable', reason: 'insecure-keyring' })
expect(vault.isUnlocked).toBe(false)
// The hard rule: no silent degradation to weak storage. Storing a password
// under a hardcoded key is worse than refusing, because the user believes
// it is protected.
expect(() => vault.sealSecret('hunter2', SECRET_CONTEXT.credential)).toThrow()
})
it('writes no master key row at all when the keyring is insecure', () => {
temp = createTempDb()
new Vault(temp.db, insecureKeyring()).initialize()
const count = temp.db
.prepare('SELECT COUNT(*) AS n FROM master_key_meta')
.get() as { n: number }
expect(count.n).toBe(0)
})
it('reports a usable failure when the stored key cannot be unwrapped', () => {
temp = createTempDb()
const first = new Vault(temp.db, fakeKeyring())
first.initialize()
// Simulates the keyring changing underneath us — a different daemon, a
// restored backup, a reimaged machine.
const brokenKeyring = { ...fakeKeyring(), unwrap: (): string => {
throw new Error('no keyring entry found')
} }
const second = new Vault(temp.db, brokenKeyring)
const status = second.initialize()
expect(status).toMatchObject({ state: 'unavailable', reason: 'unseal-failed' })
// The message must name the backend that sealed it, so the condition is
// diagnosable rather than an opaque decrypt failure.
if (status.state === 'unavailable') {
expect(status.detail).toContain('test-keyring')
}
})
it('locks, after which secrets can no longer be sealed', () => {
temp = createTempDb()
const vault = new Vault(temp.db, fakeKeyring())
vault.initialize()
vault.lock()
expect(vault.isUnlocked).toBe(false)
expect(() => vault.sealSecret('hunter2', SECRET_CONTEXT.credential)).toThrow(/locked/)
})
it('rejects a master key of the wrong length', () => {
temp = createTempDb()
new Vault(temp.db, fakeKeyring()).initialize()
// A short key would silently weaken AES-256 or make ciphertext
// unrecoverable depending on how the crypto layer reacted.
const shortKeyring = {
...fakeKeyring(),
unwrap: (): string => Buffer.from('too-short').toString('base64'),
}
const vault = new Vault(temp.db, shortKeyring)
const status = vault.initialize()
expect(status).toMatchObject({ state: 'unavailable', reason: 'unseal-failed' })
})
it('survives a restart with a fresh Vault over the same file', () => {
const db = createTempDb()
const path = db.path
const vault = new Vault(db.db, fakeKeyring())
vault.initialize()
const sealed = vault.sealSecret('across-restart', SECRET_CONTEXT.credential)
closeDatabase(db.db)
const reopened = openDatabase(path)
const afterRestart = new Vault(reopened, fakeKeyring())
afterRestart.initialize()
expect(afterRestart.unsealSecret(sealed, SECRET_CONTEXT.credential)).toBe('across-restart')
closeDatabase(reopened)
temp = { ...db, db: reopened }
})
})