Files
2026-05-27 23:47:53 +01:00

143 lines
12 KiB
Markdown

# @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.
<!-- begin:auto-docs -->
## Documentation tree
- [error-classifier](./src/core/error-classifier/error-classifier.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [error-classifier](./src/core/error-classifier/error-classifier.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [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) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [keychain-errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [error-codes](./src/error-codes.md) — Centralises the eight error codes emitted by `@suaveplan/macos-keychain` and the metadata table that downstream consume…
<!-- end:auto-docs --> - [error-classifier](./src/core/error-classifier/error-classifier.md) — <!-- begin:auto-docs --> _(no submodules)_- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [keychain-errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [keychain-errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [keychain-errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [keychain-bridge](./src/core/keychain-bridge/keychain-bridge.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_ - [output-parser](./src/core/output-parser/output-parser.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_ - [keychain-errors](./src/errors/keychain-errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ - [macos-keychain.schemas](./src/schemas/macos-keychain.schemas.md) — <!-- begin:auto-docs --> _(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.