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:
viyatb-oai
2026-02-20 10:39:55 -08:00
committed by GitHub
parent 41f15bf07b
commit e8afaed502
40 changed files with 570 additions and 739 deletions

View File

@@ -5,6 +5,8 @@ use crate::protocol::common::AuthMode;
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::account::PlanType;
use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment;
use codex_protocol::approvals::NetworkApprovalContext as CoreNetworkApprovalContext;
use codex_protocol::approvals::NetworkApprovalProtocol as CoreNetworkApprovalProtocol;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::ForcedLoginMethod;
@@ -650,6 +652,32 @@ pub enum CommandExecutionApprovalDecision {
Cancel,
}
v2_enum_from_core! {
pub enum NetworkApprovalProtocol from CoreNetworkApprovalProtocol {
Http,
Https,
Socks5Tcp,
Socks5Udp,
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct NetworkApprovalContext {
pub host: String,
pub protocol: NetworkApprovalProtocol,
}
impl From<CoreNetworkApprovalContext> for NetworkApprovalContext {
fn from(value: CoreNetworkApprovalContext) -> Self {
Self {
host: value.host,
protocol: value.protocol.into(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -3331,6 +3359,10 @@ pub struct CommandExecutionRequestApprovalParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub reason: Option<String>,
/// Optional context for managed-network approval prompts.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub network_approval_context: Option<NetworkApprovalContext>,
/// The command to be executed.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]