mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
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.
54 lines
1.7 KiB
TypeScript
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.')
|
|
})
|
|
})
|