CLI Security Guide
This document describes security considerations, best practices, and security audit findings for the Radium CLI.
Security Audit Summaryβ
Unsafe Code Usageβ
Location: apps/cli/src/main.rs (lines 437-448)
Current Implementation:
// SAFETY: We're in single-threaded main() before any async/spawning
unsafe {
config::apply_config_to_env(&cli_config);
}
// Set workspace if provided (CLI arg takes precedence)
if let Some(workspace) = args.workspace {
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("RADIUM_WORKSPACE", workspace) };
}
Security Assessment: β SAFE
Justification:
- Environment variables are set in
main()before any async operations - No threads are spawned before these operations
- The unsafe blocks are necessary because
std::env::set_varis not thread-safe, but we guarantee single-threaded execution - The TODO comment has been addressed: environment access only happens in single-threaded code
Recommendation: Add explicit documentation comment explaining the safety guarantee.
Credential Storageβ
Location: ~/.radium/auth/credentials.json
Security Measures:
- Credentials are stored in user's home directory (not world-readable)
- File permissions should be set to 0600 (owner read/write only)
- Credentials are never logged or exposed in error messages
- API keys are read from stdin (not command line arguments)
Security Checklist:
- Credentials stored in secure location (
~/.radium/auth/) - Credentials never logged
- Credentials never in command-line arguments
- File permissions verified (0600) - Needs verification
- Credentials never exposed in error messages - Needs audit
Recommendation:
- Verify file permissions are set to 0600 when creating credentials file
- Audit all error messages to ensure no credential leakage
Input Validationβ
Path Traversal Prevention:
All file operations should validate paths to prevent directory traversal attacks:
use std::path::Path;
fn validate_path(path: &Path, base: &Path) -> anyhow::Result<()> {
let canonical = path.canonicalize()
.context("Failed to canonicalize path")?;
let base_canonical = base.canonicalize()
.context("Failed to canonicalize base path")?;
if !canonical.starts_with(&base_canonical) {
anyhow::bail!("Path traversal detected: path outside base directory");
}
Ok(())
}
Command Injection Prevention:
When executing external processes, always use structured APIs:
// β
GOOD: Use structured command execution
use std::process::Command;
let output = Command::new("program")
.arg(sanitized_input)
.output()?;
// β BAD: Shell command injection risk
let output = Command::new("sh")
.arg("-c")
.arg(format!("program {}", user_input)) // DANGEROUS!
.output()?;
File Operationsβ
Security Best Practices:
- Validate all file paths before operations
- Use canonical paths to prevent symlink attacks
- Set appropriate permissions for sensitive files
- Never trust user input without validation
Example Secure File Operation:
use std::fs;
use std::path::Path;
fn secure_file_write(path: &Path, content: &str) -> anyhow::Result<()> {
// Validate path
if path.is_absolute() && !path.starts_with("/safe/directory") {
anyhow::bail!("Path outside allowed directory");
}
// Create parent directories with secure permissions
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
// Write file
fs::write(path, content)?;
// Set secure permissions (Unix only)
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path)?.permissions();
perms.set_mode(0o600); // rw-------
fs::set_permissions(path, perms)?;
}
Ok(())
}
Dependency Securityβ
Vulnerability Scanningβ
Run cargo audit regularly to check for known vulnerabilities:
# Install cargo-audit
cargo install cargo-audit
# Scan for vulnerabilities
cargo audit
Security Updatesβ
- Keep dependencies up to date
- Review security advisories for dependencies
- Test updates before deploying
- Use
cargo auditin CI/CD pipeline
Credential Handlingβ
Storageβ
Location: ~/.radium/auth/credentials.json
Format:
{
"gemini": "api-key-here",
"openai": "api-key-here",
"claude": "api-key-here"
}
Security Requirements:
- File permissions: 0600 (owner read/write only)
- Never log credentials
- Never expose in error messages
- Never pass via command-line arguments
- Read from stdin or secure input methods only
Credential Store Implementationβ
The CredentialStore in radium-core handles credential storage:
let store = CredentialStore::new()?;
store.store(provider_type, api_key)?;
Security Checklist:
- Credentials stored in secure location
- Credentials read from stdin (not CLI args)
- File permissions verified (0600)
- Credentials never logged
- Error messages don't expose credentials
Command Injection Preventionβ
Safe Command Executionβ
β Safe Pattern:
use std::process::Command;
let output = Command::new("program")
.arg("--option")
.arg(user_input) // Safe: passed as separate argument
.output()?;
β Unsafe Pattern:
use std::process::Command;
let output = Command::new("sh")
.arg("-c")
.arg(format!("program {}", user_input)) // DANGEROUS: shell injection
.output()?;
Input Sanitizationβ
Always sanitize user input before use:
fn sanitize_input(input: &str) -> String {
// Remove shell metacharacters
input
.chars()
.filter(|c| c.is_alphanumeric() || matches!(c, '-' | '_' | '.' | '/'))
.collect()
}
Path Traversal Preventionβ
Validation Functionβ
use std::path::{Path, PathBuf};
fn validate_workspace_path(path: &Path, workspace_root: &Path) -> anyhow::Result<PathBuf> {
let canonical = path.canonicalize()
.context("Failed to canonicalize path")?;
let root_canonical = workspace_root.canonicalize()
.context("Failed to canonicalize workspace root")?;
if !canonical.starts_with(&root_canonical) {
anyhow::bail!(
"Path traversal detected: {} is outside workspace root {}",
canonical.display(),
root_canonical.display()
);
}
Ok(canonical)
}
Security Checklist for New Commandsβ
When implementing new commands:
- Input Validation: All user input is validated
- Path Validation: File paths are validated and canonicalized
- Command Injection: No shell command execution with user input
- Credential Safety: No credentials in logs or error messages
- File Permissions: Sensitive files have appropriate permissions
- Error Messages: Error messages don't expose sensitive information
- Dependencies: No known vulnerabilities in dependencies
- Async Safety: No unsafe code without proper justification
Security Testingβ
Path Traversal Testβ
#[test]
fn test_path_traversal_prevention() {
let workspace_root = PathBuf::from("/safe/workspace");
let malicious_path = PathBuf::from("/safe/workspace/../../../etc/passwd");
assert!(validate_workspace_path(&malicious_path, &workspace_root).is_err());
}
Command Injection Testβ
#[test]
fn test_command_injection_prevention() {
let malicious_input = "test; rm -rf /";
// Should not execute shell commands
let output = Command::new("program")
.arg(malicious_input) // Safe: passed as argument, not shell command
.output();
// Verify no shell execution occurred
assert!(output.is_ok());
}
Credential Leakage Testβ
#[test]
fn test_no_credential_leakage() {
// Test that credentials don't appear in:
// - Log output
// - Error messages
// - Debug output
// - JSON output (unless explicitly requested)
}
Dependency Securityβ
Regular Auditsβ
Run cargo audit regularly:
cargo audit
Update Strategyβ
- Review security advisories
- Test updates in development
- Update dependencies regularly
- Monitor for new vulnerabilities
Reporting Security Issuesβ
If you discover a security vulnerability:
- Do not create a public issue
- Contact the maintainers privately
- Provide detailed information about the vulnerability
- Allow time for fix before disclosure
Security Best Practices Summaryβ
- Validate all input - Never trust user input
- Sanitize paths - Prevent path traversal attacks
- Avoid shell execution - Use structured command APIs
- Protect credentials - Never log or expose credentials
- Set file permissions - Use appropriate permissions (0600 for sensitive files)
- Audit dependencies - Regularly check for vulnerabilities
- Document unsafe code - Justify all unsafe blocks
- Test security - Include security tests in test suite
Known Security Considerationsβ
Environment Variablesβ
- Environment variables set in
main()before async operations - Unsafe blocks are necessary but safe due to single-threaded guarantee
- Documented with safety comments
Credential Storageβ
- Credentials stored in
~/.radium/auth/credentials.json - File permissions should be 0600 (needs verification)
- Credentials never logged or exposed
File Operationsβ
- All file operations should validate paths
- Use canonical paths to prevent symlink attacks
- Set appropriate permissions for sensitive files
Command Executionβ
- No shell command execution with user input
- Use structured command APIs (
std::process::Command) - Validate all inputs before execution