Skip to content

Add file policy engine for file tool harness#1585

Closed
kunpeng-ai-lab wants to merge 1 commit into
Hmbown:mainfrom
kunpeng-ai-lab:feature/file-policy-engine
Closed

Add file policy engine for file tool harness#1585
kunpeng-ai-lab wants to merge 1 commit into
Hmbown:mainfrom
kunpeng-ai-lab:feature/file-policy-engine

Conversation

@kunpeng-ai-lab
Copy link
Copy Markdown

Adds the file policy engine on top of current main/v0.8.33. Ports the harness-layer file access policy and wires it into serial tool execution, parallel tool batches, and execute_tool_with_lock. Validation: cargo check --workspace; cargo test -p deepseek-tui --bin deepseek-tui file_policy; cargo test -p deepseek-tui --bin deepseek-tui engine::; cargo test -p deepseek-tui --bin deepseek-tui composer_arrows_scroll.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a file access policy engine that allows restricting tool access to specific file paths using glob-style patterns in execpolicy.toml. The implementation includes integration into the tool execution lifecycle, comprehensive tests, and a new feature flag. Feedback from the review highlights a potential security bypass where the apply_patch tool lacks path extraction, performance overhead from repeated regex compilation, and redundant logic when handling default rule sets. Additionally, it was noted that audit events are missing in some enforcement points, which reduces visibility into policy violations.

Comment on lines +141 to +156
pub fn is_file_tool(tool_name: &str) -> bool {
matches!(
tool_name,
"read_file" | "write_file" | "edit_file" | "apply_patch"
)
}

pub fn extract_file_path<'a>(
tool_input: &'a serde_json::Value,
tool_name: &str,
) -> Option<&'a str> {
match tool_name {
"read_file" | "write_file" | "edit_file" => tool_input.get("path")?.as_str(),
_ => None,
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The apply_patch tool is included in is_file_tool but its path extraction is not implemented in extract_file_path. This creates a security bypass where a model can use apply_patch to modify files that are explicitly denied via write_file or edit_file rules. While the design doc notes this limitation, it significantly weakens the effectiveness of the file policy engine, especially since apply_patch is a common way for models to modify code.

Comment on lines +124 to +139
fn pattern_matches_path(pattern: &str, path: &str) -> bool {
if pattern == "*" {
return true;
}

let mut escaped = regex::escape(pattern);
escaped = escaped.replace("\\*\\*/", "(?:.*/)?");
escaped = escaped.replace("\\*\\*", ".*");
escaped = escaped.replace("\\*", "[^/\\\\]*");

let re_str = format!("^{escaped}$");
let Ok(re) = regex::Regex::new(&re_str) else {
return false;
};
re.is_match(path)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The pattern_matches_path function compiles a new regex::Regex on every call. Since this function is invoked within nested loops in evaluate_file for every file tool execution, this can lead to significant performance overhead. Consider pre-compiling these patterns when the configuration is loaded or using a lazy caching mechanism to store compiled regexes.

Comment thread crates/tui/src/execpolicy/rules.rs Outdated
Comment on lines +85 to +90
let rule_sets: Vec<&FileRuleSet> = self
.file_rules
.get(category)
.into_iter()
.chain(self.file_rules.get("default"))
.collect();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When category is "default", this logic will include the default rule set twice in the rule_sets vector, leading to redundant pattern matching. While not a bug, it's slightly inefficient.

        let mut rule_sets = Vec::new();
        if let Some(rules) = self.file_rules.get(category) {
            rule_sets.push(rules);
        }
        if category != "default" {
            if let Some(rules) = self.file_rules.get("default") {
                rule_sets.push(rules);
            }
        }

Comment on lines +283 to +287
if let Some(policy) = file_policy.as_ref()
&& let Some(err) = Engine::evaluate_file_policy(policy, &tool_name, &tool_input)
{
return Err(err);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This defensive check in execute_tool_with_lock does not emit a tool.file_policy_denied audit event when a call is blocked. This is inconsistent with the enforcement points in turn_loop.rs and reduces visibility into policy violations, especially for sub-calls within parallel tool batches (e.g., multi_tool_use.parallel) which rely on this guard for enforcement.

@kunpeng-ai-lab kunpeng-ai-lab force-pushed the feature/file-policy-engine branch from 749117c to 7501651 Compare May 14, 2026 02:14
@kunpeng-ai-lab
Copy link
Copy Markdown
Author

Updated this PR on top of upstream main/v0.8.35 and addressed the bot review items:

  • Added apply_patch path extraction from path override and unified diff headers, so denied patch targets are blocked.
  • Avoid default file rule duplication when category is default.
  • Added a small regex cache for path glob matching to avoid recompiling patterns on every policy check.
  • Added tool.file_policy_denied audit emission in execute_tool_with_lock defensive enforcement.
  • Added tests for apply_patch denial and patch path extraction.

Validation after update:

  • cargo check --workspace
  • cargo test -p deepseek-tui --bin deepseek-tui file_policy (15 passed)
  • cargo test -p deepseek-tui --bin deepseek-tui engine:: (112 passed)
  • cargo test -p deepseek-tui --bin deepseek-tui composer_arrows_scroll (6 passed)

@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented May 23, 2026

This PR was opened before the v0.8.41 rebrand and is now stale. Feel free to rebase onto current main and reopen. 鲸鱼兄弟们等你 🐋

@kunpeng-ai-lab
Copy link
Copy Markdown
Author

Rebased onto current main and reopened as #1974: #1974

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants