mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
## Why Local `apply_patch` permission checks can treat a macOS system alias and its canonical spelling as different locations, sending writes covered by temporary-directory grants for unnecessary approval. ## What changed - Normalize trusted top-level aliases in local policy roots and patch targets, reusing a prepared matcher for permission requests and patch safety checks. - Share alias normalization with Seatbelt and the macOS executor sandbox while preserving mutable symlinks and missing descendants. - Preserve read-only and deny precedence, protected metadata restrictions, and remote URI matching. Propagate normalization failures from fallible permission checks. ## Testing Add macOS regressions for multi-file patches with missing parent directories through both direct `apply_patch` calls and shell interception, asserting automatic approval and file contents. Add unit coverage for alias restriction precedence, mutable symlink preservation, missing descendants, and remote permission isolation. GitOrigin-RevId: 0822ff951eaab728cb1ad91281745fa1b8809f15
95 lines
3.4 KiB
Rust
95 lines
3.4 KiB
Rust
use crate::function_tool::FunctionCallError;
|
|
use crate::safety::PatchPolicyMatcher;
|
|
use crate::safety::SafetyCheck;
|
|
use crate::safety::assess_patch_safety;
|
|
use crate::session::step_context::StepContext;
|
|
use crate::session::turn_context::TurnEnvironment;
|
|
use crate::tools::sandboxing::ExecApprovalRequirement;
|
|
use codex_apply_patch::ApplyPatchAction;
|
|
use codex_apply_patch::ApplyPatchFileChange;
|
|
use codex_protocol::protocol::FileChange;
|
|
use codex_utils_path_uri::PathUri;
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct ApplyPatchRuntimeInvocation {
|
|
pub(crate) action: ApplyPatchAction,
|
|
pub(crate) auto_approved: bool,
|
|
pub(crate) exec_approval_requirement: ExecApprovalRequirement,
|
|
}
|
|
|
|
pub(crate) fn prepare_apply_patch(
|
|
step_context: &StepContext,
|
|
turn_environment: &TurnEnvironment,
|
|
matching: &PatchPolicyMatcher<'_>,
|
|
action: ApplyPatchAction,
|
|
) -> Result<ApplyPatchRuntimeInvocation, FunctionCallError> {
|
|
match assess_patch_safety(
|
|
&action,
|
|
step_context.settings.approval_policy(),
|
|
turn_environment.permission_profile(),
|
|
matching,
|
|
)
|
|
.map_err(|error| {
|
|
FunctionCallError::RespondToModel(format!("failed to check patch permissions: {error}"))
|
|
})? {
|
|
SafetyCheck::AutoApprove => Ok(ApplyPatchRuntimeInvocation {
|
|
action,
|
|
auto_approved: true,
|
|
exec_approval_requirement: ExecApprovalRequirement::Skip {
|
|
bypass_sandbox: false,
|
|
proposed_execpolicy_amendment: None,
|
|
},
|
|
}),
|
|
SafetyCheck::AskUser => {
|
|
// Delegate the approval prompt (including cached approvals) to the
|
|
// tool runtime, consistent with how shell/unified_exec approvals
|
|
// are orchestrator-driven.
|
|
Ok(ApplyPatchRuntimeInvocation {
|
|
action,
|
|
auto_approved: false,
|
|
exec_approval_requirement: ExecApprovalRequirement::NeedsApproval {
|
|
reason: None,
|
|
proposed_execpolicy_amendment: None,
|
|
},
|
|
})
|
|
}
|
|
SafetyCheck::Reject { reason } => Err(FunctionCallError::RespondToModel(format!(
|
|
"patch rejected: {reason}"
|
|
))),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn convert_apply_patch_to_protocol(
|
|
action: &ApplyPatchAction,
|
|
) -> HashMap<PathBuf, FileChange> {
|
|
let mut result = HashMap::with_capacity(action.changes().len());
|
|
for (path, change) in action.changes() {
|
|
let protocol_change = match change {
|
|
ApplyPatchFileChange::Add { content, .. } => FileChange::Add {
|
|
content: content.clone(),
|
|
},
|
|
ApplyPatchFileChange::Delete { content } => FileChange::Delete {
|
|
content: content.clone(),
|
|
},
|
|
ApplyPatchFileChange::Update {
|
|
unified_diff,
|
|
move_path,
|
|
new_content: _new_content,
|
|
} => FileChange::Update {
|
|
unified_diff: unified_diff.clone(),
|
|
move_path: move_path.as_ref().map(PathUri::to_path_buf),
|
|
},
|
|
};
|
|
// TODO(anp): Carry PathUri through patch protocol events once app-server and rollout
|
|
// compatibility no longer require path-flavored strings.
|
|
result.insert(path.to_path_buf(), protocol_change);
|
|
}
|
|
result
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "apply_patch_tests.rs"]
|
|
mod tests;
|