How the Allowlist System Works with Layered Project, User, and System Scopes in Destructive Command Guard
The Destructive Command Guard (dcg) allowlist system uses a three-layer hierarchy where project-level configuration overrides user settings, which override system-wide defaults, with each layer supporting rule IDs, exact commands, prefixes, and regex patterns under strict safety validations.
The allowlist system in Destructive Command Guard (dcg)—an open-source safety tool from Dicklesworthstone/destructive_command_guard—implements a precedence-ordered mechanism for whitelisting commands across different administrative scopes. Written in Rust and located primarily in src/allowlist.rs, this system balances flexibility with security by allowing local project configurations to override broader user and system policies.
The Three-Layer Hierarchy
The allowlist system recognizes three distinct configuration scopes with explicit precedence rules. When the engine evaluates whether to permit a command, it consults these layers in order of decreasing priority.
Project Scope
The project layer holds the highest precedence. It reads from .dcg/allowlist.toml located at the repository root. This allows development teams to commit project-specific exceptions that travel with the codebase, ensuring that legitimate but potentially destructive operations—such as specific git reset patterns—are permitted only within that project's context.
User Scope
The user layer resides at ~/.config/dcg/allowlist.toml and applies to all commands executed by the current user across all projects. This middle layer allows individual developers to set personal preferences without modifying project repositories or requiring system administrator privileges.
System Scope
The system layer optionally loads from /etc/dcg/allowlist.toml and carries the lowest precedence. Administrators can deploy organization-wide defaults here, knowing that project and user configurations will override these settings when more specific policies exist.
Loading and Merging Configuration
All layer interaction is managed through the LayeredAllowlist struct in src/allowlist.rs. The system does not require all three files to exist; missing files simply create empty layers that contribute no entries.
LayeredAllowlist::load_from_paths
The constructor LayeredAllowlist::load_from_paths initializes the hierarchy by creating a LoadedAllowlistLayer for each existing file. According to the implementation, the insertion order is project → user → system, establishing the precedence used during lookups. This ordering ensures that when multiple layers contain entries for the same command, the project-level entry wins.
Agent-Profile Overrides
Before consulting file-based layers, prepend_agent_exact_commands can inject a temporary Agent layer with the absolute highest precedence (AllowlistLayer::Agent). This layer contains only exact-command entries derived from an agent's profile and exists only for the current session. These entries bypass file I/O but cannot use regex patterns or bypass risk acknowledgments.
Allowlist Entry Types
Each entry in the allowlist is represented by an AllowEntry structure that specifies what to match and under what conditions. The AllowSelector enum determines the matching strategy.
Rule ID Matching
Entries targeting specific rule IDs use AllowSelector::Rule and match the format pack_id:pattern_name. This allows fine-grained control over individual destructive patterns defined in dcg's rule packs.
Exact Command Matching
AllowSelector::ExactCommand performs literal string matching against the full command. This is the safest match type and is the only option available for agent-profile overrides.
Command Prefix Matching
AllowSelector::CommandPrefix validates that a command starts with a specific prefix, but includes safety checks in command_prefix_safely_matches to ensure the prefix ends at a token boundary and that the remaining command tail contains no shell-chain metacharacters that could enable injection attacks.
Regex Pattern Matching
AllowSelector::RegexPattern supports arbitrary regular expressions for complex matching scenarios. However, these entries require explicit risk_acknowledged = true metadata to prevent accidental broad regexes from creating security holes.
Validity Checks and Safety Guards
Before any entry can match a command, it must pass several validation checks implemented in is_entry_valid_at_path_with_session. These guards apply across all layers equally.
Expiration and TTL
Entries may specify expires_at (a specific timestamp) or ttl (time-to-live duration). The system checks these against the current time and rejects expired entries regardless of layer precedence.
Session Binding
When session = true, the entry binds to a specific DCG_SESSION_ID environment variable or a Linux system fingerprint. The entry only matches if the current session identifier equals the stored session_id, preventing allowlisted commands from persisting across terminal sessions unintendedly.
Environment Conditions
Entries can specify conditions as key-value pairs that must match the current environment variables. All specified conditions must evaluate to true for the entry to remain valid.
Risk Acknowledgment
Regex pattern entries require risk_acknowledged = true in their metadata. Without this flag, match_pattern_at_path rejects the match even if the regex technically matches the command string.
Path Restrictions
If paths is specified, the current working directory must match at least one glob pattern using glob::Pattern. When cwd is None, path filtering is skipped for backward compatibility, but when provided, the path must satisfy the restriction.
Matching Logic and Precedence
The matching API provides separate methods for each entry type, all following the same layer-traversal strategy.
How Matches Are Resolved
match_rule_at_pathwalks layers from project to system, returning the first entry whoseAllowSelector::Rulematches the requestedpack_idandpattern_name.match_exact_command_at_pathdoes the same forAllowSelector::ExactCommand.match_command_prefix_at_pathvalidates token boundaries and safe tails before accepting a prefix match.match_pattern_at_pathcompiles regexes into a globalpattern_cacheand applies them only ifrisk_acknowledgedis true.
Wildcard Support
Rule ID lookups support wildcard pattern names (*), allowing entries like core.git:* to match all patterns within a specific pack. However, wildcard pack IDs are explicitly rejected for safety to prevent overly broad exceptions.
Implementation in Source Code
The layered allowlist system spans several key files:
src/allowlist.rs– Core implementation containingLayeredAllowlist,AllowEntry, validity checks, and all match functions.src/main.rs– Entry point that instantiates theLayeredAllowlistand coordinates command evaluation.src/evaluator.rs– Orchestrates calls to allowlist match functions before applying destructive pattern detection.Cargo.toml– Declares dependencies includingglobfor path pattern matching andfancy-regexfor regex handling.
Summary
- The allowlist system in dcg uses three precedence-ordered layers: project (
.dcg/allowlist.toml), user (~/.config/dcg/allowlist.toml), and system (/etc/dcg/allowlist.toml), with project configurations always winning. - An optional Agent layer injected via
prepend_agent_exact_commandstakes temporary precedence above all file-based layers. - Entries support four match types—rule IDs, exact commands, command prefixes, and regex patterns—each with specific safety constraints.
- All entries must pass temporal (expiration/TTL), contextual (session, environment), and spatial (path glob) validity checks before matching.
- Regex patterns require explicit
risk_acknowledged = trueand are cached globally for performance. - The implementation in
src/allowlist.rsusesLayeredAllowlist::load_from_pathsto initialize the hierarchy and provides specificmatch_*functions that traverse layers from highest to lowest precedence.
Frequently Asked Questions
What file takes precedence if the same command is allowlisted in multiple scopes?
The project scope always wins. According to the loading logic in LayeredAllowlist::load_from_paths, layers are inserted in the order project → user → system, and match functions traverse this list from top to bottom, returning the first valid entry they find. If .dcg/allowlist.toml contains an entry for git reset --hard, it overrides any git reset --hard entries in the user or system configurations.
How does the agent layer interact with the three standard scopes?
The Agent layer sits above all file-based scopes when active. Calling prepend_agent_exact_commands injects a temporary AllowlistLayer::Agent containing only exact-command entries at the head of the layer stack. These entries are checked first during evaluation, but they cannot use regex patterns and are not persisted to disk—they exist only for the current session.
What safety measures prevent regex patterns from bypassing security?
Two controls apply to AllowSelector::RegexPattern entries. First, the entry must include risk_acknowledged = true in its metadata; otherwise match_pattern_at_path rejects the match. Second, even when acknowledged, the regex must pass all other validity checks including expiration, session binding, environment conditions, and path restrictions. Wildcard pack IDs are also rejected for rule-based entries.
Can allowlist entries be restricted to specific directories?
Yes, through the paths field which accepts glob patterns. When match_rule_at_path or other match functions receive a cwd parameter, they validate the current working directory against glob::Pattern instances stored in the entry. If the directory doesn't match any specified pattern, the entry is skipped even if the command otherwise matches. When cwd is None, path filtering is bypassed for backward compatibility.
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 →