Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions artifacts/github/bundles/openai-codex-pr-27535.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"analysis_mode": "pr_first",
"commits": [
{
"author": "celia-oai",
"committed_at": "2026-06-11T00:56:21Z",
"message": "Add secret auth storage config",
"sha": "07d66eccd904cd15cd754c43d3f74a72d63a1980",
"url": "https://github.com/openai/codex/commit/07d66eccd904cd15cd754c43d3f74a72d63a1980"
},
{
"author": "celia-oai",
"committed_at": "2026-06-12T18:51:10Z",
"message": "refactor",
"sha": "3e3379b56006b9e877f5db4ba65009b4ad41c49c",
"url": "https://github.com/openai/codex/commit/3e3379b56006b9e877f5db4ba65009b4ad41c49c"
},
{
"author": "celia-oai",
"committed_at": "2026-06-11T03:51:19Z",
"message": "Add auth-specific secret namespaces",
"sha": "e026ff2133ba7c2e740eee1ecf0cc0ec5a7479a8",
"url": "https://github.com/openai/codex/commit/e026ff2133ba7c2e740eee1ecf0cc0ec5a7479a8"
},
{
"author": "celia-oai",
"committed_at": "2026-06-12T19:40:24Z",
"message": "changes",
"sha": "30f6722a49e562b8d8fccbaf221aa278d39ab30c",
"url": "https://github.com/openai/codex/commit/30f6722a49e562b8d8fccbaf221aa278d39ab30c"
}
],
"default_branch": "main",
"docs_refs": [],
"examples_refs": [],
"extracted_flags": [
"CLI",
"MCP",
"CONFIG_TOML_FILE",
"LOCAL_FS",
"FEATURES",
"KEYRING_SERVICE",
"SECRETS_VERSION",
"LOCAL_SECRETS_FILENAME",
"CODEX_AUTH_SECRETS_FILENAME",
"MCP_OAUTH_SECRETS_FILENAME",
"TUI",
"UNIX_EPOCH",
"TEST_SECRET"
],
"files": [
{
"additions": 20,
"deletions": 0,
"patch_excerpt": "@@ -113,6 +113,26 @@ pub enum OAuthCredentialsStoreMode {\n Keyring,\n }\n \n+/// Determine how auth credentials should use keyring-backed storage.\n+#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]\n+#[serde(rename_all = \"lowercase\")]\n+pub enum AuthKeyringBackendKind {\n+ /// Store the serialized auth payload directly in the OS keyring.\n+ Direct,\n+ /// Store auth payloads in the local encrypted secrets file, with the file key in the OS keyring.\n+ Secrets,\n+}\n+\n+impl Default for AuthKeyringBackendKind {\n+ fn default() -> Self {\n+ if cfg!(windows) {\n+ Self::Secrets\n+ } else {\n+ Self::Direct\n+ }\n+ }\n+}\n+\n #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]\n #[serde(rename_all = \"kebab-case\")]\n pub enum WindowsSandboxModeToml {",
"path": "codex-rs/config/src/types.rs",
"status": "modified"
},
{
"additions": 6,
"deletions": 0,
"patch_excerpt": "@@ -599,6 +599,9 @@\n \"search_tool\": {\n \"type\": \"boolean\"\n },\n+ \"secret_auth_storage\": {\n+ \"type\": \"boolean\"\n+ },\n \"shell_snapshot\": {\n \"type\": \"boolean\"\n },\n@@ -4751,6 +4754,9 @@\n \"search_tool\": {\n \"type\": \"boolean\"\n },\n+ \"secret_auth_storage\": {\n+ \"type\": \"boolean\"\n+ },\n \"shell_snapshot\": {\n \"type\": \"boolean\"\n },",
"path": "codex-rs/core/config.schema.json",
"status": "modified"
},
{
"additions": 59,
"deletions": 0,
"patch_excerpt": "@@ -0,0 +1,59 @@\n+use super::Config;\n+use super::ConfigTomlLoadResult;\n+use super::ManagedFeatures;\n+use codex_config::types::AuthKeyringBackendKind;\n+use codex_features::Feature;\n+use codex_features::FeatureConfigSource;\n+use codex_features::FeatureOverrides;\n+use codex_features::Features;\n+\n+impl Config {\n+ pub fn auth_keyring_backend_kind(&self) -> AuthKeyringBackendKind {\n+ auth_keyring_backend_kind_from_secret_auth_storage(\n+ self.features.enabled(Feature::SecretAuthStorage),\n+ )\n+ }\n+}\n+\n+/// Resolve the auth keyring backend from a partially loaded bootstrap config.\n+///\n+/// This is intended for startup paths that must read auth before managed cloud\n+/// requirements can be loaded and before a full [`Config`] exists.\n+pub fn resolve_bootstrap_auth_keyring_backend_kind(\n+ bootstrap_config: &ConfigTomlLoadResult,\n+) -> std::io::Result<AuthKeyringBac...",
"path": "codex-rs/core/src/config/auth_keyring.rs",
"status": "added"
},
{
"additions": 79,
"deletions": 0,
"patch_excerpt": "@@ -0,0 +1,79 @@\n+use super::*;\n+use codex_config::ConfigLayerStack;\n+use codex_config::ConfigRequirements;\n+use codex_config::ConfigRequirementsToml;\n+use codex_config::FeatureRequirementsToml;\n+use codex_config::RequirementSource;\n+use codex_config::Sourced;\n+use codex_config::config_toml::ConfigToml;\n+use codex_features::FeaturesToml;\n+use pretty_assertions::assert_eq;\n+use std::collections::BTreeMap;\n+\n+#[test]\n+fn resolve_bootstrap_auth_keyring_backend_kind_uses_secret_auth_storage_feature()\n+-> std::io::Result<()> {\n+ let config_toml = ConfigToml {\n+ features: Some(FeaturesToml::from(BTreeMap::from([(\n+ \"secret_auth_storage\".to_string(),\n+ true,\n+ )]))),\n+ ..Default::default()\n+ };\n+ assert_eq!(\n+ resolve_bootstrap_auth_keyring_backend_kind(&config_toml_load_result(\n+ config_toml,\n+ /*feature_requireme...",
"path": "codex-rs/core/src/config/auth_keyring_tests.rs",
"status": "added"
},
{
"additions": 2,
"deletions": 0,
"patch_excerpt": "@@ -4,8 +4,10 @@ use crate::config::edit::apply_blocking;\n use assert_matches::assert_matches;\n use codex_config::CONFIG_TOML_FILE;\n use codex_config::ConfigLayerEntry;\n+use codex_config::ConfigLayerStack;\n use codex_config::ProfileV2Name;\n use codex_config::RequirementSource;\n+use codex_config::Sourced;\n use codex_config::config_toml::AgentRoleToml;\n use codex_config::config_toml::AgentsToml;\n use codex_config::config_toml::AutoReviewToml;",
"path": "codex-rs/core/src/config/config_tests.rs",
"status": "modified"
},
{
"additions": 29,
"deletions": 1,
"patch_excerpt": "@@ -137,6 +137,7 @@ use toml::Value as TomlValue;\n use toml_edit::DocumentMut;\n \n pub(crate) mod agent_roles;\n+mod auth_keyring;\n pub mod edit;\n mod managed_features;\n mod network_proxy_spec;\n@@ -145,6 +146,7 @@ mod permissions;\n mod resolved_permission_profile;\n #[cfg(test)]\n mod schema;\n+pub use auth_keyring::resolve_bootstrap_auth_keyring_backend_kind;\n pub use codex_config::ConfigLoadOptions;\n pub use codex_config::Constrained;\n pub use codex_config::ConstraintError;\n@@ -1647,6 +1649,29 @@ pub async fn load_config_as_toml_with_cli_and_load_options(\n cli_overrides: Vec<(String, TomlValue)>,\n options: impl Into<ConfigLoadOptions>,\n ) -> std::io::Result<ConfigToml> {\n+ load_config_toml_with_layer_stack(codex_home, cwd, cli_overrides, options)\n+ .await\n+ .map(|result| result.config_toml)\n+}\n+\n+/// Partially loaded config plus the layer stack used to derive it.\n+/...",
"path": "codex-rs/core/src/config/mod.rs",
"status": "modified"
},
{
"additions": 8,
"deletions": 0,
"patch_excerpt": "@@ -81,6 +81,8 @@ pub enum Feature {\n ShellTool,\n /// Enable Claude-style lifecycle hooks loaded from hooks.json files.\n CodexHooks,\n+ /// Store CLI auth in the encrypted local secrets backend when keyring storage is selected.\n+ SecretAuthStorage,\n \n // Experimental\n /// Enable JavaScript code mode backed by the in-process V8 runtime.\n@@ -748,6 +750,12 @@ pub const FEATURES: &[FeatureSpec] = &[\n stage: Stage::Stable,\n default_enabled: true,\n },\n+ FeatureSpec {\n+ id: Feature::SecretAuthStorage,\n+ key: \"secret_auth_storage\",\n+ stage: Stage::Stable,\n+ default_enabled: cfg!(windows),\n+ },\n FeatureSpec {\n id: Feature::UnifiedExec,\n key: \"unified_exec\",",
"path": "codex-rs/features/src/lib.rs",
"status": "modified"
},
{
"additions": 10,
"deletions": 0,
"patch_excerpt": "@@ -189,6 +189,16 @@ fn tool_search_is_removed_and_disabled_by_default() {\n assert_eq!(feature_for_key(\"tool_search\"), Some(Feature::ToolSearch));\n }\n \n+#[test]\n+fn secret_auth_storage_defaults_to_windows_only() {\n+ assert_eq!(Feature::SecretAuthStorage.stage(), Stage::Stable);\n+ assert_eq!(Feature::SecretAuthStorage.default_enabled(), cfg!(windows));\n+ assert_eq!(\n+ feature_for_key(\"secret_auth_storage\"),\n+ Some(Feature::SecretAuthStorage)\n+ );\n+}\n+\n #[test]\n fn browser_controls_are_stable_and_enabled_by_default() {\n assert_eq!(Feature::InAppBrowser.stage(), Stage::Stable);",
"path": "codex-rs/features/src/tests.rs",
"status": "modified"
},
{
"additions": 1,
"deletions": 1,
"patch_excerpt": "@@ -45,7 +45,7 @@ pub trait KeyringStore: Debug + Send + Sync {\n fn delete(&self, service: &str, account: &str) -> Result<bool, CredentialStoreError>;\n }\n \n-#[derive(Debug)]\n+#[derive(Debug, Clone, Copy)]\n pub struct DefaultKeyringStore;\n \n impl KeyringStore for DefaultKeyringStore {",
"path": "codex-rs/keyring-store/src/lib.rs",
"status": "modified"
},
{
"additions": 19,
"deletions": 1,
"patch_excerpt": "@@ -17,6 +17,7 @@ mod local;\n mod sanitizer;\n \n pub use local::LocalSecretsBackend;\n+pub use local::LocalSecretsNamespace;\n pub use sanitizer::redact_secrets;\n \n const KEYRING_SERVICE: &str = \"codex\";\n@@ -122,6 +123,22 @@ impl SecretsManager {\n Self { backend }\n }\n \n+ pub fn new_with_keyring_store_and_namespace(\n+ codex_home: PathBuf,\n+ backend_kind: SecretsBackendKind,\n+ keyring_store: Arc<dyn KeyringStore>,\n+ namespace: LocalSecretsNamespace,\n+ ) -> Self {\n+ let backend: Arc<dyn SecretsBackend> = match backend_kind {\n+ SecretsBackendKind::Local => Arc::new(LocalSecretsBackend::new_with_namespace(\n+ codex_home,\n+ keyring_store,\n+ namespace,\n+ )),\n+ };\n+ Self { backend }\n+ }\n+\n pub fn set(&self, scope: &SecretScope, name: &SecretName, value: &str) -...",
"path": "codex-rs/secrets/src/lib.rs",
"status": "modified"
},
{
"additions": 88,
"deletions": 2,
"patch_excerpt": "@@ -35,6 +35,20 @@ use super::keyring_service;\n \n const SECRETS_VERSION: u8 = 1;\n const LOCAL_SECRETS_FILENAME: &str = \"local.age\";\n+const CODEX_AUTH_SECRETS_FILENAME: &str = \"codex_auth.age\";\n+const MCP_OAUTH_SECRETS_FILENAME: &str = \"mcp_oauth.age\";\n+\n+/// Selects the local encrypted file used by a `LocalSecretsBackend`.\n+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\n+pub enum LocalSecretsNamespace {\n+ /// General managed secrets stored in `local.age`.\n+ #[default]\n+ ManagedSecrets,\n+ /// Codex authentication credentials used by the CLI, TUI, app server, and other clients.\n+ CodexAuth,\n+ /// OAuth credentials for external MCP servers.\n+ McpOAuth,\n+}\n \n #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]\n struct SecretsFile {\n@@ -55,13 +69,27 @@ impl SecretsFile {\n pub struct LocalSecretsBackend {\n codex_home: PathBuf,\n keyring_st...",
"path": "codex-rs/secrets/src/local.rs",
"status": "modified"
}
],
"linked_issues": [
"#27504"
],
"notes": [
"Built from GitHub pull-request, commits, files, and repo endpoints."
],
"primary_pr": {
"body": "## Why\n\nCLI auth and MCP OAuth credentials should use separate encrypted files while sharing the existing local-secrets implementation and OS-keyring-backed encryption key mechanism.\n\nThis is the second PR in the encrypted-auth stack:\n\n1. #27504 — feature and config selection\n2. This PR — auth-specific local-secrets namespaces\n3. CLI auth implementation and activation\n4. MCP OAuth implementation and activation\n\n## What Changed\n\n- Added `LocalSecretsNamespace` variants for shared secrets, CLI auth, and MCP OAuth.\n- Selected `local.age`, `cli_auth.age`, or `mcp_oauth.age` from the namespace.\n- Made atomic temporary filenames derive from the selected secrets filename.\n- Added namespaced `SecretsManager` construction and coverage proving the auth namespaces write separate encrypted files.\n- Made the default keyring store clonable for downstream namespaced auth backends.\n\nThis PR does not activate either auth backend or change existing credential behavior.\n\n## Validation\n\n- `just test -p codex-secrets` — 7 passed\n- `just test -p codex-keyring-store` — package has no test binaries\n- `just fmt`\n",
"labels": [],
"merged_at": "2026-06-12T19:52:50Z",
"number": 27535,
"state": "merged",
"title": "feat: add auth-specific encrypted secret namespaces",
"url": "https://github.com/openai/codex/pull/27535"
},
"repo": "openai/codex",
"schema": "github_change_bundle/v1"
}
Loading