143 lines
12 KiB
Markdown
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.
|
|
|