How Destructive Command Guard Prevents Malicious Repository Configs from Weakening Protection

Destructive Command Guard (dcg) employs a layered trust model that treats repository-owned .dcg.toml files as untrusted, enforcing strict size limits, symlink validation, and policy restrictions to prevent malicious configurations from disabling security controls.

Destructive Command Guard (dcg) is a safety tool that intercepts potentially destructive shell commands before they execute. Because attackers could attempt to ship malicious .dcg.toml files in cloned repositories to disable protective packs or whitelist dangerous commands, the tool implements a hardened configuration loader in src/config.rs that strictly separates user-controlled settings from repository-owned configurations. This architecture ensures that malicious repository configs cannot weaken protection without explicit user opt-in.

Layered Trust Model and ConfigSource Hierarchy

The foundation of dcg's defense is a hierarchical trust model defined by the ConfigSource enum in src/config.rs (lines 35‑48). Only configuration sources outside the repository—such as environment variables, explicit DCG_CONFIG paths, user-level ~/.config/dcg/config.toml, or system-level /etc/dcg/config.toml—are granted full authority. In contrast, automatically discovered .dcg.toml files inside a project directory are loaded with restricted privileges.

The enum prioritizes sources as follows:

  1. Environment variables – Highest trust, can override any setting.
  2. Explicit DCG_CONFIG – User-provided path, validated before full authority is granted.
  3. User-level and system-level files – Standard locations with full policy control.
  4. Automatic project config – Limited to enforcement-only settings; cannot disable protections.

This hierarchy ensures that a repository cannot override global security policies even if it contains a .dcg.toml file.

Size Caps and Resource Limits

To prevent denial-of-service attacks via maliciously large configuration files, dcg enforces a hard 1 MiB size limit on all config files. The function read_config_file_bounded in src/config.rs (lines 27‑33) reads the file into a bounded buffer, rejecting any input that exceeds this limit before parsing begins.

// Pseudocode representation of the bounded read
fn read_config_file_bounded(path: &Path) -> Result<String, Error> {
    const MAX_SIZE: usize = 1024 * 1024; // 1 MiB
    // Implementation reads up to MAX_SIZE and rejects overflow
}

This prevents attackers from exhausting memory or triggering parser vulnerabilities with arbitrarily large .dcg.toml files.

On Unix systems, dcg prevents time-of-check to time-of-use (TOCTOU) attacks by using the O_NOFOLLOW flag when opening configuration files. The open_config_file_for_source function (lines 56‑63) opens files without following symlinks, and validate_opened_unix_config_file (lines 124‑132) verifies that the file descriptor matches the expected inode and device after opening.

This two-step validation ensures that an attacker cannot swap a legitimate config file for a malicious one via a symlink race condition after the initial path check but before the read operation.

Ownership and Permission Enforcement

System-level configurations must meet strict ownership requirements. The helper unix_owner_or_mode_is_user_writable (lines 98‑104) checks that system-wide configs are owned by root and are not group- or world-writable. If a system config fails these permission checks, it is rejected entirely, preventing privilege escalation through writable configuration injections.

Enforcement-Only Policy for Repository Configs

The most critical safeguard is the into_restricted_project_policy method (lines 661‑671). When dcg automatically discovers a .dcg.toml file in a repository, it converts the ConfigLayer into a restricted version that strips any fields capable of lowering protection:

  • Disabled packs – Any disabled list entries are discarded, preventing repos from turning off core protections like the core.git pack.
  • Allow-list rules – Policy layers that whitelist dangerous commands are removed.
  • Fail-closed overrides – Only safe subset settings such as fail_closed, known pack IDs, and deny-by-default policies are retained.

# Example: This malicious .dcg.toml cannot disable protections

[packs]
disabled = ["core.git"]  # <-- Ignored when loaded automatically

[allowlist]
commands = ["rm -rf /"]  # <-- Stripped by into_restricted_project_policy

Explicit Opt-In via DCG_CONFIG

Repository configs gain full authority only when the user explicitly sets the DCG_CONFIG environment variable to point to the file. The explicitly_trusts_project_policy function (lines 51‑59) validates this opt-in by:

  1. Canonicalizing both the specified path and the expected repository root.
  2. Verifying the file is a regular file (not a symlink).
  3. Ensuring the path matches the repository root (lines 71‑79), preventing traversal attacks.

# Opt-in example: Granting full authority to a repository config

$ export DCG_CONFIG=./.dcg.toml
$ dcg explain "git reset --hard HEAD"

Without this explicit environment variable, the repository config remains restricted regardless of its contents.

Programmatic Configuration Loading

When integrating dcg into Rust applications, the config loader exposes the trust decisions through the load_config function:

use dcg::config::{load_config, ConfigSource};

fn main() {
    // Load configuration hierarchy for current directory
    let report = load_config(std::env::current_dir().unwrap()).unwrap();
    
    // Inspect which sources were loaded vs rejected
    for src in report.sources {
        println!("{}: {}", src.layer.label(), src.status.label());
    }
}

This allows programmatic verification that repository configs are being treated with the appropriate restriction level.

Summary

  • Layered trust ensures only user-controlled sources (environment variables, explicit paths, system/user configs) have full authority over security policy.
  • Size limits (1 MiB) prevent memory exhaustion attacks via oversized config files.
  • Symlink protection using O_NOFOLLOW and inode validation blocks TOCTOU attacks on Unix systems.
  • Permission checks enforce that system configs are root-owned and non-writable by non-privileged users.
  • Enforcement-only mode automatically strips dangerous directives (disabled packs, allow-lists) from discovered repository configs via into_restricted_project_policy.
  • Explicit opt-in via DCG_CONFIG is required for repositories to gain full policy control, with path canonicalization preventing symlink attacks.

Frequently Asked Questions

Can a malicious repository disable protective packs by shipping a .dcg.toml file?

No. When dcg automatically discovers a .dcg.toml file in a repository, it applies into_restricted_project_policy (lines 661‑671) which strips any disabled entries from the packs section. The repository config operates in enforcement-only mode unless the user explicitly sets DCG_CONFIG to trust it.

How does dcg prevent a repository config from being swapped for a malicious version after detection?

The loader uses O_NOFOLLOW when opening files and validates the opened file descriptor via validate_opened_unix_config_file (lines 124‑132). This compares the inode and device of the opened file against the original path, ensuring the file has not been replaced via a symlink race condition between check and use.

What happens if a repository tries to include an oversized configuration file?

The read_config_file_bounded function (lines 27‑33) enforces a hard 1 MiB limit. Any config file exceeding this size is rejected immediately, preventing memory exhaustion attacks before the TOML parser processes the content.

How can I grant full authority to a repository's configuration?

Set the DCG_CONFIG environment variable to the path of the repository config file: export DCG_CONFIG=./.dcg.toml. The explicitly_trusts_project_policy function validates that the path is canonical, matches the repository root, and is a regular file before granting full policy control.

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 →