Understanding the Symlink Security Check in DCG's System Configuration Loading
The symlink security check in DCG prevents privilege escalation by verifying that system configuration files are not symlinks pointing to user-writable directories before loading them.
DCG (destructive_command_guard) implements a layered configuration system that loads settings from environment, project, user, and system sources. When loading the system configuration layer from /etc/dcg/config.toml on Unix or %ProgramData%\dcg\config.toml on Windows, the application performs a critical symlink security check to prevent non-privileged users from hijacking administrator settings through symbolic link attacks.
How the Symlink Security Check Works
The security mechanism operates during the read_config_file_bounded function execution, specifically when processing the ConfigSource::System layer.
Bounded File Reading
Before any security checks, DCG enforces a 1 MiB size limit on configuration files to prevent memory exhaustion attacks. The read_config_file_bounded function caps the file size before loading content into memory.
System Layer Detection
When the configuration source is identified as ConfigSource::System, DCG inspects the file metadata without following symbolic links using fs::symlink_metadata. This examination occurs in src/config.rs at lines 45-58:
if source == ConfigSource::System {
match fs::symlink_metadata(path) {
Ok(meta) if meta.file_type().is_symlink() => {
if symlink_target_is_user_writable(path) {
eprintln!(
"Warning: refusing to load system config '{}' — symlink to user-writable target",
path.display()
);
return None;
}
}
// … other branches omitted …
}
}
Symlink Target Validation
If the metadata indicates the path is a symlink, DCG invokes symlink_target_is_user_writable (defined at lines 12-33 in src/config.rs) to validate the target's security posture:
fn symlink_target_is_user_writable(path: &Path) -> bool {
let Ok(target) = fs::canonicalize(path) else { return true };
#[cfg(unix)]
{
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let parent = target.parent().unwrap_or(&target);
let Ok(meta) = fs::metadata(parent) else { return true };
meta.uid() != 0 || (meta.permissions().mode() & 0o022) != 0
}
#[cfg(not(unix))]
{ false }
}
The function resolves the symlink target using fs::canonicalize, then examines the parent directory's ownership and permissions. On Unix systems, the target is considered user-writable if the parent directory is not owned by root (UID 0) or if it has group or world-write permissions (mode bits 0o022). On non-Unix platforms, the check defaults to allowing the symlink since the attack surface differs.
Security Implications
The symlink security check serves critical protective functions:
- Privilege escalation prevention: System configuration is intended for administrator control. Without this check, a regular user could create a symlink from
/etc/dcg/config.tomlto a user-controlled file, injecting arbitrary settings into a privileged configuration layer. - Fail-safe design: The check errs conservatively. Any error during target inspection—such as missing permissions or inaccessible paths—results in refusing to load the file, ensuring potentially compromised configurations are skipped.
- Layered integrity preservation: Since DCG's configuration layers follow priority rules (environment > project > user > system), refusing a malicious system config preserves the intended security model without silently falling back to compromised settings.
Practical Examples
Detecting a Malicious Symlink
Attempting to load a system configuration through a user-writable symlink triggers the security warning:
# Create a user-writable file and a symlink under /etc/dcg
mkdir -p /tmp/user_config
echo 'check_updates = false' > /tmp/user_config/bad.toml
sudo ln -s /tmp/user_config/bad.toml /etc/dcg/config.toml
# Run dcg (it will emit a warning and skip the system config)
dcg --version
# → Warning: refusing to load system config '/etc/dcg/config.toml' — symlink to user-writable target
DCG proceeds with remaining configuration layers (user, project, environment) while skipping the compromised system layer.
Loading a Valid System Configuration
When the system configuration is a regular file owned by root, loading proceeds normally:
# Write a proper system config as root
sudo sh -c "echo 'check_updates = true' > /etc/dcg/config.toml"
# dcg now reads the file without warnings
dcg --version
# (no warning, system config applied)
Programmatic Usage
When using DCG's configuration API directly, the symlink check executes automatically:
use std::path::Path;
use dcg::config::{read_config_file_bounded, ConfigSource};
fn load_system_config() -> Option<String> {
let sys_path = dcg::config::system_config_dir().join("config.toml");
read_config_file_bounded(&sys_path, ConfigSource::System)
}
The read_config_file_bounded function automatically applies the symlink security check when the ConfigSource::System variant is passed.
Key Source Files
The symlink security logic resides in the following locations within the Dicklesworthstone/destructive_command_guard repository:
src/config.rs(lines 45-58): Contains the conditional guard that detects symlinks in system configuration paths and refuses to load user-writable targets.src/config.rs(lines 12-33): Implements thesymlink_target_is_user_writablehelper function that examines directory ownership and permission bits on Unix systems.
Summary
- The symlink security check in DCG prevents privilege escalation by validating system configuration files before loading.
- Located in
src/config.rs, the check usesfs::symlink_metadatato detect symlinks without following them, then validates targets usingsymlink_target_is_user_writable. - On Unix systems, targets are rejected if their parent directories are not owned by root (UID 0) or possess group/world-write permissions (mode
0o022). - When a suspicious symlink is detected, DCG emits a warning and returns
None, causing the system configuration layer to be skipped while preserving higher-priority layers. - The check defaults to allowing symlinks on non-Unix platforms where the attack surface differs.
Frequently Asked Questions
What triggers the symlink security check in DCG?
The check triggers when DCG attempts to load the system configuration layer from /etc/dcg/config.toml (Unix) or %ProgramData%\dcg\config.toml (Windows). Specifically, when read_config_file_bounded is called with ConfigSource::System, it examines the file metadata to determine if the path is a symbolic link before reading contents.
How does DCG determine if a symlink target is user-writable?
DCG resolves the symlink using fs::canonicalize, then examines the parent directory's metadata. On Unix systems, the symlink_target_is_user_writable function checks if the parent directory's UID is not 0 (root) or if the permission mode has group or world-write bits set (0o022). If either condition is true, the target is considered user-writable. On non-Unix systems, the function returns false, allowing the symlink.
What happens when DCG detects a suspicious symlink?
When DCG detects a symlink pointing to a user-writable target, it prints a warning message to stderr indicating the refusal to load the system config, then returns None from the configuration loader. This causes DCG to skip the system configuration layer entirely and proceed with remaining layers (user, project, environment), maintaining security without crashing the application.
Does the symlink security check work on Windows?
On Windows and other non-Unix platforms, the symlink_target_is_user_writable function immediately returns false, effectively allowing symlinks in system configuration paths. This design recognizes that the Unix-specific privilege escalation attack (involving UID and permission bits) does not apply to Windows filesystem semantics, though the bounded file size check still applies universally.
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 →