Reshape permission request hook payload

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Abhinav Vedmala
2026-04-13 13:45:41 -07:00
parent 37e9f255ed
commit 8e7a23c48c
5 changed files with 137 additions and 57 deletions

View File

@@ -448,7 +448,7 @@ async fn execve_permission_request_hook_short_circuits_prompt() -> anyhow::Resul
expected_hook_command
);
assert_eq!(
hook_inputs[0]["approval_context"]["approval_attempt"],
hook_inputs[0]["approval_context"]["attempt"]["stage"],
"initial"
);

View File

@@ -1174,12 +1174,16 @@ async fn permission_request_hook_allows_shell_command_without_user_approval() ->
assert_eq!(
hook_inputs[0]["approval_context"],
serde_json::json!({
"sandbox_permissions": "use_default",
"additional_permissions": null,
"attempt": {
"stage": "initial",
"retryReason": null,
},
"policy": {
"sandboxPermissions": "use_default",
"additionalPermissions": null,
},
"justification": null,
"approval_attempt": "initial",
"retry_reason": null,
"network_approval_context": null,
"resource": {},
})
);
assert!(
@@ -1277,12 +1281,16 @@ async fn permission_request_hook_sees_raw_exec_command_input() -> Result<()> {
assert_eq!(
hook_inputs[0]["approval_context"],
serde_json::json!({
"sandbox_permissions": "use_default",
"additional_permissions": null,
"attempt": {
"stage": "initial",
"retryReason": null,
},
"policy": {
"sandboxPermissions": "use_default",
"additionalPermissions": null,
},
"justification": null,
"approval_attempt": "initial",
"retry_reason": null,
"network_approval_context": null,
"resource": {},
})
);
@@ -1355,8 +1363,7 @@ allow_local_binding = true
.enable(Feature::CodexHooks)
.expect("test config should allow feature update");
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config.permissions.sandbox_policy =
Constrained::allow_any(sandbox_policy_for_config.clone());
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
let layers = config
.config_layer_stack
.get_layers(
@@ -1444,10 +1451,21 @@ allow_local_binding = true
"network-access http://codex-network-test.invalid:80"
);
assert_eq!(
hook_inputs[0]["approval_context"]["network_approval_context"],
hook_inputs[0]["approval_context"],
serde_json::json!({
"host": "codex-network-test.invalid",
"protocol": "http",
"attempt": {
"stage": "initial",
"retryReason": null,
},
"policy": {
"sandboxPermissions": "use_default",
"additionalPermissions": null,
},
"justification": "codex-network-test.invalid is not in the allowed_domains",
"resource": {
"host": "codex-network-test.invalid",
"protocol": "http",
},
})
);
@@ -1534,12 +1552,16 @@ async fn permission_request_hook_sees_retry_context_after_sandbox_denial() -> Re
assert_eq!(
hook_inputs[0]["approval_context"],
serde_json::json!({
"sandbox_permissions": "use_default",
"additional_permissions": null,
"attempt": {
"stage": "retry",
"retryReason": "command failed; retry without sandbox?",
},
"policy": {
"sandboxPermissions": "use_default",
"additionalPermissions": null,
},
"justification": null,
"approval_attempt": "retry",
"retry_reason": "command failed; retry without sandbox?",
"network_approval_context": null,
"resource": {},
})
);

View File

@@ -23,21 +23,6 @@
},
"type": "object"
},
"NetworkApprovalContext": {
"properties": {
"host": {
"type": "string"
},
"protocol": {
"$ref": "#/definitions/NetworkApprovalProtocol"
}
},
"required": [
"host",
"protocol"
],
"type": "object"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
@@ -82,31 +67,68 @@
"PermissionRequestApprovalContext": {
"additionalProperties": false,
"properties": {
"additional_permissions": {
"$ref": "#/definitions/PermissionProfile"
},
"approval_attempt": {
"$ref": "#/definitions/PermissionRequestApprovalAttempt"
"attempt": {
"$ref": "#/definitions/PermissionRequestAttemptContext"
},
"justification": {
"type": "string"
},
"network_approval_context": {
"$ref": "#/definitions/NetworkApprovalContext"
"policy": {
"$ref": "#/definitions/PermissionRequestPolicyContext"
},
"retry_reason": {
"resource": {
"$ref": "#/definitions/PermissionRequestResourceContext"
}
},
"required": [
"attempt",
"policy",
"resource"
],
"type": "object"
},
"PermissionRequestAttemptContext": {
"additionalProperties": false,
"properties": {
"retryReason": {
"type": "string"
},
"sandbox_permissions": {
"stage": {
"$ref": "#/definitions/PermissionRequestApprovalAttempt"
}
},
"required": [
"stage"
],
"type": "object"
},
"PermissionRequestPolicyContext": {
"additionalProperties": false,
"properties": {
"additionalPermissions": {
"$ref": "#/definitions/PermissionProfile"
},
"sandboxPermissions": {
"$ref": "#/definitions/SandboxPermissions"
}
},
"required": [
"approval_attempt",
"sandbox_permissions"
"sandboxPermissions"
],
"type": "object"
},
"PermissionRequestResourceContext": {
"additionalProperties": false,
"properties": {
"host": {
"type": "string"
},
"protocol": {
"$ref": "#/definitions/NetworkApprovalProtocol"
}
},
"type": "object"
},
"PermissionRequestToolInput": {
"additionalProperties": false,
"properties": {

View File

@@ -36,7 +36,10 @@ use crate::engine::command_runner::CommandRunResult;
use crate::engine::dispatcher;
use crate::engine::output_parser;
use crate::schema::PermissionRequestApprovalContext;
use crate::schema::PermissionRequestAttemptContext;
use crate::schema::PermissionRequestCommandInput;
use crate::schema::PermissionRequestPolicyContext;
use crate::schema::PermissionRequestResourceContext;
use crate::schema::PermissionRequestToolInput;
#[derive(Debug, Clone)]
@@ -195,6 +198,11 @@ fn resolve_permission_request_decision<'a>(
}
fn build_command_input(request: &PermissionRequestRequest) -> PermissionRequestCommandInput {
let (host, protocol) = request
.network_approval_context
.as_ref()
.map(|context| (Some(context.host.clone()), Some(context.protocol)))
.unwrap_or((None, None));
PermissionRequestCommandInput {
session_id: request.session_id.to_string(),
turn_id: request.turn_id.clone(),
@@ -208,12 +216,16 @@ fn build_command_input(request: &PermissionRequestRequest) -> PermissionRequestC
command: request.command.clone(),
},
approval_context: PermissionRequestApprovalContext {
sandbox_permissions: request.sandbox_permissions,
additional_permissions: request.additional_permissions.clone(),
attempt: PermissionRequestAttemptContext {
stage: request.approval_attempt,
retry_reason: request.retry_reason.clone(),
},
policy: PermissionRequestPolicyContext {
sandbox_permissions: request.sandbox_permissions,
additional_permissions: request.additional_permissions.clone(),
},
justification: request.justification.clone(),
approval_attempt: request.approval_attempt,
retry_reason: request.retry_reason.clone(),
network_approval_context: request.network_approval_context.clone(),
resource: PermissionRequestResourceContext { host, protocol },
},
}
}

View File

@@ -12,7 +12,7 @@ use serde_json::Value;
use std::path::Path;
use std::path::PathBuf;
use codex_protocol::approvals::NetworkApprovalContext;
use codex_protocol::approvals::NetworkApprovalProtocol;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxPermissions;
@@ -251,14 +251,38 @@ pub(crate) struct PermissionRequestToolInput {
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct PermissionRequestApprovalContext {
pub(crate) struct PermissionRequestAttemptContext {
pub stage: PermissionRequestApprovalAttempt,
pub retry_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct PermissionRequestPolicyContext {
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct PermissionRequestResourceContext {
#[serde(skip_serializing_if = "Option::is_none")]
pub host: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<NetworkApprovalProtocol>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(crate) struct PermissionRequestApprovalContext {
pub attempt: PermissionRequestAttemptContext,
pub policy: PermissionRequestPolicyContext,
pub justification: Option<String>,
pub approval_attempt: PermissionRequestApprovalAttempt,
pub retry_reason: Option<String>,
pub network_approval_context: Option<NetworkApprovalContext>,
pub resource: PermissionRequestResourceContext,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]