DeepSeek TUI Exec Policy Engine: How Tool Execution Decisions Are Made
The DeepSeek TUI Exec Policy engine evaluates shell commands against user-defined allow/deny rules in three stages—loading the policy, normalizing the command, and pattern matching—to return an Allow, Deny, or AskUser decision before any external tool executes.
The DeepSeek TUI employs a stateless Exec Policy engine to prevent accidental execution of dangerous commands. According to the Hmbown/DeepSeek-TUI source code, this gatekeeper intercepts every tool invocation—from shell commands to Git hooks—and validates them against configurable execpolicy.toml rules stored in ~/.deepseek/execpolicy.toml.
How the Exec Policy Engine Works
The engine operates as a pure function that transforms a command string into an ExecPolicyDecision. It processes each request through three distinct phases implemented across the execpolicy crate.
Loading the Policy Configuration
The process begins in crates/tui/src/execpolicy/rules.rs, where the load_default_policy function reads the TOML configuration and returns an ExecPolicyConfig struct. This structure holds a map of rule groups (e.g., [git], [danger]) to their associated allow and deny pattern vectors.
// From rules.rs lines 67-78
pub fn load_default_policy() -> Result<Option<ExecPolicyConfig>, ExecPolicyError> {
// Checks ~/.deepseek/execpolicy.toml by default
// Returns Ok(None) if file doesn't exist, Ok(Some(cfg)) if parsed
}
Normalizing Commands for Comparison
Before pattern matching occurs, the engine normalizes the raw command string to eliminate superficial differences. The normalize_command function in crates/tui/src/execpolicy/matcher.rs (lines 5-23) performs three operations:
- Strips heredoc bodies via
strip_heredoc_bodies(lines 25-99) to remove content between<<EOFmarkers - Tokenizes the command using shlex to handle quoted strings correctly
- Collapses whitespace to ensure
cat > file.txtmatchescat > file.txt
This guarantees that a rule pattern like cat > file.txt successfully matches a heredoc invocation such as cat <<EOF > file.txt … EOF.
Pattern Matching and Decision Logic
The evaluate method in rules.rs (lines 43-63) implements the decision hierarchy:
- Check deny patterns first—if any deny wildcard matches, return
ExecPolicyDecision::Deny(msg) - Check allow patterns—if any allow wildcard matches, return
ExecPolicyDecision::Allow - Fallback to AskUser—if no rules match, return
ExecPolicyDecision::AskUser(msg)to trigger a UI confirmation dialog
The pattern_matches function in matcher.rs (lines 2-18) converts glob wildcards (*) to .* regex fragments for flexible matching against normalized commands.
Configuring Execution Policies
Users define rules in TOML rule groups. The engine evaluates deny lists before allow lists within each group.
# ~/.deepseek/execpolicy.toml
[git]
allow = ["git status", "git log *"]
deny = ["git push --force"]
[danger]
allow = []
deny = ["rm -rf /"]
Programmatic Usage Examples
You can invoke the policy engine directly from Rust code to evaluate commands before execution.
use deepseek_execpolicy::{ExecPolicyEngine, ExecPolicyDecision};
fn decide(command: &str) -> ExecPolicyDecision {
// Load the default policy (if any)
let policy = deepseek_execpolicy::load_default_policy()
.expect("policy load failed")
.unwrap_or_default();
// Evaluate the command against the loaded policy
policy.evaluate(command)
}
// Example usage
match decide("git push --force") {
ExecPolicyDecision::Allow => println!("✅ allowed"),
ExecPolicyDecision::Deny(msg) => println!("⛔ denied: {}", msg),
ExecPolicyDecision::AskUser(msg) => println!("❓ ask user: {}", msg),
}
Integration with TUI Tools
The built-in shell tool demonstrates real-time integration. In crates/tui/src/tools/shell.rs, the tool constructs an ExecPolicyCheckCommand and evaluates the command before spawning the process:
// Simplified from shell.rs around line 1554
let decision = execpolicy::load_default_policy()?
.map(|cfg| cfg.evaluate(&command))
.unwrap_or(ExecPolicyDecision::AskUser(
"no execpolicy loaded".into(),
));
match decision {
ExecPolicyDecision::Allow => run_the_command(),
ExecPolicyDecision::Deny(reason) => return Err(ToolError::Denied(reason)),
ExecPolicyDecision::AskUser(prompt) => {
// Store decision for UI rendering and user confirmation
execpolicy_decision = Some(decision.clone());
}
}
The ExecPolicyCheckCommand struct in execpolicycheck.rs provides the CLI-style interface that bridges the engine with the TUI event loop.
Summary
- Three-stage pipeline: The engine loads TOML rules from
~/.deepseek/execpolicy.toml, normalizes commands viamatcher.rs, and evaluates them throughrules.rs::evaluate. - Deny-first logic: Patterns in deny lists take precedence over allow lists; unmatched commands trigger
AskUserprompts rather than defaulting to allow. - Stateless design: The engine can be invoked repeatedly with different configurations, making it safe for sub-agents and session compaction.
- Heredoc awareness: The
strip_heredoc_bodiesfunction ensures that verbose shell heredocs match simple rule patterns after normalization.
Frequently Asked Questions
Where does DeepSeek TUI store the default execution policy file?
By default, the engine looks for ~/.deepseek/execpolicy.toml. The load_default_policy function in crates/tui/src/execpolicy/rules.rs handles this path resolution, returning Ok(None) if the file does not exist rather than failing.
How does the Exec Policy engine handle shell heredocs in commands?
The normalize_command function in crates/tui/src/execpolicy/matcher.rs strips heredoc bodies via strip_heredoc_bodies (lines 25-99), tokenizes the remaining command with shlex, and collapses whitespace. This ensures that cat <<EOF > file.txt … EOF normalizes to match a rule like cat > file.txt.
What happens if a command doesn't match any allow or deny rules?
When no patterns match, the evaluate method returns ExecPolicyDecision::AskUser(msg), prompting the TUI to display a confirmation dialog. This deny-by-default approach prevents accidental execution of unvetted commands.
Can the policy engine be used outside the TUI for other Rust applications?
Yes. The crates/execpolicy/src/lib.rs re-exports the public API including ExecPolicyEngine, ExecPolicyDecision, and helper functions. The engine is deliberately stateless and has no TUI dependencies, allowing integration into any Rust project requiring command authorization.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →