# @suaveplan/macos-keychain - macOS Keychain bridge for generic-password credentials via the /usr/bin/security CLI: typed errors, AbortSignal cancellation, OTel instrumentation, and an injectable CommandExecutor for testing ## Summary macOS Keychain bridge for generic-password credentials. Wraps the system `/usr/bin/security` CLI behind a strongly-typed CRUD surface with typed errors, AbortSignal cancellation, OpenTelemetry instrumentation, and an injectable `CommandExecutor` that lets tests run anywhere without touching the real keychain. ## Documentation tree - [error-classifier](./src/core/error-classifier/error-classifier.md) — _(no submodules)_ - [error-classifier](./src/core/error-classifier/error-classifier.md) — _(no submodules)_ - [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_ - [default-executor](./src/core/keychain-bridge/default-executor.md) — Provides the runtime-aware default `CommandExecutor` used by the bridge when the consumer does not inject one. The exec… - [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_ - [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_ - [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_ - [errors](./src/errors/keychain-errors.md) — _(no submodules)_ - [keychain-errors](./src/errors/keychain-errors.md) — _(no submodules)_ - [schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_ - [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_ - [error-codes](./src/error-codes.md) — Centralises the eight error codes emitted by `@suaveplan/macos-keychain` and the metadata table that downstream consume… - [error-classifier](./src/core/error-classifier/error-classifier.md) — _(no submodules)_- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_- [default-executor](./src/core/keychain-bridge/default-executor.md) — Provides the runtime-aware default `CommandExecutor` used by the bridge when the consumer does not inject one. The exec… - [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_- [errors](./src/errors/keychain-errors.md) — _(no submodules)_- [keychain-errors](./src/errors/keychain-errors.md) — _(no submodules)_- [schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_- [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_- [error-codes](./src/error-codes.md) — Centralises the eight error codes emitted by `@suaveplan/macos-keychain` and the metadata table that downstream consume… - [error-classifier](./src/core/error-classifier/error-classifier.md) — _(no submodules)_- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_- [default-executor](./src/core/keychain-bridge/default-executor.md) — Provides the runtime-aware default `CommandExecutor` used by the bridge when the consumer does not inject one. The exec… - [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_- [errors](./src/errors/keychain-errors.md) — _(no submodules)_- [keychain-errors](./src/errors/keychain-errors.md) — _(no submodules)_- [schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_- [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_- [error-codes](./src/error-codes.md) — Centralises the eight error codes emitted by `@suaveplan/macos-keychain` and the metadata table that downstream consume… - [error-classifier](./src/core/error-classifier/error-classifier.md) — _(no submodules)_- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_- [default-executor](./src/core/keychain-bridge/default-executor.md) — Provides the runtime-aware default `CommandExecutor` used by the bridge when the consumer does not inject one. The exec… - [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_- [errors](./src/errors/keychain-errors.md) — _(no submodules)_- [keychain-errors](./src/errors/keychain-errors.md) — _(no submodules)_- [schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_- [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_- [error-codes](./src/error-codes.md) — Centralises the eight error codes emitted by `@suaveplan/macos-keychain` and the metadata table that downstream consume… - [error-classifier](./src/core/error-classifier/error-classifier.md) — _(no submodules)_- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_ - [default-executor](./src/core/keychain-bridge/default-executor.md) — Provides the runtime-aware default `CommandExecutor` used by the bridge when the consumer does not inject one. The exec… - [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_ - [output-parser](./src/core/output-parser/output-parser.md) — _(no submodules)_- [errors](./src/errors/keychain-errors.md) — _(no submodules)_ - [keychain-errors](./src/errors/keychain-errors.md) — _(no submodules)_- [schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_ - [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — _(no submodules)_- [error-codes](./src/error-codes.md) — Centralises the eight error codes emitted by `@suaveplan/macos-keychain` and the metadata table that downstream consume… ## Install ```bash bun add @suaveplan/macos-keychain ``` ## Quick start ```ts import { MacOSKeychainBridge } from "@suaveplan/macos-keychain"; const bridge = new MacOSKeychainBridge({ timeoutMs: 5_000 }); await bridge.addItem({ service: "my-app", account: "alice", password: "hunter2", }); const item = await bridge.findItem("my-app", "alice"); console.log(item.password); // "hunter2" await bridge.deleteItem("my-app", "alice"); ``` ## Security model - Arguments are always passed to the binary as a `string[]` — no shell wrapper, no string interpolation. Even payloads containing shell metacharacters (`a; rm -rf /`, `$(whoami)`, …) are forwarded verbatim. - The default child-process launcher uses `Bun.spawn` on Bun and Node's `child_process.spawn` otherwise; both spawn the binary directly by file path. - The output parser materialises the password bytes into a temporary `Uint8Array`, decodes UTF-8, and zeroes the buffer via `@suaveplan/crypto-utils`'s `zeroize` before the parser returns. The decoded `string` is the caller's responsibility to dispose of. - The `accessGroup` config option is forwarded to the CLI so credentials can be scoped to a specific keychain access group. ## Stub-executor guide for tests ```ts import { MacOSKeychainBridge, type CommandExecutor, } from "@suaveplan/macos-keychain"; const stubRunner: CommandExecutor = async (_cmd, args) => { if (args[0] === "find-generic-password") { return { stdout: "", stderr: 'password: "stored"', exitCode: 0 }; } return { stdout: "", stderr: "", exitCode: 0 }; }; const bridge = new MacOSKeychainBridge({ timeoutMs: 1_000 }, stubRunner); const item = await bridge.findItem("svc", "user"); ``` The bridge's only escape hatch into the OS is the second constructor argument; injecting any plain object literal that satisfies the `CommandExecutor` type covers every branch without `vi.mock`. ## Platform requirements The package builds on every platform but the real CRUD operations only work on macOS where `/usr/bin/security` exists. Off-Darwin, the bridge throws `KEYCHAIN.UNAVAILABLE`. The integration test file (`tests/integration.test.ts`) is platform-guarded and skipped automatically on non-Darwin hosts. ## Modules - [src/types/macos-keychain.types.md](./src/types/macos-keychain.types.md) — public type contracts. - [src/error-codes.md](./src/error-codes.md) — eight stable codes + metadata table. - [src/errors/keychain-errors.md](./src/errors/keychain-errors.md) — named factory helpers. - [src/schemas/macos-keychain.schemas.md](./src/schemas/macos-keychain.schemas.md) — Zod schemas. - [src/core/error-classifier/error-classifier.md](./src/core/error-classifier/error-classifier.md) — exit-code/stderr mapping. - [src/core/output-parser/output-parser.md](./src/core/output-parser/output-parser.md) — `find-generic-password` and `dump-keychain` parsers. - [src/core/keychain-bridge/keychain-bridge.md](./src/core/keychain-bridge/keychain-bridge.md) — the entry-point class. - [src/core/keychain-bridge/default-executor.md](./src/core/keychain-bridge/default-executor.md) — runtime-aware default `CommandExecutor`. ## Benchmarks Run via `bun run bench`. Numbers are reported on the local macOS / Linux host and serve as drift trackers, not absolute SLOs. | Benchmark | Iterations | Notes | |-----------------------------|------------|----------------------------------------------| | `findItem_stub` | 100 000 | Parses static stdout/stderr through the bridge. | | `classifyError_10_patterns` | 1 000 000 | Pure-function classification overhead. | Baseline numbers will be inserted once the first hosted CI runs the bench job; until then the suite exists to detect regressions locally via `bunx mitata`. ## Errors Eight codes — see `src/error-codes.md` for the full metadata table: `KEYCHAIN.NOT_FOUND` · `KEYCHAIN.ITEM_EXISTS` · `KEYCHAIN.ACCESS_FAILED` · `KEYCHAIN.LOCKED` · `KEYCHAIN.UNAVAILABLE` · `KEYCHAIN.PERMISSION_DENIED` · `KEYCHAIN.ITEM_INVALID` · `KEYCHAIN.TIMEOUT`. ## License MIT © Suaveplan Team.