oc-codex-multi-auth 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +58 -18
- package/assets/readme-hero.svg +10 -10
- package/config/README.md +1 -1
- package/config/minimal-opencode.json +1 -1
- package/config/opencode-legacy.json +1 -1
- package/config/opencode-modern.json +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +98 -2520
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts/persistence.d.ts +34 -0
- package/dist/lib/accounts/persistence.d.ts.map +1 -0
- package/dist/lib/accounts/persistence.js +153 -0
- package/dist/lib/accounts/persistence.js.map +1 -0
- package/dist/lib/accounts/recovery.d.ts +59 -0
- package/dist/lib/accounts/recovery.d.ts.map +1 -0
- package/dist/lib/accounts/recovery.js +229 -0
- package/dist/lib/accounts/recovery.js.map +1 -0
- package/dist/lib/accounts/rotation.d.ts +40 -0
- package/dist/lib/accounts/rotation.d.ts.map +1 -0
- package/dist/lib/accounts/rotation.js +204 -0
- package/dist/lib/accounts/rotation.js.map +1 -0
- package/dist/lib/accounts/state.d.ts +92 -0
- package/dist/lib/accounts/state.d.ts.map +1 -0
- package/dist/lib/accounts/state.js +366 -0
- package/dist/lib/accounts/state.js.map +1 -0
- package/dist/lib/accounts.d.ts +48 -91
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +121 -699
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auth/auth.d.ts +1 -1
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +5 -2
- package/dist/lib/auth/auth.js.map +1 -1
- package/dist/lib/auth/login-runner.d.ts +32 -0
- package/dist/lib/auth/login-runner.d.ts.map +1 -1
- package/dist/lib/auth/login-runner.js +79 -64
- package/dist/lib/auth/login-runner.js.map +1 -1
- package/dist/lib/auth/server.d.ts.map +1 -1
- package/dist/lib/auth/server.js +3 -8
- package/dist/lib/auth/server.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +53 -2
- package/dist/lib/circuit-breaker.d.ts.map +1 -1
- package/dist/lib/circuit-breaker.js +65 -6
- package/dist/lib/circuit-breaker.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +77 -32
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/error-sentinels.d.ts +20 -0
- package/dist/lib/error-sentinels.d.ts.map +1 -0
- package/dist/lib/error-sentinels.js +28 -0
- package/dist/lib/error-sentinels.js.map +1 -0
- package/dist/lib/errors.d.ts +100 -1
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +116 -1
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/logger.d.ts +14 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +65 -5
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/oauth-constants.d.ts +18 -0
- package/dist/lib/oauth-constants.d.ts.map +1 -0
- package/dist/lib/oauth-constants.js +18 -0
- package/dist/lib/oauth-constants.js.map +1 -0
- package/dist/lib/oauth-success.d.ts +2 -0
- package/dist/lib/oauth-success.d.ts.map +1 -0
- package/dist/lib/oauth-success.html +711 -711
- package/dist/lib/oauth-success.js +714 -0
- package/dist/lib/oauth-success.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +12 -3
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/prompts/opencode-codex.d.ts.map +1 -1
- package/dist/lib/prompts/opencode-codex.js +3 -2
- package/dist/lib/prompts/opencode-codex.js.map +1 -1
- package/dist/lib/recovery/hook.d.ts +43 -0
- package/dist/lib/recovery/hook.d.ts.map +1 -0
- package/dist/lib/recovery/hook.js +325 -0
- package/dist/lib/recovery/hook.js.map +1 -0
- package/dist/lib/recovery/index.d.ts +1 -0
- package/dist/lib/recovery/index.d.ts.map +1 -1
- package/dist/lib/recovery/index.js +1 -0
- package/dist/lib/recovery/index.js.map +1 -1
- package/dist/lib/recovery/storage.d.ts.map +1 -1
- package/dist/lib/recovery/storage.js +5 -1
- package/dist/lib/recovery/storage.js.map +1 -1
- package/dist/lib/recovery.d.ts +15 -30
- package/dist/lib/recovery.d.ts.map +1 -1
- package/dist/lib/recovery.js +14 -312
- package/dist/lib/recovery.js.map +1 -1
- package/dist/lib/request/fetch-helpers.js +1 -1
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/response-handler.d.ts.map +1 -1
- package/dist/lib/request/response-handler.js +8 -2
- package/dist/lib/request/response-handler.js.map +1 -1
- package/dist/lib/runtime.d.ts +101 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +77 -0
- package/dist/lib/runtime.js.map +1 -0
- package/dist/lib/schemas.d.ts +164 -1
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +251 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/storage/atomic-write.d.ts +31 -0
- package/dist/lib/storage/atomic-write.d.ts.map +1 -0
- package/dist/lib/storage/atomic-write.js +75 -0
- package/dist/lib/storage/atomic-write.js.map +1 -0
- package/dist/lib/storage/backup.d.ts +13 -0
- package/dist/lib/storage/backup.d.ts.map +1 -0
- package/dist/lib/storage/backup.js +59 -0
- package/dist/lib/storage/backup.js.map +1 -0
- package/dist/lib/storage/errors.d.ts +17 -0
- package/dist/lib/storage/errors.d.ts.map +1 -0
- package/dist/lib/storage/errors.js +38 -0
- package/dist/lib/storage/errors.js.map +1 -0
- package/dist/lib/storage/export-import.d.ts +84 -0
- package/dist/lib/storage/export-import.d.ts.map +1 -0
- package/dist/lib/storage/export-import.js +222 -0
- package/dist/lib/storage/export-import.js.map +1 -0
- package/dist/lib/storage/flagged.d.ts +29 -0
- package/dist/lib/storage/flagged.d.ts.map +1 -0
- package/dist/lib/storage/flagged.js +207 -0
- package/dist/lib/storage/flagged.js.map +1 -0
- package/dist/lib/storage/identity.d.ts +72 -0
- package/dist/lib/storage/identity.d.ts.map +1 -0
- package/dist/lib/storage/identity.js +230 -0
- package/dist/lib/storage/identity.js.map +1 -0
- package/dist/lib/storage/keychain.d.ts +146 -0
- package/dist/lib/storage/keychain.d.ts.map +1 -0
- package/dist/lib/storage/keychain.js +278 -0
- package/dist/lib/storage/keychain.js.map +1 -0
- package/dist/lib/storage/load-save.d.ts +61 -0
- package/dist/lib/storage/load-save.d.ts.map +1 -0
- package/dist/lib/storage/load-save.js +606 -0
- package/dist/lib/storage/load-save.js.map +1 -0
- package/dist/lib/storage/migrations.d.ts +35 -0
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage/migrations.js +37 -0
- package/dist/lib/storage/migrations.js.map +1 -1
- package/dist/lib/storage/normalize.d.ts +22 -0
- package/dist/lib/storage/normalize.d.ts.map +1 -0
- package/dist/lib/storage/normalize.js +114 -0
- package/dist/lib/storage/normalize.js.map +1 -0
- package/dist/lib/storage/paths.d.ts.map +1 -1
- package/dist/lib/storage/paths.js +2 -1
- package/dist/lib/storage/paths.js.map +1 -1
- package/dist/lib/storage/state.d.ts +46 -0
- package/dist/lib/storage/state.d.ts.map +1 -0
- package/dist/lib/storage/state.js +100 -0
- package/dist/lib/storage/state.js.map +1 -0
- package/dist/lib/storage/worktree-lock.d.ts +79 -0
- package/dist/lib/storage/worktree-lock.d.ts.map +1 -0
- package/dist/lib/storage/worktree-lock.js +265 -0
- package/dist/lib/storage/worktree-lock.js.map +1 -0
- package/dist/lib/storage.d.ts +21 -155
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +24 -1167
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/tools/_shared.d.ts +16 -0
- package/dist/lib/tools/_shared.d.ts.map +1 -0
- package/dist/lib/tools/_shared.js +21 -0
- package/dist/lib/tools/_shared.js.map +1 -0
- package/dist/lib/tools/codex-dashboard.d.ts +8 -0
- package/dist/lib/tools/codex-dashboard.d.ts.map +1 -0
- package/dist/lib/tools/codex-dashboard.js +176 -0
- package/dist/lib/tools/codex-dashboard.js.map +1 -0
- package/dist/lib/tools/codex-diag.d.ts +19 -0
- package/dist/lib/tools/codex-diag.d.ts.map +1 -0
- package/dist/lib/tools/codex-diag.js +139 -0
- package/dist/lib/tools/codex-diag.js.map +1 -0
- package/dist/lib/tools/codex-diff.d.ts +44 -0
- package/dist/lib/tools/codex-diff.d.ts.map +1 -0
- package/dist/lib/tools/codex-diff.js +284 -0
- package/dist/lib/tools/codex-diff.js.map +1 -0
- package/dist/lib/tools/codex-doctor.d.ts +8 -0
- package/dist/lib/tools/codex-doctor.d.ts.map +1 -0
- package/dist/lib/tools/codex-doctor.js +284 -0
- package/dist/lib/tools/codex-doctor.js.map +1 -0
- package/dist/lib/tools/codex-export.d.ts +8 -0
- package/dist/lib/tools/codex-export.d.ts.map +1 -0
- package/dist/lib/tools/codex-export.js +63 -0
- package/dist/lib/tools/codex-export.js.map +1 -0
- package/dist/lib/tools/codex-health.d.ts +8 -0
- package/dist/lib/tools/codex-health.d.ts.map +1 -0
- package/dist/lib/tools/codex-health.js +124 -0
- package/dist/lib/tools/codex-health.js.map +1 -0
- package/dist/lib/tools/codex-help.d.ts +8 -0
- package/dist/lib/tools/codex-help.d.ts.map +1 -0
- package/dist/lib/tools/codex-help.js +127 -0
- package/dist/lib/tools/codex-help.js.map +1 -0
- package/dist/lib/tools/codex-import.d.ts +8 -0
- package/dist/lib/tools/codex-import.d.ts.map +1 -0
- package/dist/lib/tools/codex-import.js +97 -0
- package/dist/lib/tools/codex-import.js.map +1 -0
- package/dist/lib/tools/codex-keychain.d.ts +36 -0
- package/dist/lib/tools/codex-keychain.d.ts.map +1 -0
- package/dist/lib/tools/codex-keychain.js +289 -0
- package/dist/lib/tools/codex-keychain.js.map +1 -0
- package/dist/lib/tools/codex-label.d.ts +8 -0
- package/dist/lib/tools/codex-label.d.ts.map +1 -0
- package/dist/lib/tools/codex-label.js +143 -0
- package/dist/lib/tools/codex-label.js.map +1 -0
- package/dist/lib/tools/codex-limits.d.ts +8 -0
- package/dist/lib/tools/codex-limits.d.ts.map +1 -0
- package/dist/lib/tools/codex-limits.js +497 -0
- package/dist/lib/tools/codex-limits.js.map +1 -0
- package/dist/lib/tools/codex-list.d.ts +8 -0
- package/dist/lib/tools/codex-list.d.ts.map +1 -0
- package/dist/lib/tools/codex-list.js +260 -0
- package/dist/lib/tools/codex-list.js.map +1 -0
- package/dist/lib/tools/codex-metrics.d.ts +8 -0
- package/dist/lib/tools/codex-metrics.d.ts.map +1 -0
- package/dist/lib/tools/codex-metrics.js +180 -0
- package/dist/lib/tools/codex-metrics.js.map +1 -0
- package/dist/lib/tools/codex-next.d.ts +8 -0
- package/dist/lib/tools/codex-next.d.ts.map +1 -0
- package/dist/lib/tools/codex-next.js +54 -0
- package/dist/lib/tools/codex-next.js.map +1 -0
- package/dist/lib/tools/codex-note.d.ts +8 -0
- package/dist/lib/tools/codex-note.d.ts.map +1 -0
- package/dist/lib/tools/codex-note.js +79 -0
- package/dist/lib/tools/codex-note.js.map +1 -0
- package/dist/lib/tools/codex-refresh.d.ts +8 -0
- package/dist/lib/tools/codex-refresh.d.ts.map +1 -0
- package/dist/lib/tools/codex-refresh.js +78 -0
- package/dist/lib/tools/codex-refresh.js.map +1 -0
- package/dist/lib/tools/codex-remove.d.ts +8 -0
- package/dist/lib/tools/codex-remove.d.ts.map +1 -0
- package/dist/lib/tools/codex-remove.js +184 -0
- package/dist/lib/tools/codex-remove.js.map +1 -0
- package/dist/lib/tools/codex-setup.d.ts +8 -0
- package/dist/lib/tools/codex-setup.d.ts.map +1 -0
- package/dist/lib/tools/codex-setup.js +26 -0
- package/dist/lib/tools/codex-setup.js.map +1 -0
- package/dist/lib/tools/codex-status.d.ts +8 -0
- package/dist/lib/tools/codex-status.d.ts.map +1 -0
- package/dist/lib/tools/codex-status.js +263 -0
- package/dist/lib/tools/codex-status.js.map +1 -0
- package/dist/lib/tools/codex-switch.d.ts +8 -0
- package/dist/lib/tools/codex-switch.d.ts.map +1 -0
- package/dist/lib/tools/codex-switch.js +122 -0
- package/dist/lib/tools/codex-switch.js.map +1 -0
- package/dist/lib/tools/codex-tag.d.ts +8 -0
- package/dist/lib/tools/codex-tag.d.ts.map +1 -0
- package/dist/lib/tools/codex-tag.js +101 -0
- package/dist/lib/tools/codex-tag.js.map +1 -0
- package/dist/lib/tools/index.d.ts +143 -0
- package/dist/lib/tools/index.d.ts.map +1 -0
- package/dist/lib/tools/index.js +67 -0
- package/dist/lib/tools/index.js.map +1 -0
- package/dist/lib/ui/format.d.ts.map +1 -1
- package/dist/lib/ui/format.js +5 -0
- package/dist/lib/ui/format.js.map +1 -1
- package/dist/lib/ui/runtime.d.ts +10 -0
- package/dist/lib/ui/runtime.d.ts.map +1 -1
- package/dist/lib/ui/runtime.js +16 -11
- package/dist/lib/ui/runtime.js.map +1 -1
- package/dist/lib/ui/select.d.ts.map +1 -1
- package/dist/lib/ui/select.js +12 -3
- package/dist/lib/ui/select.js.map +1 -1
- package/dist/lib/ui/theme.d.ts +19 -0
- package/dist/lib/ui/theme.d.ts.map +1 -1
- package/dist/lib/ui/theme.js +27 -0
- package/dist/lib/ui/theme.js.map +1 -1
- package/package.json +24 -16
- package/scripts/copy-oauth-success.js +16 -6
- package/scripts/install-oc-codex-multi-auth-core.js +462 -381
- package/scripts/install-oc-codex-multi-auth.js +15 -15
- package/scripts/test-all-models.sh +1 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in OS-keychain credential backend.
|
|
3
|
+
*
|
|
4
|
+
* Phase 4 F1. Addresses the audit finding (docs/audits/09-security-trust.md)
|
|
5
|
+
* that per-project accounts are stored as plaintext V3 JSON on disk. This
|
|
6
|
+
* module introduces an alternate storage backend that persists the SAME
|
|
7
|
+
* V3 JSON blob as a secret value in the OS keychain:
|
|
8
|
+
*
|
|
9
|
+
* - macOS: Keychain
|
|
10
|
+
* - Windows: Credential Manager
|
|
11
|
+
* - Linux: Secret Service / libsecret
|
|
12
|
+
*
|
|
13
|
+
* **Default behavior is unchanged.** The backend only activates when
|
|
14
|
+
* `CODEX_KEYCHAIN=1` is set in the environment. Any other value (unset,
|
|
15
|
+
* `"0"`, `"false"`, `""`, `"yes"`, ...) leaves the existing JSON path in
|
|
16
|
+
* full control. This is deliberate: credential storage is the highest-trust
|
|
17
|
+
* surface in the plugin and every new failure mode is a login regression.
|
|
18
|
+
*
|
|
19
|
+
* Data model:
|
|
20
|
+
* - Service name: `oc-codex-multi-auth` (fixed)
|
|
21
|
+
* - Account key: `accounts:<project-storage-key>` where the project key
|
|
22
|
+
* is the `projectName-sha256hash12` string already produced by
|
|
23
|
+
* `lib/storage/paths.ts#getProjectStorageKey`. For the global file we
|
|
24
|
+
* use the literal key `accounts:global`.
|
|
25
|
+
* - Secret payload: the exact JSON string that would otherwise be written
|
|
26
|
+
* to disk (UTF-8, pretty-printed to match the file contents; the OS
|
|
27
|
+
* keychain APIs used here store arbitrary UTF-8 so no base64 encoding
|
|
28
|
+
* is required — documented here for reviewers).
|
|
29
|
+
*
|
|
30
|
+
* Fallback contract:
|
|
31
|
+
* - Every entry point returns a soft failure (`null` on read,
|
|
32
|
+
* `{ok: false}` on write) instead of throwing when the native module
|
|
33
|
+
* cannot load or a keychain call fails. Callers MUST fall back to the
|
|
34
|
+
* JSON backend on failure; they must never silently lose credentials.
|
|
35
|
+
* - All errors are logged with `log.warn` / `log.error`. Secret values
|
|
36
|
+
* never appear in log text — only the service name, account key, and
|
|
37
|
+
* the native error message.
|
|
38
|
+
*
|
|
39
|
+
* Testing:
|
|
40
|
+
* - The native module is loaded lazily so unit tests can substitute
|
|
41
|
+
* `_setBackendForTests` to avoid hitting the real OS keychain.
|
|
42
|
+
* Integration tests that DO want to hit the real keychain can set
|
|
43
|
+
* `CODEX_KEYCHAIN=1` and let `loadBackend` resolve the module.
|
|
44
|
+
*/
|
|
45
|
+
import { createLogger } from "../logger.js";
|
|
46
|
+
const log = createLogger("storage.keychain");
|
|
47
|
+
/**
|
|
48
|
+
* The keychain service identifier this plugin owns. Chosen to match the npm
|
|
49
|
+
* package name so a human inspecting Keychain Access / Credential Manager
|
|
50
|
+
* can easily identify which program owns a stored credential.
|
|
51
|
+
*/
|
|
52
|
+
export const KEYCHAIN_SERVICE_NAME = "oc-codex-multi-auth";
|
|
53
|
+
/**
|
|
54
|
+
* Account key used when the plugin is operating without a per-project
|
|
55
|
+
* storage path (i.e. the global accounts file). Kept distinct from any
|
|
56
|
+
* per-project key so the two storage scopes never collide in the OS
|
|
57
|
+
* keychain's (service, account) index.
|
|
58
|
+
*/
|
|
59
|
+
export const GLOBAL_KEYCHAIN_ACCOUNT_KEY = "accounts:global";
|
|
60
|
+
/**
|
|
61
|
+
* Reserved account key used exclusively by the availability probe. Kept
|
|
62
|
+
* double-underscored and service-suffixed so a future refactor that drops
|
|
63
|
+
* the `accounts:` prefix on real entries (or adds a different prefix) still
|
|
64
|
+
* cannot collide with this probe (F1 post-merge LOW finding).
|
|
65
|
+
*/
|
|
66
|
+
export const KEYCHAIN_PROBE_ACCOUNT_KEY = `__probe__@${KEYCHAIN_SERVICE_NAME}`;
|
|
67
|
+
let cachedBackend = null;
|
|
68
|
+
let cacheResolved = false;
|
|
69
|
+
async function loadNativeBackend() {
|
|
70
|
+
try {
|
|
71
|
+
// Dynamic import keeps the module resolution inside the try/catch so
|
|
72
|
+
// a missing prebuilt binary (e.g. unsupported platform, partial npm
|
|
73
|
+
// install) degrades to JSON fallback instead of crashing at require
|
|
74
|
+
// time. The cast narrows the type without using `any`.
|
|
75
|
+
const mod = (await import("@napi-rs/keyring"));
|
|
76
|
+
if (!mod || typeof mod.Entry !== "function") {
|
|
77
|
+
log.warn("keychain: @napi-rs/keyring module shape unexpected; disabling keychain backend");
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// @napi-rs/keyring's `Entry` methods are synchronous by design (they
|
|
81
|
+
// delegate to native OS keychain calls). The backend interface is
|
|
82
|
+
// Promise-typed so it can be mocked with in-memory Promise-returning
|
|
83
|
+
// stubs in tests, so each method returns `Promise.resolve(...)` of
|
|
84
|
+
// the native sync result. Using non-async methods keeps
|
|
85
|
+
// `@typescript-eslint/require-await` satisfied without per-method
|
|
86
|
+
// suppressions while the Promise-returning interface stays intact.
|
|
87
|
+
const backend = {
|
|
88
|
+
get(service, account) {
|
|
89
|
+
try {
|
|
90
|
+
const entry = new mod.Entry(service, account);
|
|
91
|
+
const value = entry.getPassword();
|
|
92
|
+
return Promise.resolve(value ?? null);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
log.warn("keychain: read failed", {
|
|
96
|
+
service,
|
|
97
|
+
account,
|
|
98
|
+
error: err.message,
|
|
99
|
+
});
|
|
100
|
+
return Promise.resolve(null);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
set(service, account, secret) {
|
|
104
|
+
const entry = new mod.Entry(service, account);
|
|
105
|
+
entry.setPassword(secret);
|
|
106
|
+
return Promise.resolve();
|
|
107
|
+
},
|
|
108
|
+
delete(service, account) {
|
|
109
|
+
try {
|
|
110
|
+
const entry = new mod.Entry(service, account);
|
|
111
|
+
return Promise.resolve(entry.deletePassword());
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
log.warn("keychain: delete failed", {
|
|
115
|
+
service,
|
|
116
|
+
account,
|
|
117
|
+
error: err.message,
|
|
118
|
+
});
|
|
119
|
+
return Promise.resolve(false);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
isAvailable() {
|
|
123
|
+
// Round-trip a throwaway entry under a reserved, namespaced
|
|
124
|
+
// account key so we actually exercise the OS keychain rather
|
|
125
|
+
// than trusting that import succeeded. The key is prefixed
|
|
126
|
+
// with the service name so a future refactor that drops the
|
|
127
|
+
// `accounts:` prefix on real entries cannot collide with
|
|
128
|
+
// this probe (F1 post-merge LOW finding).
|
|
129
|
+
try {
|
|
130
|
+
const entry = new mod.Entry(KEYCHAIN_SERVICE_NAME, KEYCHAIN_PROBE_ACCOUNT_KEY);
|
|
131
|
+
entry.setPassword("probe");
|
|
132
|
+
entry.deletePassword();
|
|
133
|
+
return Promise.resolve(true);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
log.warn("keychain: availability probe failed", {
|
|
137
|
+
error: err.message,
|
|
138
|
+
});
|
|
139
|
+
return Promise.resolve(false);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
return backend;
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
log.warn("keychain: native module unavailable", {
|
|
147
|
+
error: err.message,
|
|
148
|
+
});
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolve and memoize the backend. Returns `null` when the native module
|
|
154
|
+
* cannot be loaded; callers fall back to JSON in that case.
|
|
155
|
+
*/
|
|
156
|
+
async function getBackend() {
|
|
157
|
+
if (cacheResolved)
|
|
158
|
+
return cachedBackend;
|
|
159
|
+
cachedBackend = await loadNativeBackend();
|
|
160
|
+
cacheResolved = true;
|
|
161
|
+
return cachedBackend;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build the keychain account key for a given project storage key. When the
|
|
165
|
+
* plugin is running against the global accounts file, pass `null` to use
|
|
166
|
+
* the reserved global account key.
|
|
167
|
+
*/
|
|
168
|
+
export function buildKeychainAccountKey(projectStorageKey) {
|
|
169
|
+
if (!projectStorageKey)
|
|
170
|
+
return GLOBAL_KEYCHAIN_ACCOUNT_KEY;
|
|
171
|
+
return `accounts:${projectStorageKey}`;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Reads the V3 JSON blob from the OS keychain for the given project storage
|
|
175
|
+
* key. Returns `null` when:
|
|
176
|
+
* - the native module is unavailable (not installed, missing prebuilt)
|
|
177
|
+
* - no entry exists yet
|
|
178
|
+
* - any keychain error occurs
|
|
179
|
+
*
|
|
180
|
+
* The caller MUST treat `null` as "fall back to JSON". Callers must never
|
|
181
|
+
* interpret `null` as "no credentials" unconditionally — it may mean
|
|
182
|
+
* "keychain locked / permission denied" and the JSON file still holds the
|
|
183
|
+
* authoritative copy.
|
|
184
|
+
*/
|
|
185
|
+
export async function readFromKeychain(projectStorageKey) {
|
|
186
|
+
const backend = await getBackend();
|
|
187
|
+
if (!backend)
|
|
188
|
+
return null;
|
|
189
|
+
const account = buildKeychainAccountKey(projectStorageKey);
|
|
190
|
+
return backend.get(KEYCHAIN_SERVICE_NAME, account);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Persist the V3 JSON blob to the OS keychain. Returns `{ok:false}` on
|
|
194
|
+
* failure so callers can fall back to JSON. Never throws.
|
|
195
|
+
*/
|
|
196
|
+
export async function writeToKeychain(projectStorageKey, jsonBlob) {
|
|
197
|
+
const backend = await getBackend();
|
|
198
|
+
if (!backend) {
|
|
199
|
+
return { ok: false, error: "backend unavailable" };
|
|
200
|
+
}
|
|
201
|
+
const account = buildKeychainAccountKey(projectStorageKey);
|
|
202
|
+
try {
|
|
203
|
+
await backend.set(KEYCHAIN_SERVICE_NAME, account, jsonBlob);
|
|
204
|
+
return { ok: true };
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
const message = err.message;
|
|
208
|
+
log.error("keychain: write failed", {
|
|
209
|
+
service: KEYCHAIN_SERVICE_NAME,
|
|
210
|
+
account,
|
|
211
|
+
error: message,
|
|
212
|
+
});
|
|
213
|
+
return { ok: false, error: message };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Remove the plugin's keychain entry for the given project key. Returns
|
|
218
|
+
* true when the entry existed and was deleted, false otherwise (including
|
|
219
|
+
* "not present"). Never throws.
|
|
220
|
+
*/
|
|
221
|
+
export async function deleteFromKeychain(projectStorageKey) {
|
|
222
|
+
const backend = await getBackend();
|
|
223
|
+
if (!backend)
|
|
224
|
+
return false;
|
|
225
|
+
const account = buildKeychainAccountKey(projectStorageKey);
|
|
226
|
+
return backend.delete(KEYCHAIN_SERVICE_NAME, account);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Probe the backend end-to-end (write + read + delete a throwaway entry)
|
|
230
|
+
* to confirm the OS keychain is reachable and unlocked. Used by the
|
|
231
|
+
* `codex-keychain status` tool to give the operator a clear yes/no signal
|
|
232
|
+
* instead of waiting until the next real save.
|
|
233
|
+
*
|
|
234
|
+
* Gated on the opt-in flag (F1 post-merge LOW finding): with
|
|
235
|
+
* `CODEX_KEYCHAIN` unset the probe is a no-op that returns `false` without
|
|
236
|
+
* touching the OS keychain. This preserves the "unset -> zero keychain code
|
|
237
|
+
* path" invariant the feature advertises and avoids the first-run macOS
|
|
238
|
+
* "allow/always allow" prompt that `entry.setPassword` can otherwise
|
|
239
|
+
* trigger for users who run `codex-keychain status` without opting in.
|
|
240
|
+
*
|
|
241
|
+
* Pass `env` to override the opt-in lookup in tests.
|
|
242
|
+
*/
|
|
243
|
+
export async function keychainIsAvailable(env = process.env) {
|
|
244
|
+
if (!isKeychainOptInEnabled(env))
|
|
245
|
+
return false;
|
|
246
|
+
const backend = await getBackend();
|
|
247
|
+
if (!backend)
|
|
248
|
+
return false;
|
|
249
|
+
return backend.isAvailable();
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Parse the `CODEX_KEYCHAIN` environment variable. Only the literal string
|
|
253
|
+
* `"1"` enables the opt-in. Anything else (unset, `"0"`, `"false"`, `""`,
|
|
254
|
+
* `"yes"`, ...) leaves the JSON backend in full control. This mirrors
|
|
255
|
+
* `EnvBooleanSchema`'s contract and is documented in README.md.
|
|
256
|
+
*/
|
|
257
|
+
export function isKeychainOptInEnabled(env = process.env) {
|
|
258
|
+
return env.CODEX_KEYCHAIN === "1";
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Test-only hook: inject a deterministic backend (typically an in-memory
|
|
262
|
+
* Map-backed stub) and mark the cache as resolved so `getBackend` returns
|
|
263
|
+
* the injected backend without attempting to load the native module.
|
|
264
|
+
*
|
|
265
|
+
* Passing `null` clears the cache so a subsequent call re-runs the lazy
|
|
266
|
+
* loader — useful when a test needs to verify the unavailable-backend
|
|
267
|
+
* branch.
|
|
268
|
+
*/
|
|
269
|
+
export function _setBackendForTests(backend) {
|
|
270
|
+
cachedBackend = backend;
|
|
271
|
+
cacheResolved = backend !== null;
|
|
272
|
+
}
|
|
273
|
+
/** Test-only reset to the initial "not yet loaded" state. */
|
|
274
|
+
export function _resetBackendForTests() {
|
|
275
|
+
cachedBackend = null;
|
|
276
|
+
cacheResolved = false;
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=keychain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../../lib/storage/keychain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account storage load/save pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `lib/storage.ts` in RC-2. This module owns:
|
|
5
|
+
* - the `.gitignore` side-effect when writing into a project repo,
|
|
6
|
+
* - the legacy project + global storage migrations triggered on ENOENT,
|
|
7
|
+
* - the project-global fallback seed flow,
|
|
8
|
+
* - the atomic write (temp file + rename + EEMPTY guard),
|
|
9
|
+
* - and the `withAccountStorageTransaction` read-modify-write primitive
|
|
10
|
+
* that every mutating caller above the storage layer uses.
|
|
11
|
+
*
|
|
12
|
+
* The error-handling contract is subtle and load-bearing: forward-compat
|
|
13
|
+
* (`UNSUPPORTED_SCHEMA_VERSION`) and unknown-V2 failures MUST reach the
|
|
14
|
+
* caller. Swallowing either would overwrite future-schema credentials or
|
|
15
|
+
* silently discard a user's V2 file, which is exactly the class of bug the
|
|
16
|
+
* audit flagged.
|
|
17
|
+
*/
|
|
18
|
+
import { type AccountStorageV3 } from "./migrations.js";
|
|
19
|
+
/**
|
|
20
|
+
* Loads OAuth accounts from disk storage.
|
|
21
|
+
* Automatically migrates v1 storage to v3 format if needed.
|
|
22
|
+
* @returns AccountStorageV3 if file exists and is valid, null otherwise
|
|
23
|
+
* @throws StorageError (code `UNSUPPORTED_SCHEMA_VERSION`) when the on-disk
|
|
24
|
+
* `version` field is greater than the newest format this plugin understands.
|
|
25
|
+
* Surfacing the error stops a downgraded plugin from overwriting the user's
|
|
26
|
+
* future-schema credentials with a stale or empty payload.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadAccounts(): Promise<AccountStorageV3 | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Executes a read-modify-write transaction under the storage lock and exposes
|
|
31
|
+
* an unlocked persist callback so nested save operations do not deadlock.
|
|
32
|
+
*/
|
|
33
|
+
export declare function withAccountStorageTransaction<T>(handler: (current: AccountStorageV3 | null, persist: (storage: AccountStorageV3) => Promise<void>) => Promise<T>): Promise<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Persists account storage to disk using atomic write (temp file + rename).
|
|
36
|
+
* Creates the .opencode directory if it doesn't exist.
|
|
37
|
+
* Verifies file was written correctly and provides detailed error messages.
|
|
38
|
+
* @param storage - Account storage data to save
|
|
39
|
+
* @throws StorageError with platform-aware hints on failure
|
|
40
|
+
*/
|
|
41
|
+
export declare function saveAccounts(storage: AccountStorageV3): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Deletes the account storage file from disk.
|
|
44
|
+
* Silently ignores if file doesn't exist.
|
|
45
|
+
*
|
|
46
|
+
* Ordering (F1 post-merge MEDIUM finding): unlink the on-disk JSON FIRST,
|
|
47
|
+
* then delete the keychain entry. If we cleared the keychain first and the
|
|
48
|
+
* unlink failed for a non-ENOENT reason (EACCES, EBUSY, filesystem drift),
|
|
49
|
+
* a subsequent load with opt-in still on would take the "no keychain entry,
|
|
50
|
+
* fall back to JSON" branch (see `loadAccountsInternal`) and resurrect the
|
|
51
|
+
* credentials from the still-present JSON file. Callers typically run
|
|
52
|
+
* `clearAccounts` to recover from a compromised token, so a silent
|
|
53
|
+
* resurrection is a meaningful failure mode.
|
|
54
|
+
*
|
|
55
|
+
* Fail-safe invariant: if the JSON unlink fails (non-ENOENT), we skip the
|
|
56
|
+
* keychain delete and log at `error`. Both copies remain in sync so the
|
|
57
|
+
* caller can retry safely. The operation is still best-effort (never
|
|
58
|
+
* throws) to preserve the existing contract above the storage layer.
|
|
59
|
+
*/
|
|
60
|
+
export declare function clearAccounts(): Promise<void>;
|
|
61
|
+
//# sourceMappingURL=load-save.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-save.d.ts","sourceRoot":"","sources":["../../../lib/storage/load-save.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAmBH,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAkjBzB;;;;;;;;GAQG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAErE;AAED;;;GAGG;AACH,wBAAsB,6BAA6B,CAAC,CAAC,EACnD,OAAO,EAAE,CACP,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,OAAO,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,KAClD,OAAO,CAAC,CAAC,CAAC,GACd,OAAO,CAAC,CAAC,CAAC,CAKZ;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAI3E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAgCnD"}
|