mirror of
https://github.com/openai/codex.git
synced 2026-05-05 05:42:33 +03:00
Refactor network approvals to host/protocol/port scope (#12140)
## Summary Simplify network approvals by removing per-attempt proxy correlation and moving to session-level approval dedupe keyed by (host, protocol, port). Instead of encoding attempt IDs into proxy credentials/URLs, we now treat approvals as a destination policy decision. - Concurrent calls to the same destination share one approval prompt. - Different destinations (or same host on different ports) get separate prompts. - Allow once approves the current queued request group only. - Allow for session caches that (host, protocol, port) and auto-allows future matching requests. - Never policy continues to deny without prompting. Example: - 3 calls: - a.com (line 443) - b.com (line 443) - a.com (line 443) => 2 prompts total (a, b), second a waits on the first decision. - a.com:80 is treated separately from a.com line 443 ## Testing - `just fmt` (in `codex-rs`) - `cargo test -p codex-core tools::network_approval::tests` - `cargo test -p codex-core` (unit tests pass; existing integration-suite failures remain in this environment)
This commit is contained in:
@@ -44,6 +44,7 @@ use codex_app_server_protocol::McpToolCallError;
|
||||
use codex_app_server_protocol::McpToolCallResult;
|
||||
use codex_app_server_protocol::McpToolCallStatus;
|
||||
use codex_app_server_protocol::ModelReroutedNotification;
|
||||
use codex_app_server_protocol::NetworkApprovalContext as V2NetworkApprovalContext;
|
||||
use codex_app_server_protocol::PatchApplyStatus;
|
||||
use codex_app_server_protocol::PlanDeltaNotification;
|
||||
use codex_app_server_protocol::RawResponseItemCompletedNotification;
|
||||
@@ -106,6 +107,17 @@ use tracing::error;
|
||||
|
||||
type JsonValue = serde_json::Value;
|
||||
|
||||
enum CommandExecutionApprovalPresentation {
|
||||
Network(V2NetworkApprovalContext),
|
||||
Command(CommandExecutionCompletionItem),
|
||||
}
|
||||
|
||||
struct CommandExecutionCompletionItem {
|
||||
command: String,
|
||||
cwd: PathBuf,
|
||||
command_actions: Vec<V2ParsedCommand>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn apply_bespoke_event_handling(
|
||||
event: Event,
|
||||
@@ -245,6 +257,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
command,
|
||||
cwd,
|
||||
reason,
|
||||
network_approval_context,
|
||||
proposed_execpolicy_amendment,
|
||||
parsed_cmd,
|
||||
..
|
||||
@@ -280,7 +293,32 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
.cloned()
|
||||
.map(V2ParsedCommand::from)
|
||||
.collect::<Vec<_>>();
|
||||
let command_string = shlex_join(&command);
|
||||
let presentation = if let Some(network_approval_context) =
|
||||
network_approval_context.map(V2NetworkApprovalContext::from)
|
||||
{
|
||||
CommandExecutionApprovalPresentation::Network(network_approval_context)
|
||||
} else {
|
||||
let command_string = shlex_join(&command);
|
||||
let completion_item = CommandExecutionCompletionItem {
|
||||
command: command_string,
|
||||
cwd: cwd.clone(),
|
||||
command_actions: command_actions.clone(),
|
||||
};
|
||||
CommandExecutionApprovalPresentation::Command(completion_item)
|
||||
};
|
||||
let (network_approval_context, command, cwd, command_actions, completion_item) =
|
||||
match presentation {
|
||||
CommandExecutionApprovalPresentation::Network(
|
||||
network_approval_context,
|
||||
) => (Some(network_approval_context), None, None, None, None),
|
||||
CommandExecutionApprovalPresentation::Command(completion_item) => (
|
||||
None,
|
||||
Some(completion_item.command.clone()),
|
||||
Some(completion_item.cwd.clone()),
|
||||
Some(completion_item.command_actions.clone()),
|
||||
Some(completion_item),
|
||||
),
|
||||
};
|
||||
let proposed_execpolicy_amendment_v2 =
|
||||
proposed_execpolicy_amendment.map(V2ExecPolicyAmendment::from);
|
||||
|
||||
@@ -290,9 +328,10 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
item_id: call_id.clone(),
|
||||
approval_id: approval_id.clone(),
|
||||
reason,
|
||||
command: Some(command_string.clone()),
|
||||
cwd: Some(cwd.clone()),
|
||||
command_actions: Some(command_actions.clone()),
|
||||
network_approval_context,
|
||||
command,
|
||||
cwd,
|
||||
command_actions,
|
||||
proposed_execpolicy_amendment: proposed_execpolicy_amendment_v2,
|
||||
};
|
||||
let rx = outgoing
|
||||
@@ -306,9 +345,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
conversation_id,
|
||||
approval_id,
|
||||
call_id,
|
||||
command_string,
|
||||
cwd,
|
||||
command_actions,
|
||||
completion_item,
|
||||
rx,
|
||||
conversation,
|
||||
outgoing,
|
||||
@@ -1790,9 +1827,7 @@ async fn on_command_execution_request_approval_response(
|
||||
conversation_id: ThreadId,
|
||||
approval_id: Option<String>,
|
||||
item_id: String,
|
||||
command: String,
|
||||
cwd: PathBuf,
|
||||
command_actions: Vec<V2ParsedCommand>,
|
||||
completion_item: Option<CommandExecutionCompletionItem>,
|
||||
receiver: oneshot::Receiver<ClientRequestResult>,
|
||||
conversation: Arc<CodexThread>,
|
||||
outgoing: ThreadScopedOutgoingMessageSender,
|
||||
@@ -1864,15 +1899,16 @@ async fn on_command_execution_request_approval_response(
|
||||
|
||||
if let Some(status) = completion_status
|
||||
&& !suppress_subcommand_completion_item
|
||||
&& let Some(completion_item) = completion_item
|
||||
{
|
||||
complete_command_execution_item(
|
||||
conversation_id,
|
||||
event_turn_id.clone(),
|
||||
item_id.clone(),
|
||||
command.clone(),
|
||||
cwd.clone(),
|
||||
completion_item.command,
|
||||
completion_item.cwd,
|
||||
None,
|
||||
command_actions.clone(),
|
||||
completion_item.command_actions,
|
||||
status,
|
||||
&outgoing,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user