mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: use encrypted local secrets for CLI auth (#27539)
## Why Windows Credential Manager limits generic credential blobs to 2,560 bytes. Large serialized ChatGPT auth payloads can exceed that limit, so keyring-mode CLI auth needs a backend that keeps only the encryption key in the OS keyring and stores the payload in Codex's encrypted local-secrets file. This is the third PR in the encrypted-auth stack: 1. #27504 — feature and config selection 2. #27535 — auth-specific local-secrets namespaces 3. This PR — CLI auth implementation and activation 4. MCP OAuth implementation and activation ## What Changed - Added encrypted CLI-auth storage using the `CliAuth` secrets namespace. - Preserved direct keyring storage for platforms/configurations where it remains selected. - Selected the backend consistently for login, logout, refresh, device-code login, auth loading, and login restrictions. - Threaded resolved bootstrap/full config through CLI, exec, TUI, app-server account handling, cloud config, and cloud tasks. - Removed stale `auth.json` fallback data after successful encrypted saves and removed encrypted, direct-keyring, and fallback data during logout. - Added storage and integration coverage for both direct and encrypted keyring modes. MCP OAuth persistence is intentionally left to the next PR. ## Validation - `just test -p codex-login` — 131 passed - `just test -p codex-cli` — 280 passed - `just test -p codex-app-server v2::account` — 25 passed - `just test -p codex-cloud-config service` — 21 passed, 7 skipped - `just fix -p codex-login` - `just fix -p codex-cli` - `just fmt`
This commit is contained in:
committed by
GitHub
Unverified
parent
576f603440
commit
56c97e3b5c
@@ -62,8 +62,10 @@ use codex_core::check_execpolicy_for_warnings;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::ConfigTomlLoadResult;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::config::load_config_as_toml_with_cli_and_load_options;
|
||||
use codex_core::config::load_config_toml_with_layer_stack;
|
||||
use codex_core::config::resolve_bootstrap_auth_keyring_backend_kind;
|
||||
use codex_core::config::resolve_oss_provider;
|
||||
use codex_core::config::resolve_profile_v2_config_path;
|
||||
use codex_core::find_thread_meta_by_name_str;
|
||||
@@ -331,7 +333,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let bootstrap_config_toml = load_config_toml_or_exit(
|
||||
let bootstrap_config = load_bootstrap_config_or_exit(
|
||||
&codex_home,
|
||||
Some(&config_cwd),
|
||||
cli_kv_overrides.clone(),
|
||||
@@ -340,6 +342,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
CloudConfigBundleLoader::default(),
|
||||
)
|
||||
.await;
|
||||
let bootstrap_config_toml = &bootstrap_config.config_toml;
|
||||
|
||||
let chatgpt_base_url = bootstrap_config_toml
|
||||
.chatgpt_base_url
|
||||
@@ -351,6 +354,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
bootstrap_config_toml
|
||||
.cli_auth_credentials_store
|
||||
.unwrap_or_default(),
|
||||
resolve_bootstrap_auth_keyring_backend_kind(&bootstrap_config)?,
|
||||
chatgpt_base_url,
|
||||
)
|
||||
.await;
|
||||
@@ -359,12 +363,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
let run_cloud_config_bundle = cloud_config_bundle.clone();
|
||||
|
||||
let model_provider = if oss {
|
||||
let config_toml_with_cloud_config;
|
||||
let bootstrap_config_with_cloud_config;
|
||||
let config_toml_for_oss = if oss_provider.is_none() {
|
||||
// The first load intentionally skips cloud config so we can read
|
||||
// auth/base-url settings needed to fetch the bundle. If OSS mode
|
||||
// needs a default provider from config, reload with the bundle.
|
||||
config_toml_with_cloud_config = load_config_toml_or_exit(
|
||||
bootstrap_config_with_cloud_config = load_bootstrap_config_or_exit(
|
||||
&codex_home,
|
||||
Some(&config_cwd),
|
||||
cli_kv_overrides.clone(),
|
||||
@@ -373,9 +377,9 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
cloud_config_bundle.clone(),
|
||||
)
|
||||
.await;
|
||||
&config_toml_with_cloud_config
|
||||
&bootstrap_config_with_cloud_config.config_toml
|
||||
} else {
|
||||
&bootstrap_config_toml
|
||||
bootstrap_config_toml
|
||||
};
|
||||
|
||||
let resolved = resolve_oss_provider(oss_provider.as_deref(), config_toml_for_oss);
|
||||
@@ -466,6 +470,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
if let Err(err) = enforce_login_restrictions(&AuthConfig {
|
||||
codex_home: config.codex_home.to_path_buf(),
|
||||
auth_credentials_store_mode: config.cli_auth_credentials_store_mode,
|
||||
keyring_backend_kind: config.auth_keyring_backend_kind(),
|
||||
forced_login_method: config.forced_login_method,
|
||||
forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(),
|
||||
chatgpt_base_url: Some(config.chatgpt_base_url.clone()),
|
||||
@@ -609,15 +614,15 @@ where
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
async fn load_config_toml_or_exit(
|
||||
async fn load_bootstrap_config_or_exit(
|
||||
codex_home: &Path,
|
||||
cwd: Option<&AbsolutePathBuf>,
|
||||
cli_kv_overrides: Vec<(String, codex_config::TomlValue)>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
cloud_config_bundle: CloudConfigBundleLoader,
|
||||
) -> codex_config::config_toml::ConfigToml {
|
||||
match load_config_as_toml_with_cli_and_load_options(
|
||||
) -> ConfigTomlLoadResult {
|
||||
match load_config_toml_with_layer_stack(
|
||||
codex_home,
|
||||
cwd,
|
||||
cli_kv_overrides,
|
||||
|
||||
Reference in New Issue
Block a user