mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Require explicit sandbox permissions with shell justifications (#36350)
## What changed Reject `shell_command` and unified `exec_command` calls that provide a `justification` without also specifying `sandbox_permissions`. The model-visible error tells callers to explicitly request `require_escalated` execution or omit the justification. ## Testing - Add unit coverage for `shell_command` argument validation. - Add integration coverage for both shell tool paths, including verification that rejected unified exec calls do not begin execution. GitOrigin-RevId: 6af39d2c889af492440615eae12085b99a8b5169
This commit is contained in:
committed by
copyberry
parent
3d1d26915a
commit
7b38c48da9
@@ -89,6 +89,19 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_sandbox_permissions(
|
||||
sandbox_permissions: Option<SandboxPermissions>,
|
||||
justification: Option<&str>,
|
||||
) -> Result<SandboxPermissions, FunctionCallError> {
|
||||
if justification.is_some() && sandbox_permissions.is_none() {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"`justification` requires an explicit `sandbox_permissions`; use `sandbox_permissions: \"require_escalated\"` for unsandboxed execution, or omit `justification`.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(sandbox_permissions.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn updated_hook_command(updated_input: &Value) -> Result<&str, FunctionCallError> {
|
||||
updated_input
|
||||
.get("command")
|
||||
|
||||
@@ -16,6 +16,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::context::boxed_tool_output;
|
||||
use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::handlers::resolve_sandbox_permissions;
|
||||
use crate::tools::handlers::resolve_workdir_base_path;
|
||||
use crate::tools::handlers::rewrite_function_string_argument;
|
||||
use crate::tools::handlers::updated_hook_command;
|
||||
@@ -106,6 +107,10 @@ impl ShellCommandHandler {
|
||||
);
|
||||
let active_permission_profile = turn_context.config.permissions.active_permission_profile();
|
||||
inject_permission_profile_env(&mut env, active_permission_profile.as_ref());
|
||||
let sandbox_permissions = resolve_sandbox_permissions(
|
||||
params.sandbox_permissions,
|
||||
params.justification.as_deref(),
|
||||
)?;
|
||||
|
||||
Ok(ExecParams {
|
||||
command,
|
||||
@@ -115,7 +120,7 @@ impl ShellCommandHandler {
|
||||
env,
|
||||
network: turn_context.network.clone(),
|
||||
network_environment_id: Some(turn_environment.environment_id.clone()),
|
||||
sandbox_permissions: params.sandbox_permissions.unwrap_or_default(),
|
||||
sandbox_permissions,
|
||||
windows_sandbox_level: turn_context.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: turn_context
|
||||
.config
|
||||
|
||||
@@ -229,6 +229,45 @@ async fn shell_command_handler_defaults_to_non_login_when_disallowed() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shell_command_handler_rejects_justification_without_sandbox_permissions() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
let turn_environment = turn_context
|
||||
.environments
|
||||
.primary()
|
||||
.expect("primary environment");
|
||||
let cwd = turn_environment
|
||||
.cwd()
|
||||
.to_abs_path()
|
||||
.expect("native environment cwd");
|
||||
let params = ShellCommandToolCallParams {
|
||||
command: "echo hello".to_string(),
|
||||
workdir: None,
|
||||
login: None,
|
||||
timeout_ms: None,
|
||||
sandbox_permissions: None,
|
||||
additional_permissions: None,
|
||||
prefix_rule: None,
|
||||
justification: Some("Allow this command".to_string()),
|
||||
};
|
||||
|
||||
let err = ShellCommandHandler::to_exec_params(
|
||||
¶ms,
|
||||
&session,
|
||||
&turn_context,
|
||||
turn_environment,
|
||||
cwd,
|
||||
/*allow_login_shell*/ false,
|
||||
)
|
||||
.expect_err("justification without sandbox permissions should be rejected");
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("`justification` requires an explicit `sandbox_permissions`"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_command_handler_rejects_login_when_disallowed() {
|
||||
let err =
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) struct ExecCommandArgs {
|
||||
#[serde(default)]
|
||||
max_output_tokens: Option<usize>,
|
||||
#[serde(default)]
|
||||
sandbox_permissions: SandboxPermissions,
|
||||
sandbox_permissions: Option<SandboxPermissions>,
|
||||
#[serde(default)]
|
||||
additional_permissions: Option<AdditionalPermissionProfile>,
|
||||
#[serde(default)]
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::tools::handlers::implicit_granted_permissions;
|
||||
use crate::tools::handlers::normalize_and_validate_additional_permissions;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::handlers::resolve_sandbox_permissions;
|
||||
use crate::tools::handlers::resolve_tool_environment;
|
||||
use crate::tools::handlers::rewrite_function_string_argument;
|
||||
use crate::tools::handlers::updated_hook_command;
|
||||
@@ -188,6 +189,8 @@ impl ExecCommandHandler {
|
||||
parse_arguments(&arguments)?
|
||||
}
|
||||
};
|
||||
let sandbox_permissions =
|
||||
resolve_sandbox_permissions(args.sandbox_permissions, args.justification.as_deref())?;
|
||||
let hook_command = args.cmd.clone();
|
||||
// TODO(anp) wire PathUri through implicit skills instead of skipping on foreign paths
|
||||
if let Some(native_cwd) = native_cwd.as_ref() {
|
||||
@@ -243,7 +246,7 @@ impl ExecCommandHandler {
|
||||
tty,
|
||||
yield_time_ms,
|
||||
max_output_tokens,
|
||||
sandbox_permissions,
|
||||
sandbox_permissions: _,
|
||||
additional_permissions,
|
||||
justification,
|
||||
prefix_rule,
|
||||
|
||||
Reference in New Issue
Block a user