mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Represent MCP authentication with an enum (#29924)
## Why MCP authentication has distinct OAuth and ChatGPT-session flows. Representing that choice as `use_chatgpt_auth` makes one flow implicit and allows the configuration model to express the distinction only through a boolean. ChatGPT credential forwarding also needs a first-party trust boundary. A configurable `chatgpt_base_url` controls routing, but must not grant an MCP server permission to receive session credentials. This change builds on #29733, where the boolean was introduced. ## What changed - Replace `use_chatgpt_auth` with an `auth` field backed by the exhaustive `McpServerAuth` enum. - Support `auth = "oauth"` and `auth = "chatgpt"`, with OAuth remaining the default. - Trust only the origin derived from the existing hardcoded `CHATGPT_CODEX_BASE_URL` when granting ChatGPT auth to an MCP server. - Keep configured bearer tokens and authorization headers ahead of the selected authentication flow. - Update config writers, schema output, fixtures, and integration-test setup to use the enum. ## Verification Integration coverage exercises the complete streamable HTTP startup path in two independent configurations: - A directly constructed MCP configuration verifies that matching an overridden `chatgpt_base_url` does not grant ChatGPT auth. - A persisted `config.toml` containing an attacker-controlled `chatgpt_base_url` and `auth = "chatgpt"` verifies the same boundary through normal config parsing. Both tests complete MCP initialization and tool listing and assert that the full captured request sequence contains no authorization headers. Separate integration coverage verifies that configured authorization takes precedence over ChatGPT auth.
This commit is contained in:
committed by
GitHub
Unverified
parent
6801941cfe
commit
f8937b7d86
@@ -114,7 +114,7 @@ use tempfile::TempDir;
|
||||
|
||||
fn stdio_mcp(command: &str) -> McpServerConfig {
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: command.to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -141,7 +141,7 @@ fn stdio_mcp(command: &str) -> McpServerConfig {
|
||||
|
||||
fn http_mcp(url: &str) -> McpServerConfig {
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: url.to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -5568,7 +5568,7 @@ async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> {
|
||||
servers.insert(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "echo".to_string(),
|
||||
args: vec!["hello".to_string()],
|
||||
@@ -5926,7 +5926,7 @@ async fn replace_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: vec!["--verbose".to_string()],
|
||||
@@ -6006,7 +6006,7 @@ async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6062,7 +6062,7 @@ async fn replace_mcp_servers_serializes_sourced_env_vars() -> anyhow::Result<()>
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6121,7 +6121,7 @@ async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6177,7 +6177,7 @@ async fn replace_mcp_servers_streamable_http_serializes_bearer_token() -> anyhow
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: Some("MCP_TOKEN".to_string()),
|
||||
@@ -6245,7 +6245,7 @@ async fn replace_mcp_servers_streamable_http_serializes_custom_headers() -> anyh
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: Some("MCP_TOKEN".to_string()),
|
||||
@@ -6328,7 +6328,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh
|
||||
let mut servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: Some("MCP_TOKEN".to_string()),
|
||||
@@ -6367,7 +6367,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh
|
||||
servers.insert(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -6435,7 +6435,7 @@ async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers()
|
||||
(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: Some("MCP_TOKEN".to_string()),
|
||||
@@ -6464,7 +6464,7 @@ async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers()
|
||||
(
|
||||
"logs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "logs-server".to_string(),
|
||||
args: vec!["--follow".to_string()],
|
||||
@@ -6553,7 +6553,7 @@ async fn replace_mcp_servers_serializes_disabled_flag() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6604,7 +6604,7 @@ async fn replace_mcp_servers_serializes_required_flag() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6655,7 +6655,7 @@ async fn replace_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6711,7 +6711,7 @@ async fn replace_mcp_servers_streamable_http_serializes_oauth_resource() -> anyh
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use codex_config::types::AppToolApproval;
|
||||
use codex_config::types::McpServerAuth;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerEnvVar;
|
||||
use codex_config::types::McpServerToolConfig;
|
||||
@@ -95,8 +96,8 @@ fn serialize_mcp_server_table(config: &McpServerConfig) -> TomlTable {
|
||||
}
|
||||
}
|
||||
|
||||
if config.use_chatgpt_auth {
|
||||
entry["use_chatgpt_auth"] = value(true);
|
||||
if matches!(&config.auth, McpServerAuth::ChatGpt) {
|
||||
entry["auth"] = value("chatgpt");
|
||||
}
|
||||
if !config.enabled {
|
||||
entry["enabled"] = value(false);
|
||||
|
||||
@@ -873,7 +873,7 @@ fn blocking_replace_mcp_servers_round_trips() {
|
||||
servers.insert(
|
||||
"stdio".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: vec!["--flag".to_string()],
|
||||
@@ -908,7 +908,7 @@ fn blocking_replace_mcp_servers_round_trips() {
|
||||
servers.insert(
|
||||
"http".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com".to_string(),
|
||||
bearer_token_env_var: Some("TOKEN".to_string()),
|
||||
@@ -983,7 +983,7 @@ fn blocking_replace_mcp_servers_serializes_tool_approval_overrides() {
|
||||
servers.insert(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1044,7 +1044,7 @@ foo = { command = "cmd" }
|
||||
servers.insert(
|
||||
"foo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1095,7 +1095,7 @@ foo = { command = "cmd" } # keep me
|
||||
servers.insert(
|
||||
"foo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1145,7 +1145,7 @@ foo = { command = "cmd", args = ["--flag"] } # keep me
|
||||
servers.insert(
|
||||
"foo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1196,7 +1196,7 @@ foo = { command = "cmd" }
|
||||
servers.insert(
|
||||
"foo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -357,7 +357,7 @@ fn mcp_dependency_to_server_config(
|
||||
.as_ref()
|
||||
.ok_or_else(|| "missing url for streamable_http dependency".to_string())?;
|
||||
return Ok(McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: url.clone(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -387,7 +387,7 @@ fn mcp_dependency_to_server_config(
|
||||
.as_ref()
|
||||
.ok_or_else(|| "missing command for stdio dependency".to_string())?;
|
||||
return Ok(McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
auth: Default::default(),
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: command.clone(),
|
||||
args: Vec::new(),
|
||||
|
||||
Reference in New Issue
Block a user