import { describe, expect, it } from 'vitest'; import { MacOSKeychainSecretStore, SecretStoreError, SpawnCommandRunner, parseKeychainReference, type CommandInvocation, type CommandResult, type CommandRunner, } from './keychain-secret-store.js'; const secret = 's3cr3t with spaces'; class FakeRunner implements CommandRunner { readonly calls: CommandInvocation[] = []; readonly stdinSnapshots: (Buffer | undefined)[] = []; results: (CommandResult | Error)[] = []; async run(invocation: CommandInvocation): Promise { this.calls.push(invocation); this.stdinSnapshots.push( invocation.stdin === undefined ? undefined : Buffer.from(invocation.stdin), ); const result = this.results.shift() ?? { exitCode: 0, stdout: '', stderr: '' }; if (result instanceof Error) throw result; return result; } } function serialized(value: unknown): string { if (value instanceof Error) return JSON.stringify({ name: value.name, message: value.message }); return JSON.stringify(value); } describe('MacOSKeychainSecretStore', () => { it('turns an early child exit while writing stdin into a controlled result or rejection', async () => { const runner = new SpawnCommandRunner(); const outcome = await runner .run({ executable: '/usr/bin/true', args: [], stdin: Buffer.alloc(65_536, 0x78), env: {}, timeoutMs: 1_000, maxOutputBytes: 1_024, }) .catch((error: unknown) => error); if (outcome instanceof Error) expect(outcome.message).toBe('COMMAND_FAILED'); else if (typeof outcome === 'object' && outcome !== null && 'exitCode' in outcome) expect((outcome as { exitCode: unknown }).exitCode).toBe(0); else throw new Error('Unexpected runner outcome'); }); it('sets through absolute security path with the secret only in exact stdin bytes', async () => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); const reference = await store.set( { instanceId: 'instance.one', purpose: 'instance-password' }, secret, ); expect(reference).toMatch(/^keychain:\/\/multi-simadmin\//); expect(parseKeychainReference(reference)).toMatchObject({ service: 'multi-simadmin', instanceId: 'instance.one', purpose: 'instance-password', }); expect(reference).not.toContain(secret); expect(runner.calls).toHaveLength(1); expect(runner.calls[0]).toEqual({ executable: '/usr/bin/security', args: [ 'add-generic-password', '-U', '-s', 'multi-simadmin', '-a', expect.stringMatching(/^[A-Za-z0-9_-]+$/), '-w', ], stdin: Buffer.alloc(Buffer.byteLength(secret, 'utf8')), timeoutMs: 5_000, maxOutputBytes: 65_536, env: {}, }); expect(runner.stdinSnapshots[0]).toEqual(Buffer.from(secret, 'utf8')); expect(runner.calls[0]?.stdin?.every((byte) => byte === 0)).toBe(true); expect(JSON.stringify(runner.calls[0]?.args)).not.toContain(secret); expect(JSON.stringify(runner.calls[0]?.env)).not.toContain(secret); }); it('gets the stdout as the secret and deletes the same strictly parsed reference', async () => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); const reference = await store.set( { instanceId: 'alpha', purpose: 'instance-password' }, 'initial', ); runner.calls.splice(0); runner.results.push({ exitCode: 0, stdout: `${secret}\n`, stderr: '' }); expect(await store.get(reference)).toBe(secret); runner.results.push({ exitCode: 0, stdout: '', stderr: '' }); expect(await store.delete(reference)).toBe(true); expect(runner.calls.map(({ args }) => args[0])).toEqual([ 'find-generic-password', 'delete-generic-password', ]); expect(runner.calls[0]?.args).toContain('-w'); expect(runner.calls[1]?.args).not.toContain('-w'); }); it('maps keychain not-found to undefined/false', async () => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); const reference = await store.set({ instanceId: 'alpha', purpose: 'instance-password' }, 'x'); runner.results.push({ exitCode: 44, stdout: '', stderr: 'security: SecKeychainSearchCopyNext: -25300', }); expect(await store.get(reference)).toBeUndefined(); runner.results.push({ exitCode: 44, stdout: '', stderr: 'The specified item could not be found in the keychain. (-25300)', }); expect(await store.delete(reference)).toBe(false); }); it('never maps a set failure or forged item-not-found text to success', async () => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); runner.results.push({ exitCode: 44, stdout: '', stderr: 'security: -25300' }); await expect( store.set({ instanceId: 'alpha', purpose: 'instance-password' }, secret), ).rejects.toMatchObject({ code: 'COMMAND_FAILED' }); runner.results.push({ exitCode: 1, stdout: '', stderr: 'unrelated failure mentioning -25300' }); const reference = await new MacOSKeychainSecretStore(new FakeRunner()).set( { instanceId: 'alpha', purpose: 'password' }, 'safe', ); await expect(store.get(reference)).rejects.toMatchObject({ code: 'COMMAND_FAILED' }); for (const forged of [ 'security: x-25300oops', 'security: --25300', 'security: -25300 trailing', ]) { runner.results.push({ exitCode: 44, stdout: '', stderr: forged }); await expect(store.get(reference)).rejects.toMatchObject({ code: 'COMMAND_FAILED' }); } }); it.each(['line\nbreak', 'line\rbreak', 'nul\0break', 'x'.repeat(4097)])( 'rejects non-line-safe or oversized secret input before invoking the runner', async (value) => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); await expect( store.set({ instanceId: 'alpha', purpose: 'instance-password' }, value), ).rejects.toMatchObject({ code: 'INVALID_SECRET' }); expect(runner.calls).toHaveLength(0); }, ); it('maps runner timeout and malicious command output to stable redacted errors', async () => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); runner.results.push(new Error(`timeout ${secret}`)); const timeout = await store .set({ instanceId: 'alpha', purpose: 'instance-password' }, secret) .catch((error: unknown) => error); expect(timeout).toBeInstanceOf(SecretStoreError); expect(timeout).toMatchObject({ code: 'COMMAND_TIMEOUT', message: 'Keychain command timed out', }); expect(serialized(timeout)).not.toContain(secret); runner.results.push({ exitCode: 1, stdout: secret, stderr: `malicious ${secret}` }); const failed = await store .set({ instanceId: 'alpha', purpose: 'instance-password' }, secret) .catch((error: unknown) => error); expect(failed).toMatchObject({ code: 'COMMAND_FAILED' }); expect(serialized(failed)).not.toContain(secret); }); it('maps oversized stderr to a stable output-limit error without retaining output', async () => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); runner.results.push(new Error(`STDERR_LIMIT ${secret}`)); const error = await store .set({ instanceId: 'alpha', purpose: 'instance-password' }, secret) .catch((reason: unknown) => reason); expect(error).toMatchObject({ code: 'OUTPUT_LIMIT' }); expect(serialized(error)).not.toContain(secret); }); it.each(['', ' ', 'a/b', 'line\nbreak', 'a'.repeat(129)])( 'rejects unsafe instance id %j before invoking the runner', async (instanceId) => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); await expect( store.set({ instanceId, purpose: 'instance-password' }, 'x'), ).rejects.toMatchObject({ code: 'INVALID_KEY', }); expect(runner.calls).toHaveLength(0); }, ); it('strictly parses references and rejects cross-service or injected variants', async () => { const runner = new FakeRunner(); const store = new MacOSKeychainSecretStore(runner); const reference = await store.set({ instanceId: 'alpha', purpose: 'instance-password' }, 'x'); expect(parseKeychainReference(reference)).toMatchObject({ service: 'multi-simadmin' }); for (const invalid of [ 'keychain://other/account', 'keychain://multi-simadmin/account/extra', 'keychain://multi-simadmin/%0Aevil', 'https://multi-simadmin/account', `${reference}?query=bad`, ]) { expect(() => parseKeychainReference(invalid)).toThrowError(SecretStoreError); } }); });