Follow-up hardening on top of #57972 (granular sandbox write permissions), based on a review of that branch. The most important fix is that model-requested `fs_write_paths` were joined but never normalized, so a path containing `..` could pass the lexical subtree-containment checks while seatbelt canonicalized it to somewhere else entirely — causing skipped approval prompts and silent runtime write denials. Write paths are now lexically normalized (via `util::paths::normalize_lexically`) at the point they enter the system, both for model requests and for hand-edited persistent grants, so the containment check, the approval prompt, and the enforced sandbox policy all operate on the same path. The subtree insert/prune and containment logic had drifted into roughly five near-identical copies across `agent`, `agent_settings`, and `settings_content`; these now share `util::paths::insert_subtree` and `path_within_subtree`. The dead `SandboxPermissions::covers` (only ever exercised by its own tests, duplicating the production `covers_with_persistent`) is removed, and its tests rewritten to cover the real settings-compilation path including `..` normalization. The remaining changes reduce brittleness: the sandbox permission option ids (`allow`, `allow_thread`, `allow_always`, `deny`) are now shared constants in `acp_thread` instead of bare string literals scattered across the agent and UI, `persist_sandbox_always_permission` logs instead of silently doing nothing when no filesystem is available, and a comment documents why replay always resolves the terminal tool to the non-sandboxed variant. Release Notes: - N/A --------- Co-authored-by: Martin Ye <martin@zed.dev> Co-authored-by: MartinYe1234 <52641447+MartinYe1234@users.noreply.github.com>
9 files changedLine totals unavailable