/** * Opt-in OS-keychain credential backend. * * Addresses the concern that per-project accounts are otherwise stored as * plaintext V3 JSON on disk. This * module introduces an alternate storage backend that persists the SAME * V3 JSON blob as a secret value in the OS keychain: * * - macOS: Keychain * - Windows: Credential Manager * - Linux: Secret Service / libsecret * * **Default behavior is unchanged.** The backend only activates when * `CODEX_KEYCHAIN=1` is set in the environment. Any other value (unset, * `"0"`, `"false"`, `""`, `"yes"`, ...) leaves the existing JSON path in * full control. This is deliberate: credential storage is the highest-trust * surface in the plugin and every new failure mode is a login regression. * * Data model: * - Service name: `oc-codex-multi-auth` (fixed) * - Account key: `accounts:` where the project key * is the `projectName-sha256hash12` string already produced by * `lib/storage/paths.ts#getProjectStorageKey`. For the global file we * use the literal key `accounts:global`. * - Secret payload: the exact JSON string that would otherwise be written * to disk (UTF-8, pretty-printed to match the file contents; the OS * keychain APIs used here store arbitrary UTF-8 so no base64 encoding * is required — documented here for reviewers). * * Fallback contract: * - Every entry point returns a soft failure (`null` on read, * `{ok: false}` on write) instead of throwing when the native module * cannot load or a keychain call fails. Callers MUST fall back to the * JSON backend on failure; they must never silently lose credentials. * - All errors are logged with `log.warn` / `log.error`. Secret values * never appear in log text — only the service name, account key, and * the native error message. * * Testing: * - The native module is loaded lazily so unit tests can substitute * `_setBackendForTests` to avoid hitting the real OS keychain. * Integration tests that DO want to hit the real keychain can set * `CODEX_KEYCHAIN=1` and let `loadBackend` resolve the module. */ /** * The keychain service identifier this plugin owns. Chosen to match the npm * package name so a human inspecting Keychain Access / Credential Manager * can easily identify which program owns a stored credential. */ export declare const KEYCHAIN_SERVICE_NAME = "oc-codex-multi-auth"; /** * Account key used when the plugin is operating without a per-project * storage path (i.e. the global accounts file). Kept distinct from any * per-project key so the two storage scopes never collide in the OS * keychain's (service, account) index. */ export declare const GLOBAL_KEYCHAIN_ACCOUNT_KEY = "accounts:global"; /** * Reserved account key used exclusively by the availability probe. Kept * double-underscored and service-suffixed so a future refactor that drops * the `accounts:` prefix on real entries (or adds a different prefix) still * cannot collide with this probe (F1 post-merge LOW finding). */ export declare const KEYCHAIN_PROBE_ACCOUNT_KEY = "__probe__@oc-codex-multi-auth"; /** * Minimal abstraction over the native `@napi-rs/keyring` `Entry` API. * Declared as an interface so tests can inject a deterministic in-memory * backend without touching the real OS keychain. */ export interface KeychainBackend { get(service: string, account: string): Promise; set(service: string, account: string, secret: string): Promise; delete(service: string, account: string): Promise; isAvailable(): Promise; } /** * Build the keychain account key for a given project storage key. When the * plugin is running against the global accounts file, pass `null` to use * the reserved global account key. */ export declare function buildKeychainAccountKey(projectStorageKey: string | null): string; /** * Keychain account key for the FLAGGED-account sibling store. Distinct from the * main accounts key so the two blobs never collide. Flagged records carry raw * refresh tokens (needed to restore via verify-flagged), so they must get the * same keychain protection as the main store when opt-in is enabled. */ export declare function buildKeychainFlaggedKey(projectStorageKey: string | null): string; /** * Reads the V3 JSON blob from the OS keychain for the given project storage * key. Returns `null` when: * - the native module is unavailable (not installed, missing prebuilt) * - no entry exists yet * - any keychain error occurs * * The caller MUST treat `null` as "fall back to JSON". Callers must never * interpret `null` as "no credentials" unconditionally — it may mean * "keychain locked / permission denied" and the JSON file still holds the * authoritative copy. */ export declare function readFromKeychain(projectStorageKey: string | null): Promise; export interface KeychainWriteResult { ok: boolean; /** Populated on failure. Never contains secret material. */ error?: string; } /** * Persist the V3 JSON blob to the OS keychain. Returns `{ok:false}` on * failure so callers can fall back to JSON. Never throws. */ export declare function writeToKeychain(projectStorageKey: string | null, jsonBlob: string): Promise; /** * Remove the plugin's keychain entry for the given project key. Returns * true when the entry existed and was deleted, false otherwise (including * "not present"). Never throws. */ export declare function deleteFromKeychain(projectStorageKey: string | null): Promise; /** * Flagged-store variants of the keychain read/write/delete helpers. They use * the dedicated flagged account key so flagged credentials get the same * keychain protection as the main store without colliding with it. */ export declare function readFlaggedFromKeychain(projectStorageKey: string | null): Promise; export declare function writeFlaggedToKeychain(projectStorageKey: string | null, jsonBlob: string): Promise; /** * Probe the backend end-to-end (write + read + delete a throwaway entry) * to confirm the OS keychain is reachable and unlocked. Used by the * `codex-keychain status` tool to give the operator a clear yes/no signal * instead of waiting until the next real save. * * Gated on the opt-in flag (F1 post-merge LOW finding): with * `CODEX_KEYCHAIN` unset the probe is a no-op that returns `false` without * touching the OS keychain. This preserves the "unset -> zero keychain code * path" invariant the feature advertises and avoids the first-run macOS * "allow/always allow" prompt that `entry.setPassword` can otherwise * trigger for users who run `codex-keychain status` without opting in. * * Pass `env` to override the opt-in lookup in tests. */ export declare function keychainIsAvailable(env?: NodeJS.ProcessEnv): Promise; /** * Parse the `CODEX_KEYCHAIN` environment variable. Only the literal string * `"1"` enables the opt-in. Anything else (unset, `"0"`, `"false"`, `""`, * `"yes"`, ...) leaves the JSON backend in full control. This mirrors * `EnvBooleanSchema`'s contract and is documented in README.md. */ export declare function isKeychainOptInEnabled(env?: NodeJS.ProcessEnv): boolean; /** * Test-only hook: inject a deterministic backend (typically an in-memory * Map-backed stub) and mark the cache as resolved so `getBackend` returns * the injected backend without attempting to load the native module. * * Passing `null` clears the cache so a subsequent call re-runs the lazy * loader — useful when a test needs to verify the unavailable-backend * branch. */ export declare function _setBackendForTests(backend: KeychainBackend | null): void; /** Test-only reset to the initial "not yet loaded" state. */ export declare function _resetBackendForTests(): void; //# sourceMappingURL=keychain.d.ts.map