fix(permissions): native path deny/ask rules apply to the file the tool opens

Edit/Read/Grep rules matched only the raw argument string, so a workspace
symlink (or a quoted, `~`, `./`, `..`, or absolute spelling) reached a
denied file. The policy now resolves the path exactly as the tools do
(sanitize, expand `~`, display-cwd remap, join to the requesting session's
cwd), follows symlinks parent-first, and re-checks deny/ask rules on the
opened path and its target as absolute, `./`-relative and relative
spellings. Escalation only: no allow is granted through a link. A link
that cannot be followed forces a prompt that survives YOLO, auto mode and
the session edit grant, with event reason `unresolved_link`.

Permission requests now carry the requesting tool's cwd and display cwd
so a child worktree session sharing its parent's handle is checked where
it runs.

Ported from upstream 1.0.14 (grok-build permission/policy.rs symlink
re-check), reshaped for Kigi's lexical rule matcher.

Claude-Session: https://claude.ai/code/session_01RUqV8HVGXcAETyYbZ2tPQ4
This commit is contained in:
雷电芽衣 committed 2026-09-13 05:48:06 -04:00
1 parent fb3fe89d33
commit 5f7416f3ef
8 files changed
+995 -34

No files matched your search

@@ -957,6 +957,8 @@ impl SessionActor {
Some(self.session_info.id.0.to_string()),
None,
None,
self.tool_context.cwd.clone(),
self.display_cwd.get().map(std::path::PathBuf::from),
)
.await
};
@@ -82,6 +82,8 @@ async fn set_auto_mode_path_wires_live_side_query_via_session_actor() {
None,
None,
None,
session.tool_context.cwd.clone(),
None,
)
.await;
// cargo is heuristic-allow when sampling fails; must not be Prompt-only
@@ -99,6 +101,8 @@ async fn set_auto_mode_path_wires_live_side_query_via_session_actor() {
None,
None,
None,
session.tool_context.cwd.clone(),
None,
)
.await;
assert!(