How dcg Resolves Conflicts Between Multiple Configuration Files: Layered Priority Explained

dcg resolves configuration conflicts by applying a strict five-layer hierarchy where environment variables override explicit config files, which override user configs, which override system configs, with compiled defaults serving as the final fallback.

The Destructive Command Guard (dcg) implements a deterministic, security-first approach to configuration management in the Dicklesworthstone/destructive_command_guard repository. When multiple configuration sources define the same setting, dcg's layered configuration system uses a fixed precedence order to ensure predictable behavior and prevent security downgrades. This design guarantees that sensitive settings from higher-trust sources always take precedence over lower-trust alternatives.

The Five-Layer Configuration Hierarchy

dcg builds its runtime Config struct by loading sources in a specific sequence, with each layer overwriting values from the previous one. The resolution order implemented in src/config.rs prioritizes user intent and system security:

Layer 1: Environment Variables (Highest Priority)

Environment variables prefixed with DCG_ represent the highest-priority configuration source. Any setting exposed via environment overrides all file-based configurations, making them ideal for temporary overrides or containerized deployments. For example, setting DCG_POLICY_DEFAULT_MODE=warn immediately changes the policy behavior regardless of config file contents.

Layer 2: Explicit Config File (DCG_CONFIG)

When users specify a trusted explicit config file via the DCG_CONFIG environment variable, dcg loads this file with elevated priority. This layer overrides the user-wide and system-wide configurations but remains subordinate to direct DCG_ environment variables. The file path specified in DCG_CONFIG is treated as explicitly trusted by the operator.

Layer 3: User Config (XDG Directories)

dcg searches for user-specific configuration in standard XDG directories. It first checks $XDG_CONFIG_HOME/dcg/config.toml, falling back to ~/.config/dcg/config.toml if the variable is unset. This user config layer provides personalized defaults that apply across all repositories for the current user, overriding system-wide settings but yielding to explicit configs and environment variables.

Layer 4: System Config (/etc/dcg/)

The system config at /etc/dcg/config.toml provides global defaults for all users on the host. Administrators use this layer to establish baseline security policies across multi-user systems. According to the source comments in src/config.rs, this layer serves as the foundation that higher-priority sources build upon or override.

Layer 5: Compiled Defaults (Fallback)

Compiled defaults are hard-coded values embedded in the binary during compilation. These values activate only when no other configuration layer provides a specific setting, ensuring the application functions even in the absence of any configuration files.

Special Handling for Repository-Local .dcg.toml Files

A repository-local file named .dcg.toml receives special treatment due to security considerations. Because this file can be contributed by anyone in a shared repository, dcg treats it as untrusted by default. According to the implementation in src/config.rs, auto-discovered .dcg.toml files cannot weaken security: they may only add protective rules and never introduce allow-list entries, disable packs, or change security-critical paths.

Users can voluntarily promote a .dcg.toml to trusted status by setting DCG_CONFIG=.dcg.toml. In this case, the file moves from the auto-discovered untrusted layer to the explicit config layer (Layer 2), gaining the ability to modify security settings.

The Merge Process and Conflict Resolution

When dcg initializes via Config::load() (called from src/main.rs), it executes a sequential merge process defined in Config::from_sources. The implementation follows this pattern:

let env_cfg = Config::from_env();                // 1. Env vars
let explicit_cfg = Config::from_path(env_cfg_path); // 2. DCG_CONFIG
let user_cfg = Config::from_path(user_path);     // 3. User config
let system_cfg = Config::from_path(system_path); // 4. System config
let defaults = Config::default();                // 5. Compiled defaults

let final_cfg = defaults
    .merge(system_cfg)
    .merge(user_cfg)
    .merge(explicit_cfg)
    .merge(env_cfg);

The merge method copies any defined fields from the right-hand argument onto the left, respecting the precedence order. When conflicts arise—such as the same key existing in both user and system configs—the value from the higher-priority layer wins. This deterministic approach ensures identical runtime configurations given the same set of files and environment variables.

Practical Configuration Examples

Override the default policy mode using environment variables:


# Highest priority - overrides any config file

export DCG_POLICY_DEFAULT_MODE="warn"
dcg rm -rf /sensitive/path

Use an explicit trusted configuration:


# Promotes /home/alice/strict-dcg.toml to Layer 2 priority

export DCG_CONFIG="/home/alice/strict-dcg.toml"
dcg git reset --hard

User-wide configuration fallback:


# ~/.config/dcg/config.toml

policy_default_mode = "deny"
packs = { enabled = ["core.git", "core.filesystem"] }

System-wide baseline defaults:


# /etc/dcg/config.toml

policy_default_mode = "log"

Security Safeguards in Config Loading

Before merging layers, dcg applies security constraints through read_config_file_bounded in src/config.rs. This function enforces size limits and performs symlink-safety checks to prevent configuration injection attacks. Additionally, src/allowlist.rs implements trust verification ensuring that allow-list entries are only accepted from trusted layers (user, system, or explicit configs), never from auto-discovered .dcg.toml files.

The src/cli.rs module provides command-line flags that map internally to environment variables, preserving the same precedence hierarchy while offering convenient runtime overrides.

Summary

  • dcg uses a five-layer hierarchy: Environment variables → Explicit config → User config → System config → Compiled defaults.
  • Higher layers overwrite lower layers: The merge method in src/config.rs implements deterministic conflict resolution where the most recent (highest priority) value persists.
  • Repository configs are untrusted: Auto-discovered .dcg.toml files cannot weaken security; they only add protective rules unless explicitly promoted via DCG_CONFIG.
  • Security-first loading: Size limits and symlink checks in read_config_file_bounded prevent exploitation during config file reading.
  • Predictable behavior: Given the same files and environment variables, Config::load() produces identical runtime configurations every time.

Frequently Asked Questions

What happens if the same key exists in user and system config?

The user config value takes precedence. According to the precedence hierarchy in src/config.rs, user configuration (Layer 3) ranks higher than system configuration (Layer 4). When the merge method processes these layers, values from user_cfg replace those from system_cfg, ensuring individual users can override administrative defaults without modifying system files.

Can a .dcg.toml file disable security features or add allow-list entries?

No, not when auto-discovered. The source code explicitly treats .dcg.toml as untrusted because it may originate from external contributors. As implemented in the configuration loading logic, these files cannot weaken security—they may only add protective rules. Allow-list entries and security-disabling settings are rejected from auto-discovered .dcg.toml files; they are only accepted from trusted layers (user, system, or explicit configs via DCG_CONFIG).

How do I override a specific setting without modifying config files?

Set a DCG_-prefixed environment variable. Environment variables constitute Layer 1 (highest priority) in dcg's configuration hierarchy and override all file-based settings. For example, export DCG_POLICY_DEFAULT_MODE=interactive immediately changes the policy behavior for that shell session without touching any TOML files. The src/cli.rs module also provides flags that map to these environment variables for convenience.

Where does dcg look for configuration files by default?

dcg searches four specific locations in order of priority: first, the path specified in DCG_CONFIG (explicit Layer 2); second, $XDG_CONFIG_HOME/dcg/config.toml or ~/.config/dcg/config.toml (user Layer 3); third, /etc/dcg/config.toml (system Layer 4); and finally, auto-discovered .dcg.toml in the current directory (untrusted, restricted). The Config::from_path and read_config_file_bounded functions in src/config.rs handle this discovery and loading process with appropriate security checks.

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 →