Files
orca/src/shared/secret-store.test.ts
Neil 838f5bfb75 fix(secrets): tell Linux users when their secrets are only obfuscated (#16033)
On Linux with no keyring, Electron falls back to the `basic_text` backend, which
"encrypts" with a hardcoded password. `isEncryptionAvailable()` returns true for
it, so Orca reported those secrets as sealed. They are not.

The obvious fix — returning false for basic_text — is wrong and would have been a
credential regression: `decryptWithStatus()` skips decryption entirely when
encryption is unavailable, so every already-stored secret would read back empty.
Sealing genuinely works on basic_text and must keep working.

So capability and trust are now separate questions. `isEncryptionAvailable()`
still answers "can this host seal and unseal", and `describeProtectionGap()`
(renamed from `describeUnavailable`) answers "is my data actually protected",
covering both no-sealing and weak-sealing.

That method had no production caller — the port documented a promise nothing
kept. `reportSecretProtectionGap()` now reads it at startup. A user-visible
surface is follow-up; this at least stops the silence.

Adds a bootstrap wiring guard over all nine host port installs. The no-op
defaults are correct for a renderer-less host and silently wrong for the desktop,
and a dropped or reordered install fails no existing test. Verified in both
directions: it fails when an install is removed, and when one moves after the
runtime is constructed.
2026-08-22 22:30:11 -07:00

54 lines
1.7 KiB
TypeScript

import { describe, expect, it, beforeEach } from 'vitest'
import {
getSecretStore,
hasSecretStore,
_resetSecretStoreForTests,
setSecretStore,
type SecretStore
} from './secret-store'
function fakeStore(overrides: Partial<SecretStore> = {}): SecretStore {
return {
isEncryptionAvailable: () => true,
encryptString: (plainText) => Buffer.from(`sealed:${plainText}`),
decryptString: (cipher) => cipher.toString().slice('sealed:'.length),
describeProtectionGap: () => null,
...overrides
}
}
describe('SecretStore registry', () => {
beforeEach(() => {
_resetSecretStoreForTests()
})
it('throws until a store is installed, rather than defaulting to one that cannot seal', () => {
expect(hasSecretStore()).toBe(false)
expect(() => getSecretStore()).toThrow(/SecretStore not initialized/)
})
it('returns the installed store', () => {
const store = fakeStore()
setSecretStore(store)
expect(hasSecretStore()).toBe(true)
expect(getSecretStore()).toBe(store)
expect(getSecretStore().encryptString('token').toString()).toBe('sealed:token')
})
it('lets a later install replace an earlier one, so a test fake wins over the global default', () => {
setSecretStore(fakeStore())
setSecretStore(fakeStore({ isEncryptionAvailable: () => false }))
expect(getSecretStore().isEncryptionAvailable()).toBe(false)
})
it('carries a reason when sealing is unavailable, so the degradation can be surfaced', () => {
setSecretStore(
fakeStore({
isEncryptionAvailable: () => false,
describeProtectionGap: () => 'The OS keyring is unavailable.'
})
)
expect(getSecretStore().describeProtectionGap()).toBe('The OS keyring is unavailable.')
})
})