Files
codex/prs/bolinfest/study/PR-1705-study.md
2025-09-02 15:17:45 -07:00

111 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
**PR #1705 — Apply Patch via Sandbox: DOs and DONTs**
**DOs**
- Bold: Route auto-approved patches through sandbox: Use `InternalApplyPatchInvocation::DelegateToExec` when `SafetyCheck::AutoApprove` is returned so writes happen under the configured sandbox.
- Bold: Keep programmatic path for explicit approvals: If the user explicitly approves, return `InternalApplyPatchInvocation::Output` from `core::apply_patch` and apply in-process.
- Bold: Use a single canonical arg: Reference `codex_core::CODEX_APPLY_PATCH_ARG1` instead of hardcoding `"--codex-run-as-apply-patch"`.
- Bold: Execute via codex binary: Build `ExecParams` with `std::env::current_exe()` + `{CODEX_APPLY_PATCH_ARG1, patch}` and the actions `cwd`.
- Bold: Assess as untrusted: For delegated `apply_patch`, call `assess_safety_for_untrusted_command` to select sandboxing independent of prior approvals.
- Bold: Show sanitized command: Emit `ExecCommandBegin` with `command_for_display = ["apply_patch", patch]` and the effective `cwd`.
- Bold: Handle non-UTF8/lookup failures gracefully: On failure to derive codex path, return a non-panic error response.
- Bold: Preserve raw patch and hunks: Make parsing return `ApplyPatchArgs { patch, hunks }`; pass `patch` (sans heredoc wrapper) to the exec invocation; use `hunks` for verification and previews.
- Bold: Inline variables in `format!`: Embed variables directly inside `{}` in strings.
- Bold: Update tests and call sites: Access `parse_patch(...).unwrap().hunks` in tests and logic.
```rust
// Detect apply_patch and decide programmatic vs sandboxed execution
let action_for_exec = match maybe_parse_apply_patch_verified(&params.command, &params.cwd) {
MaybeApplyPatchVerified::Body(changes) => match apply_patch::apply_patch(sess, &sub_id, &call_id, changes).await {
InternalApplyPatchInvocation::Output(item) => return item, // explicit approval → in-process
InternalApplyPatchInvocation::DelegateToExec(action) => Some(action) // auto-approved → sandboxed exec
},
MaybeApplyPatchVerified::CorrectnessError(e) => {
return ResponseInputItem::FunctionCallOutput {
call_id,
output: FunctionCallOutputPayload { content: format!("error: {e:#}"), success: None },
};
}
_ => None,
};
// Build ExecParams for delegated apply_patch
let (params, safety, command_for_display) = match action_for_exec {
Some(ApplyPatchAction { patch, cwd, .. }) => {
let path_to_codex = std::env::current_exe()
.ok()
.map(|p| p.to_string_lossy().to_string());
let Some(path_to_codex) = path_to_codex else {
return ResponseInputItem::FunctionCallOutput {
call_id,
output: FunctionCallOutputPayload {
content: "failed to determine path to codex executable".to_string(),
success: None,
},
};
};
let params = ExecParams {
command: vec![path_to_codex, CODEX_APPLY_PATCH_ARG1.to_string(), patch.clone()],
cwd,
timeout_ms: params.timeout_ms,
env: std::collections::HashMap::new(),
};
let safety = assess_safety_for_untrusted_command(sess.approval_policy, &sess.sandbox_policy);
(params, safety, vec!["apply_patch".to_string(), patch])
}
None => {
let safety = {
let state = sess.state.lock().unwrap();
assess_command_safety(&params.command, sess.approval_policy, &sess.sandbox_policy, &state.approved_commands)
};
let command_for_display = params.command.clone();
(params, safety, command_for_display)
}
};
// Emit sanitized begin event
sess.notify_exec_command_begin(&sub_id, &call_id, command_for_display.clone(), &params.cwd).await;
```
```rust
// arg0 dispatch uses constant
use codex_core::CODEX_APPLY_PATCH_ARG1;
if argv1 == CODEX_APPLY_PATCH_ARG1 {
// ... run apply_patch subcommand ...
}
```
```rust
// Parser returns both raw patch and hunks
let parsed = parse_patch(&patch_text)?;
let hunks = parsed.hunks; // for verification and previews
let patch = parsed.patch; // clean text (no heredoc wrapper) for exec
```
```rust
// Keep format! inlined variables when constructing minimal patches
let patch = format!(
r#"*** Begin Patch
*** Update File: {filename}
@@
+ {content}
*** End Patch"#,
);
```
**DONTs**
- Bold: Dont apply auto-approved patches in-process: Avoid trusting path checks alone; symlinks/hard links can bypass them—always sandbox.
- Bold: Dont hardcode special flags: Never write `"--codex-run-as-apply-patch"` inline; use `CODEX_APPLY_PATCH_ARG1`.
- Bold: Dont panic on path issues: Non-UTF8 or lookup failures for the codex binary must not crash; return a structured error.
- Bold: Dont leak host binary paths in events: Use `["apply_patch", patch]`, not the codex binary path, in `ExecCommandBegin`.
- Bold: Dont pass heredoc wrappers to exec: Strip to the canonical `patch` string and pass only that.
- Bold: Dont drop `cwd`: Ensure the delegated exec uses the actions `cwd` (where relative paths were resolved).
- Bold: Dont bypass safety plumbing: Keep `(params, safety, command_for_display)` flow consistent with normal exec and reuse error handling paths.
```rust
// Bad: in-process write on auto-approve (vulnerable to hard-link exploits)
// let _ = apply_patch_in_process(action);
// Good: delegate auto-approved patches to sandboxed exec instead
// InternalApplyPatchInvocation::DelegateToExec(action)
```