Download codex-rs/protocol/src/approvals.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 19.3 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/protocol/src/approvals.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/protocol/src/approvals.rs
-
curl -L -o approvals.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/protocol/src/approvals.rs
19.3 kB
| use crate::mcp::RequestId; | |
| use crate::models::AdditionalPermissionProfile; | |
| use crate::models::PermissionProfile; | |
| use crate::parse_command::ParsedCommand; | |
| use crate::protocol::FileChange; | |
| use crate::protocol::ReviewDecision; | |
| use crate::request_permissions::RequestPermissionProfile; | |
| use codex_utils_absolute_path::AbsolutePathBuf; | |
| use codex_utils_path_uri::LegacyAppPathString; | |
| use codex_utils_path_uri::PathUri; | |
| use schemars::JsonSchema; | |
| use serde::Deserialize; | |
| use serde::Serialize; | |
| use serde_json::Value as JsonValue; | |
| use std::collections::HashMap; | |
| use std::path::PathBuf; | |
| use ts_rs::TS; | |
| /// Fully resolved permissions for rerunning an intercepted child process. | |
| pub struct ResolvedPermissionProfile { | |
| pub permission_profile: PermissionProfile, | |
| } | |
| pub enum EscalationPermissions { | |
| /// Permissions to merge with the active turn permissions. | |
| AdditionalPermissionProfile(AdditionalPermissionProfile), | |
| /// Fully resolved permissions that should replace the active turn permissions. | |
| ResolvedPermissionProfile(ResolvedPermissionProfile), | |
| } | |
| /// Proposed execpolicy change to allow commands starting with this prefix. | |
| /// | |
| /// The `command` tokens form the prefix that would be added as an execpolicy | |
| /// `prefix_rule(..., decision="allow")`, letting the agent bypass approval for | |
| /// commands that start with this token sequence. | |
| pub struct ExecPolicyAmendment { | |
| pub command: Vec<String>, | |
| } | |
| impl ExecPolicyAmendment { | |
| pub fn new(command: Vec<String>) -> Self { | |
| Self { command } | |
| } | |
| pub fn command(&self) -> &[String] { | |
| &self.command | |
| } | |
| } | |
| impl From<Vec<String>> for ExecPolicyAmendment { | |
| fn from(command: Vec<String>) -> Self { | |
| Self { command } | |
| } | |
| } | |
| pub enum NetworkApprovalProtocol { | |
| // TODO(viyatb): Add websocket protocol variants when managed proxy policy | |
| // decisions expose websocket traffic as a distinct approval context. | |
| Http, | |
| Https, | |
| Socks5Tcp, | |
| Socks5Udp, | |
| } | |
| pub struct NetworkApprovalContext { | |
| pub host: String, | |
| pub protocol: NetworkApprovalProtocol, | |
| } | |
| pub enum NetworkPolicyRuleAction { | |
| Allow, | |
| Deny, | |
| } | |
| pub enum GuardianRiskLevel { | |
| Low, | |
| Medium, | |
| High, | |
| Critical, | |
| } | |
| pub enum GuardianUserAuthorization { | |
| Unknown, | |
| Low, | |
| Medium, | |
| High, | |
| } | |
| /// Final allow/deny outcome returned by the guardian reviewer. | |
| pub enum GuardianAssessmentOutcome { | |
| Allow, | |
| Deny, | |
| } | |
| pub enum GuardianAssessmentStatus { | |
| InProgress, | |
| Approved, | |
| Denied, | |
| TimedOut, | |
| Aborted, | |
| } | |
| pub enum GuardianAssessmentDecisionSource { | |
| Agent, | |
| } | |
| pub enum GuardianCommandSource { | |
| Shell, | |
| UnifiedExec, | |
| } | |
| pub enum GuardianAssessmentAction { | |
| Command { | |
| source: GuardianCommandSource, | |
| command: String, | |
| cwd: LegacyAppPathString, | |
| }, | |
| Execve { | |
| source: GuardianCommandSource, | |
| program: String, | |
| argv: Vec<String>, | |
| cwd: AbsolutePathBuf, | |
| }, | |
| /// A child approval for input to an existing command execution item. | |
| WriteStdin { | |
| approval_id: String, | |
| process_id: String, | |
| stdin: String, | |
| /// Launch directory of the existing terminal, not its current working directory. | |
| cwd: PathUri, | |
| }, | |
| ApplyPatch { | |
| cwd: LegacyAppPathString, | |
| files: Vec<LegacyAppPathString>, | |
| }, | |
| NetworkAccess { | |
| target: String, | |
| host: String, | |
| protocol: NetworkApprovalProtocol, | |
| port: u16, | |
| }, | |
| McpToolCall { | |
| server: String, | |
| tool_name: String, | |
| connector_id: Option<String>, | |
| connector_name: Option<String>, | |
| tool_title: Option<String>, | |
| }, | |
| RequestPermissions { | |
| reason: Option<String>, | |
| permissions: RequestPermissionProfile, | |
| }, | |
| } | |
| pub struct NetworkPolicyAmendment { | |
| pub host: String, | |
| pub action: NetworkPolicyRuleAction, | |
| } | |
| /// Why this approval needs a fresh Guardian assessment. | |
| pub enum GuardianReviewReason { | |
| Policy, | |
| FreshRequired, | |
| MissingScore, | |
| StaleScore, | |
| InvalidScore, | |
| IncompatibleCompaction, | |
| ElevatedRisk, | |
| ScoringFailure, | |
| AuthorizationChanged, | |
| Unknown, | |
| } | |
| pub struct GuardianAssessmentEvent { | |
| /// Request-scoped trigger; absent in events recorded by older clients. | |
| pub review_reason: Option<GuardianReviewReason>, | |
| pub model_context: Option<crate::items::ModelInvocationContext>, | |
| /// Stable identifier for this guardian review lifecycle. | |
| pub id: String, | |
| /// Thread item being reviewed, when the review maps to a concrete item. | |
| pub target_item_id: Option<String>, | |
| /// Trusted plugin attribution for command items synthesized from this review. | |
| pub plugin_id: Option<String>, | |
| /// Safe plugin-relative path for command items synthesized from this review. | |
| pub script_path: Option<String>, | |
| /// Turn ID that this assessment belongs to. | |
| /// Uses `#[serde(default)]` for backwards compatibility. | |
| pub turn_id: String, | |
| pub started_at_ms: i64, | |
| pub completed_at_ms: Option<i64>, | |
| pub status: GuardianAssessmentStatus, | |
| /// Coarse risk label. Omitted while the assessment is in progress. | |
| pub risk_level: Option<GuardianRiskLevel>, | |
| /// How directly the transcript authorizes the reviewed action. | |
| pub user_authorization: Option<GuardianUserAuthorization>, | |
| /// Human-readable explanation of the final assessment. Omitted while in progress. | |
| pub rationale: Option<String>, | |
| /// Source that produced the terminal assessment decision. | |
| pub decision_source: Option<GuardianAssessmentDecisionSource>, | |
| /// Canonical action payload that was reviewed. | |
| pub action: GuardianAssessmentAction, | |
| } | |
| /// Distinguishes a command approval from input sent to an existing terminal. | |
| pub enum ExecApprovalKind { | |
| Command, | |
| WriteStdin, | |
| } | |
| pub struct ExecApprovalRequestEvent { | |
| pub model_context: Option<crate::items::ModelInvocationContext>, | |
| /// Missing on older events, which retain command approval semantics. | |
| pub kind: ExecApprovalKind, | |
| /// Identifier for the associated command execution item. | |
| pub call_id: String, | |
| /// Trusted plugin attribution for the command item, when available. | |
| pub plugin_id: Option<String>, | |
| /// Safe plugin-relative path for the command item, when available. | |
| pub script_path: Option<String>, | |
| /// Identifier for this specific approval callback. | |
| /// | |
| /// When absent, the approval is for the command item itself (`call_id`). | |
| /// This is present for subcommand approvals (via execve intercept) and stdin writes. | |
| pub approval_id: Option<String>, | |
| /// Turn ID that this command belongs to. | |
| /// Uses `#[serde(default)]` for backwards compatibility. | |
| pub turn_id: String, | |
| /// Environment in which the command will run. | |
| pub environment_id: Option<String>, | |
| pub started_at_ms: i64, | |
| /// The command to be executed. | |
| pub command: Vec<String>, | |
| /// The command's working directory, or the launch directory for terminal input. | |
| pub cwd: LegacyAppPathString, | |
| /// Optional human-readable reason for the approval (e.g. retry without sandbox). | |
| pub reason: Option<String>, | |
| /// Optional network context for a blocked request that can be approved. | |
| pub network_approval_context: Option<NetworkApprovalContext>, | |
| /// Proposed execpolicy amendment that can be applied to allow future runs. | |
| pub proposed_execpolicy_amendment: Option<ExecPolicyAmendment>, | |
| /// Proposed network policy amendments (for example allow/deny this host in future). | |
| pub proposed_network_policy_amendments: Option<Vec<NetworkPolicyAmendment>>, | |
| /// Optional additional filesystem permissions requested for this command. | |
| pub additional_permissions: Option<AdditionalPermissionProfile>, | |
| /// Ordered list of decisions the client may present for this prompt. | |
| /// | |
| /// When absent, clients should derive the legacy default set from the | |
| /// other fields on this request. | |
| pub available_decisions: Option<Vec<ReviewDecision>>, | |
| pub parsed_cmd: Vec<ParsedCommand>, | |
| } | |
| impl ExecApprovalRequestEvent { | |
| pub fn effective_approval_id(&self) -> String { | |
| self.approval_id | |
| .clone() | |
| .unwrap_or_else(|| self.call_id.clone()) | |
| } | |
| pub fn effective_available_decisions(&self) -> Vec<ReviewDecision> { | |
| // available_decisions is a new field that may not be populated by older | |
| // senders, so we fall back to the legacy logic if it's not present. | |
| match &self.available_decisions { | |
| Some(decisions) => decisions.clone(), | |
| None => Self::default_available_decisions( | |
| self.network_approval_context.as_ref(), | |
| self.proposed_execpolicy_amendment.as_ref(), | |
| self.proposed_network_policy_amendments.as_deref(), | |
| self.additional_permissions.as_ref(), | |
| ), | |
| } | |
| } | |
| pub fn default_available_decisions( | |
| network_approval_context: Option<&NetworkApprovalContext>, | |
| proposed_execpolicy_amendment: Option<&ExecPolicyAmendment>, | |
| proposed_network_policy_amendments: Option<&[NetworkPolicyAmendment]>, | |
| additional_permissions: Option<&AdditionalPermissionProfile>, | |
| ) -> Vec<ReviewDecision> { | |
| if network_approval_context.is_some() { | |
| let mut decisions = vec![ReviewDecision::Approved, ReviewDecision::ApprovedForSession]; | |
| if let Some(amendment) = proposed_network_policy_amendments.and_then(|amendments| { | |
| amendments | |
| .iter() | |
| .find(|amendment| amendment.action == NetworkPolicyRuleAction::Allow) | |
| }) { | |
| decisions.push(ReviewDecision::NetworkPolicyAmendment { | |
| network_policy_amendment: amendment.clone(), | |
| }); | |
| } | |
| decisions.push(ReviewDecision::Abort); | |
| return decisions; | |
| } | |
| if additional_permissions.is_some() { | |
| return vec![ReviewDecision::Approved, ReviewDecision::Abort]; | |
| } | |
| let mut decisions = vec![ReviewDecision::Approved]; | |
| if let Some(prefix) = proposed_execpolicy_amendment { | |
| decisions.push(ReviewDecision::ApprovedExecpolicyAmendment { | |
| proposed_execpolicy_amendment: prefix.clone(), | |
| }); | |
| } | |
| decisions.push(ReviewDecision::Abort); | |
| decisions | |
| } | |
| } | |
| pub enum ElicitationRequest { | |
| UserVerification { | |
| title: String, | |
| description: String, | |
| challenge: String, | |
| }, | |
| Form { | |
| meta: Option<JsonValue>, | |
| message: String, | |
| requested_schema: JsonValue, | |
| }, | |
| OpenAiForm { | |
| meta: Option<JsonValue>, | |
| message: String, | |
| requested_schema: JsonValue, | |
| }, | |
| OpenAiElicitationForm { | |
| meta: Option<JsonValue>, | |
| message: String, | |
| requested_schema: JsonValue, | |
| }, | |
| Url { | |
| meta: Option<JsonValue>, | |
| message: String, | |
| url: String, | |
| elicitation_id: String, | |
| }, | |
| } | |
| pub struct ElicitationRequestEvent { | |
| /// Turn ID that this elicitation belongs to, when known. | |
| pub turn_id: Option<String>, | |
| pub server_name: String, | |
| pub id: RequestId, | |
| pub request: ElicitationRequest, | |
| } | |
| pub enum ElicitationAction { | |
| Accept, | |
| Decline, | |
| Cancel, | |
| } | |
| pub struct ApplyPatchApprovalRequestEvent { | |
| /// Responses API call id for the associated patch apply call, if available. | |
| pub call_id: String, | |
| /// Turn ID that this patch belongs to. | |
| /// Uses `#[serde(default)]` for backwards compatibility with older senders. | |
| pub turn_id: String, | |
| pub started_at_ms: i64, | |
| pub changes: HashMap<PathBuf, FileChange>, | |
| /// Optional explanatory reason (e.g. request for extra write access). | |
| pub reason: Option<String>, | |
| /// When set, the agent is asking the user to allow writes under this root for the remainder of the session. | |
| pub grant_root: Option<PathBuf>, | |
| } | |
| mod tests { | |
| use super::*; | |
| use codex_utils_absolute_path::test_support::PathBufExt; | |
| use codex_utils_absolute_path::test_support::test_path_buf; | |
| use pretty_assertions::assert_eq; | |
| fn guardian_assessment_action_deserializes_command_shape() { | |
| let action: GuardianAssessmentAction = serde_json::from_value(serde_json::json!({ | |
| "type": "command", | |
| "source": "shell", | |
| "command": "rm -rf /tmp/guardian", | |
| "cwd": test_path_buf("/tmp"), | |
| })) | |
| .expect("guardian action"); | |
| assert_eq!( | |
| action, | |
| GuardianAssessmentAction::Command { | |
| source: GuardianCommandSource::Shell, | |
| command: "rm -rf /tmp/guardian".to_string(), | |
| cwd: test_path_buf("/tmp").abs().into(), | |
| } | |
| ); | |
| } | |
| fn guardian_assessment_action_round_trips_execve_shape() { | |
| let value = serde_json::json!({ | |
| "type": "execve", | |
| "source": "shell", | |
| "program": "/bin/rm", | |
| "argv": ["/usr/bin/rm", "-f", "/tmp/file.sqlite"], | |
| "cwd": "/tmp", | |
| }); | |
| let action: GuardianAssessmentAction = | |
| serde_json::from_value(value.clone()).expect("guardian action"); | |
| assert_eq!( | |
| serde_json::to_value(&action).expect("serialize guardian action"), | |
| value | |
| ); | |
| assert_eq!( | |
| action, | |
| GuardianAssessmentAction::Execve { | |
| source: GuardianCommandSource::Shell, | |
| program: "/bin/rm".to_string(), | |
| argv: vec![ | |
| "/usr/bin/rm".to_string(), | |
| "-f".to_string(), | |
| "/tmp/file.sqlite".to_string(), | |
| ], | |
| cwd: test_path_buf("/tmp").abs(), | |
| } | |
| ); | |
| } | |
| } | |