Difference Between Safe Patterns and Destructive Patterns in DCG Evaluation Order

In the Destructive Command Guard (dcg) pipeline, safe patterns are evaluated first as a whitelist that immediately allows matching commands, while destructive patterns act as a blacklist checked only when safe patterns fail to match.

The Dicklesworthstone/destructive_command_guard repository implements a deterministic two-stage regex matching system to prevent accidental execution of dangerous shell commands. Understanding the difference between safe patterns and destructive patterns in dcg evaluation order is essential for configuring effective command-line protections without blocking legitimate workflows.

The Two-Stage Evaluation Pipeline

DCG processes every incoming command through a strict sequential filter defined in src/evaluator.rs. The pipeline relies on two statically defined regex pattern containers initialized via LazyLock:

  1. Safe patterns (SAFE_PATTERNS) — A whitelist of known-harmless commands
  2. Destructive patterns (DESTRUCTIVE_PATTERNS) — A blacklist of dangerous operations

The evaluation logic follows an immutable priority: whitelist first, blacklist second, default allow last.

Safe Patterns: The Whitelist Layer

Safe patterns represent operations explicitly deemed harmless, such as git checkout -b <branch>, git clean -n, or rm -rf /tmp/*. When a command matches any entry in SAFE_PATTERNS, dcg immediately returns Decision::Allow and skips all further security checks.

This short-circuit behavior ensures that common, safe development workflows incur minimal overhead and cannot be accidentally blocked by overly broad destructive patterns.

In src/evaluator.rs, the safe pattern check appears first in the evaluation function:

// 1️⃣ Check safe patterns
for pattern in SAFE_PATTERNS.iter() {
    if pattern.is_match(&command) { return Decision::Allow; }
}

Destructive Patterns: The Blacklist Layer

Destructive patterns target high-risk commands like git reset --hard, git push --force, or rm -rf /. Each pattern carries metadata including a human-readable reason and a unique rule ID (e.g., "core.git:reset-hard").

These patterns are evaluated only if the command failed to match any safe pattern. When matched, dcg returns a structured Decision::Deny containing the rule ID and explanation:

// 2️⃣ Check destructive patterns
for pattern in DESTRUCTIVE_PATTERNS.iter() {
    if pattern.is_match(&command) {
        return Decision::Deny { rule_id, reason, … };
    }
}

The pattern definitions include context-specific reasons such as "git reset --hard destroys uncommitted changes", providing clear feedback to users about why their command was intercepted.

Why Evaluation Order Matters

The deterministic ordering ensures that the whitelist truly overrides the blacklist. If a command could theoretically match both pattern types—such as a safe variant of a generally dangerous command—the safe pattern takes precedence.

This priority prevents false positives for legitimate operations while maintaining strict security for truly destructive commands. The logic guarantees that adding a safe pattern is sufficient to exempt a command from destructive pattern scrutiny, regardless of regex overlap.

Implementation Details in src/evaluator.rs

The core evaluation logic resides in src/evaluator.rs, where both pattern sets are initialized as thread-safe static variables:

static SAFE_PATTERNS: LazyLock<Vec<Pattern>> = LazyLock::new(|| { … });
static DESTRUCTIVE_PATTERNS: LazyLock<Vec<Pattern>> = LazyLock::new(|| { … });

The complete three-step flow concludes with a default allow decision for commands matching neither set:

// 3️⃣ Default allow
Decision::Allow

Documentation in README.md (line 1613) and the SKILL.md cheat sheet (line 262) visualize this flow as: Check SAFE_PATTERNS → Allow if match; otherwise check DESTRUCTIVE_PATTERNS → Deny if match; otherwise Allow.

Practical Examples

The following examples demonstrate how the evaluation order behaves in practice:

// Example: a safe command – allowed immediately
let cmd = "git clean -n";
assert_eq!(dcg.evaluate(cmd), Decision::Allow);

// Example: a destructive command – denied after safe check fails
let cmd = "git reset --hard HEAD";
assert_eq!(
    dcg.evaluate(cmd),
    Decision::Deny {
        rule_id: "core.git:reset-hard",
        reason: "git reset --hard destroys uncommitted changes",
        …
    }
);

// Example: a command that matches neither set – allowed by default
let cmd = "echo Hello";
assert_eq!(dcg.evaluate(cmd), Decision::Allow);

Summary

  • Safe patterns (SAFE_PATTERNS) are evaluated first in src/evaluator.rs and trigger immediate Decision::Allow, functioning as a whitelist for known-harmless commands.
  • Destructive patterns (DESTRUCTIVE_PATTERNS) act as a blacklist checked only when safe patterns fail to match, returning Decision::Deny with specific rule IDs and reasons.
  • The evaluation order is strictly sequential: whitelist → blacklist → default allow.
  • This priority ensures safe patterns override destructive patterns, preventing false positives for legitimate operations while blocking truly dangerous commands.
  • The implementation uses LazyLock for thread-safe static initialization of regex patterns in src/evaluator.rs.

Frequently Asked Questions

What happens if a command matches both safe and destructive patterns?

If a command matches both pattern types, the safe pattern takes precedence because dcg checks SAFE_PATTERNS before DESTRUCTIVE_PATTERNS. The command is immediately allowed, and the destructive pattern check is skipped entirely. This whitelist-override behavior prevents legitimate safe variants of dangerous commands from being blocked.

Where are the safe and destructive patterns defined in the codebase?

Both pattern sets are defined as static variables using LazyLock in src/evaluator.rs. The SAFE_PATTERNS and DESTRUCTIVE_PATTERNS vectors are initialized at startup with compiled regular expressions. The README.md and SKILL.md files provide high-level documentation and visual flowcharts of how these patterns are utilized during command evaluation.

What is the default decision if a command matches neither pattern set?

If a command fails to match any safe pattern and also fails to match any destructive pattern, dcg returns Decision::Allow by default. This permissive fallback assumes that unknown commands are safe unless explicitly blacklisted, ensuring the tool does not disrupt normal shell usage while still protecting against known destructive operations.

How does the evaluation order prevent false positives?

By checking safe patterns first, dcg ensures that explicitly whitelisted commands—such as safe git operations or targeted temporary file removals—cannot be caught by broad destructive regexes. This ordering guarantees that administrators can define precise exceptions to general security rules without modifying complex negative lookahead patterns in the blacklist.

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 →