Permission System for Bash Tool Execution and Destructive Command Approval in Reasonix
Reasonix employs a two-layer permission system combining global tool-approval modes (Ask/Auto/Yolo) with fine-grained policy rules to govern bash execution, ensuring destructive commands require explicit user consent while allowing harmless operations to proceed automatically.
The DeepSeek-Reasonix repository implements a robust security model to prevent accidental workspace modifications when executing shell commands. This article examines the permission system for bash tool execution and destructive command approval in Reasonix, detailing how the engine balances automation with safety through runtime modes and configurable policy rules defined in internal/permission/permission.go.
Two-Layer Permission Architecture
Reasonix protects workspace integrity through complementary mechanisms: global runtime modes that establish baseline behavior, and per-tool policy rules that provide granular control over specific command patterns.
Tool-Approval Modes (Ask, Auto, and Yolo)
The system defines three global modes that determine how the bash tool and other workspace-modifying utilities are handled:
- Ask: Every controlled tool call triggers an approval card in the UI. Users may select Allow once, Allow for session, Always allow, or Deny. This mode is ideal for unfamiliar repositories or production environments.
- Auto: Ordinary tool calls execute without interruption unless an explicit
askordenyrule matches, or the tool carries adestructiveHintflag. This supports daily coding workflows while maintaining guards for dangerous operations. - Yolo: Skips ordinary prompts entirely; only explicit
denyrules and sandbox checks remain active. Intended for bulk mechanical edits on disposable branches.
Users select modes via the Desktop composer or keyboard shortcuts (Ctrl+Y / Cmd+Y). These modes operate independently of collaboration modes (Plan/Goal), which control task sequencing rather than permission boundaries.
Fine-Grained Policy Rules
Beneath the global mode, the permission engine evaluates specific command patterns against a Policy struct containing three rule slices: Allow, Ask, and Deny. The core decision logic resides in DecideSubject and Decide methods within internal/permission/permission.go (lines 149–262).
The engine resolves decisions through this priority order:
- Allow rules: If the command matches an explicit allow pattern, execution proceeds immediately.
- Ask rules: If the command matches an ask pattern, the system requires user confirmation.
- Deny rules: If the command matches a deny pattern, execution is blocked.
If no rules match, the default decision is Ask, ensuring any unrecognized writer-type tool pauses for confirmation. The Decision type is defined as:
type Decision int
const (
Allow Decision = iota // default for reads
Ask // conservative fallback
Deny // explicit block
)
Destructive Command Handling
Destructive operations—such as git push, rm -rf, or any command marked with destructiveHint—receive special scrutiny regardless of the global approval mode.
The destructiveHint Flag
According to docs/TOOL_APPROVAL_MODES.md, the destructiveHint attribute in a tool's contract indicates operations that modify external state or destroy data. In Auto mode, the system does not automatically prompt for approval solely because a command is destructive; however, any tool entry carrying this flag still requires an explicit ask/deny rule or fresh user approval before execution.
The built-in bash tool is explicitly marked as not read-only (false in docs/TOOL_CONTRACT.md), automatically subjecting it to the Ask default unless permitted by policy rules.
Command Decomposition and Evaluation
When processing a bash command, Reasonix parses the JSON payload and decomposes the command string into segments (e.g., pipelines, redirects) using logic tested in internal/permission/bash_decompose_test.go. Each segment is evaluated separately against the policy:
// Conceptual flow based on permission.go implementation
func evaluateBashCommand(command string, policy Policy) Decision {
segments := decomposeCommand(command) // bash_decompose logic
for _, seg := range segments {
decision := policy.Decide("bash", false, seg)
if decision == Ask || decision == Deny {
return decision // Most conservative wins
}
}
return Allow
}
If any segment resolves to Ask, the entire call requires approval. If the final decision is Deny, Reasonix returns an error without executing the command, and SessionGuard (in internal/tool/builtin/session_guard.go) surfaces a warning about the guarded state reference.
Implementation Details
Permission Engine Core
The internal/permission/permission.go file implements the authoritative decision engine. The Decide method accepts the tool name, a read-only flag, and the command payload, returning one of the three decision constants. This design allows the system to handle nested tool calls and indirect bash execution consistently.
Key implementation characteristics:
- Rule matching: Pattern matching against command subjects supports exact strings and configurable wildcards.
- Caching: User decisions from approval cards (e.g., Allow for session) are cached according to the selected scope to prevent repetitive prompts.
- Sandbox integration: Even in Yolo mode, sandbox-level restrictions provide a final safety net.
Bash Contract and Read-Only Status
The docs/TOOL_CONTRACT.md file defines the schema for built-in tools. The bash tool explicitly sets readOnly: false, automatically placing it under the permission system's jurisdiction. This contract validation ensures that policy rules in reasonix.toml conform to the tool's capabilities.
Configuration Examples
Setting Approval Modes via CLI
# Require explicit approval for all controlled tools
reasonix mode set ask
# Auto-approve ordinary calls, respect destructiveHint rules
reasonix mode set auto
# Minimal prompts, deny rules only
reasonix mode set yolo
Defining Policy Rules in TOML
Create or modify reasonix.toml in your workspace root:
[permissions]
# Allow harmless status checks without prompting
allow = [
{ tool = "bash", subject = "git status" },
{ tool = "bash", subject = "ls -la" }
]
# Require explicit approval for destructive operations
ask = [
{ tool = "bash", subject = "git push" },
{ tool = "bash", subject = "git reset --hard" }
]
# Block dangerous commands entirely
deny = [
{ tool = "bash", subject = "rm -rf /" },
{ tool = "bash", subject = "mkfs.*" }
]
Programmatic Permission Checks
Developers integrating with Reasonix can invoke the permission engine directly:
package main
import (
"encoding/json"
"github.com/esengine/DeepSeek-Reasonix/internal/permission"
)
func validateCommand(cmd string) (permission.Decision, error) {
// Marshal command to expected JSON payload format
payload, _ := json.Marshal(map[string]string{
"command": cmd,
})
// Load current session policy
pol := permission.LoadCurrentPolicy()
// Evaluate: tool name, readOnly flag, payload
decision := pol.Decide("bash", false, payload)
return decision, nil
}
Summary
- Two-layer protection: Global modes (Ask/Auto/Yolo) establish baseline behavior, while policy rules provide command-specific controls.
- Default Ask posture: Any bash command not explicitly permitted defaults to requiring user approval, preventing accidental execution.
- Destructive command handling: The
destructiveHintflag and explicitdenyrules ensure dangerous operations likegit pushorrm -rfcannot run without consent. - Segmented evaluation: Complex commands are decomposed into segments, with the most restrictive decision applied to the entire operation.
- Source locations: Core logic resides in
internal/permission/permission.go, with contract definitions indocs/TOOL_CONTRACT.mdand mode documentation indocs/TOOL_APPROVAL_MODES.md.
Frequently Asked Questions
What is the default behavior for bash commands in Reasonix?
By default, bash commands require user approval because the tool is marked as non-read-only in the contract. If no explicit allow rule matches a command, the permission engine returns Ask, triggering an approval card in the UI or requiring programmatic confirmation.
How does Auto mode handle destructive commands?
In Auto mode, ordinary tool calls execute without prompting, but commands flagged with destructiveHint or matching an explicit ask rule still require approval. Auto mode does not automatically prompt for destructive commands unless they are explicitly configured in the policy or carry the destructive flag.
Can I configure specific git commands to require approval while allowing others?
Yes. Define granular rules in reasonix.toml under the [permissions] section. For example, place git status in the allow array and git push in the ask array. The permission engine evaluates these rules before applying the global mode, ensuring pushes require confirmation while status checks proceed automatically.
What happens when the permission system blocks a bash command?
When the engine returns Deny, Reasonix immediately returns an error to the calling context without executing the command. The UI surfaces a warning through SessionGuard indicating the command references a guarded state. The user must modify the command or update the policy rules to permit the operation.
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 →