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:

  1. Strips heredoc bodies via strip_heredoc_bodies (lines 25-99) to remove content between <<EOF markers
  2. Tokenizes the command using shlex to handle quoted strings correctly
  3. Collapses whitespace to ensure cat > file.txt matches cat > 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:

  1. Check deny patterns first—if any deny wildcard matches, return ExecPolicyDecision::Deny(msg)
  2. Check allow patterns—if any allow wildcard matches, return ExecPolicyDecision::Allow
  3. 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 via matcher.rs, and evaluates them through rules.rs::evaluate.
  • Deny-first logic: Patterns in deny lists take precedence over allow lists; unmatched commands trigger AskUser prompts 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_bodies function 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →