Claude-skill-registry agentic-jumpstart-security

Security best practices and guidelines for the Jarvy CLI codebase - a cross-platform development environment provisioning tool that executes system commands with elevated privileges

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/agentic-jumpstart-security" ~/.claude/skills/majiayu000-claude-skill-registry-agentic-jumpstart-security && rm -rf "$T"
manifest: skills/data/agentic-jumpstart-security/SKILL.md
safety · automated scan (high risk)
This is a pattern-based risk scan, not a security review. Our crawler flagged:
  • curl piped into shell
  • uses sudo
  • makes HTTP requests (curl)
Always read a skill's source content before installing. Patterns alone don't mean the skill is malicious — but they warrant attention.
source content

Security Guidelines for Jarvy CLI

This skill provides comprehensive security guidelines for developing and maintaining the Jarvy CLI codebase. Jarvy is a cross-platform tool that provisions development environments by executing package manager commands (brew, apt, dnf, winget, etc.) with potential privilege escalation.

Threat Model Overview

Jarvy operates in a high-risk security context:

  1. Command Execution: Executes external binaries (package managers, shells) with user-supplied data
  2. Privilege Escalation: Uses
    sudo
    on POSIX systems for package installation
  3. Configuration Parsing: Reads TOML files from user-specified paths
  4. Network Requests: Makes HTTP requests to PostHog and OTLP endpoints
  5. Telemetry Collection: Gathers machine fingerprints and usage data
  6. Shell Script Execution: Downloads and executes installer scripts (rustup, nvm)

1. Command Injection Prevention

The Risk

Jarvy passes user-provided tool names and versions to package manager commands. A malicious

jarvy.toml
could attempt command injection.

CORRECT Pattern: Argument Array Separation

Always use

Command::new()
with separate arguments - NEVER shell string interpolation:

// CORRECT: Arguments are passed as separate array elements
// Each argument is escaped by the OS, preventing injection
use std::process::Command;

pub fn run(cmd: &str, args: &[&str]) -> Result<Output, InstallError> {
    Command::new(cmd)
        .args(args)  // Safe: each arg is a separate element
        .output()
        .map_err(|e| /* handle error */)
}

// Usage - safe even with untrusted input
run("brew", &["install", user_provided_package_name])?;
run("apt-get", &["install", "-y", package_name])?;
run("winget", &["install", "-e", "--id", package_id])?;

ANTI-PATTERN: Shell String Interpolation

// DANGEROUS: Never concatenate user input into shell commands
let cmd = format!("brew install {}", user_input);  // VULNERABLE
Command::new("sh").args(["-c", &cmd]).output()?;   // VULNERABLE

// DANGEROUS: Using shell expansion with user data
let script = format!("curl ... | sh -s -- {}", user_version);  // VULNERABLE

CORRECT Pattern: Hardcoded Package IDs

For package managers that use IDs, hardcode known-safe values:

// CORRECT: Package IDs are compile-time constants
const GIT_WINGET_ID: &str = "Git.Git";
const RUSTUP_WINGET_ID: &str = "Rustlang.Rustup";

fn install_windows() -> Result<(), InstallError> {
    run("winget", &["install", "-e", "--id", GIT_WINGET_ID])?;
    Ok(())
}

CORRECT Pattern: Input Validation for Tool Names

Validate tool names against an allowlist before any operations:

// CORRECT: Only process tools registered in the known registry
pub fn add(name: &str, version: &str) -> Result<(), InstallError> {
    let key = name.to_ascii_lowercase();  // Normalize
    let map = registry().read().expect("registry rwlock poisoned");

    if let Some(handler) = map.get(&key) {
        // Only known tools can be processed
        let f = *handler;
        drop(map);
        f(version)
    } else {
        Err(InstallError::Parse("unknown tool"))  // Reject unknown tools
    }
}

Version String Validation

Version hints should be validated to prevent injection via version parameters:

// CORRECT: Validate version strings contain only expected characters
fn validate_version_hint(hint: &str) -> bool {
    // Allow: digits, dots, hyphens, plus signs (semver compatible)
    hint.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '+'))
}

// CORRECT: Use version for comparison only, not command construction
pub fn cmd_satisfies(cmd: &str, min_prefix: &str) -> bool {
    if let Ok(out) = Command::new(cmd).arg("--version").output() {
        let s = String::from_utf8_lossy(&out.stdout);
        return s.contains(min_prefix);  // Read-only comparison
    }
    false
}

2. Privilege Escalation Security

The Risk

Improper sudo handling can lead to privilege escalation attacks or unintended system modifications.

CORRECT Pattern: Explicit Sudo Control

// CORRECT: Sudo is a conscious decision, not automatic
pub fn run_maybe_sudo(use_sudo: bool, cmd: &str, args: &[&str]) -> Result<Output, InstallError> {
    match current_os() {
        Os::Windows => run(cmd, args),  // No sudo on Windows
        Os::Linux | Os::Macos => {
            if use_sudo {
                // Prepend sudo as a separate command
                let mut all = Vec::with_capacity(1 + args.len());
                all.push(cmd);
                all.extend_from_slice(args);
                run("sudo", &all)  // sudo is the command, original cmd is first arg
            } else {
                run(cmd, args)
            }
        }
    }
}

CORRECT Pattern: Sudo Detection and User Configuration

// CORRECT: Check if sudo is available before attempting
pub fn has(cmd: &str) -> bool {
    Command::new(cmd)
        .arg("--version")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

// CORRECT: Allow users to control sudo behavior via config
pub fn plan_sudo_attempts(use_sudo: Option<bool>, sudo_available: bool) -> Vec<bool> {
    match use_sudo {
        Some(flag) => vec![flag],  // User explicitly set preference
        None => {
            if sudo_available {
                vec![false, true]  // Try without first, then with
            } else {
                vec![false]  // No sudo available
            }
        }
    }
}

ANTI-PATTERN: Unconditional Sudo

// DANGEROUS: Always using sudo without user consent
fn install_package(pkg: &str) -> Result<(), Error> {
    run("sudo", &["apt-get", "install", pkg])?;  // WRONG: No user control
    Ok(())
}

// DANGEROUS: Sudo in shell strings
let cmd = "sudo apt-get install -y package";  // WRONG: Shell injection risk
Command::new("sh").args(["-c", cmd]).output()?;

Permission Error Handling

// CORRECT: Distinct error types for permission issues
#[derive(thiserror::Error, Debug)]
pub enum InstallError {
    #[error("invalid permissions: {0}")]
    InvalidPermissions(&'static str),
    // ...
}

// CORRECT: Map permission errors to specific exit codes
match e.kind() {
    std::io::ErrorKind::PermissionDenied => {
        InstallError::InvalidPermissions("operation requires elevated privileges")
    }
    // ...
}

3. Path Traversal Prevention

The Risk

User-specified config file paths could access sensitive system files.

CORRECT Pattern: Path Canonicalization

use std::path::Path;

// CORRECT: Canonicalize and validate paths
fn safe_read_config(path: &str) -> Result<String, ConfigError> {
    let path = Path::new(path);

    // Canonicalize to resolve symlinks and ../ sequences
    let canonical = path.canonicalize()
        .map_err(|_| ConfigError::InvalidPath)?;

    // Ensure file has expected extension
    if canonical.extension() != Some(std::ffi::OsStr::new("toml")) {
        return Err(ConfigError::InvalidExtension);
    }

    std::fs::read_to_string(canonical)
        .map_err(ConfigError::IoError)
}

CORRECT Pattern: Restricted Config Locations

// CORRECT: Global config is always in a known location
pub fn initialize() -> CliConfig {
    let home_dir = dirs::home_dir().expect("Failed to get home directory");
    let jarvy_dir = home_dir.join(".jarvy");  // Fixed subdirectory
    let config_file_path = jarvy_dir.join("config.toml");  // Fixed filename

    // Only read from this specific path
    let config_content = fs::read_to_string(&config_file_path).unwrap_or_default();
    // ...
}

ANTI-PATTERN: Unrestricted Path Access

// DANGEROUS: Directly using user path without validation
fn read_config(path: &str) -> String {
    fs::read_to_string(path).unwrap()  // WRONG: No validation
}

// DANGEROUS: String concatenation for paths
let path = format!("{}/{}", user_dir, user_filename);  // WRONG: Path injection

4. Telemetry Data Security

The Risk

Telemetry could leak PII, credentials, or sensitive system information.

CORRECT Pattern: Anonymized Identifiers

// CORRECT: Use hardware-based anonymous fingerprint
fn get_hwid_fingerprint() -> Result<String, Box<dyn std::error::Error>> {
    let mut builder = IdBuilder::new(Encryption::SHA256);

    // Components that identify the machine, not the user
    builder
        .add_component(HWIDComponent::SystemID)
        .add_component(HWIDComponent::CPUCores)
        .add_component(HWIDComponent::OSName)
        .add_component(HWIDComponent::DriveSerial);

    // Salted hash - cannot be reversed to original values
    const SALT: &str = "9f86d081884c7d659a2feaa0c55ad015...";
    builder.build(SALT)
}

CORRECT Pattern: Explicit Opt-Out

// CORRECT: Allow users to disable telemetry
pub fn init(enable_analytics: bool, distinct_id: String) {
    // Environment variable override
    let env_disable = std::env::var("JARVY_ANALYTICS").ok();
    let enabled = match env_disable.as_deref() {
        Some("0") | Some("false") => false,  // Honor user preference
        _ => enable_analytics,
    };

    // Clear documentation about telemetry
    println!(r"
        Jarvy collects telemetry data to help us improve your experience.
        The data collected is anonymized...
        If you wish to opt-out, add to ~/.jarvy/config.toml:
        [settings]
        telemetry = false
    ");
}

Data Minimization

// CORRECT: Only collect necessary, non-sensitive data
pub fn capture(event: &str, mut properties: serde_json::Map<String, serde_json::Value>) {
    // Safe to collect: OS type, shell type, CLI version
    properties.entry("os".to_string())
        .or_insert(serde_json::Value::String(detect_os()));
    properties.entry("shell".to_string())
        .or_insert(serde_json::Value::String(detect_shell()));
    properties.entry("version".to_string())
        .or_insert(serde_json::Value::String(env!("CARGO_PKG_VERSION").to_string()));

    // DO NOT collect: usernames, paths containing usernames, environment variables
    // containing secrets, command arguments that might contain credentials
}

ANTI-PATTERN: PII in Telemetry

// DANGEROUS: Collecting potentially sensitive data
properties.insert("username", std::env::var("USER").unwrap());  // WRONG: PII
properties.insert("home_dir", dirs::home_dir().display());  // WRONG: Contains username
properties.insert("full_command", std::env::args().collect());  // WRONG: May contain secrets
properties.insert("env_vars", std::env::vars().collect());  // WRONG: Contains secrets

5. HTTP Request Security

The Risk

Network requests could be intercepted (MITM) or directed to malicious endpoints.

CORRECT Pattern: HTTPS Only with Certificate Validation

// CORRECT: Use HTTPS endpoints with proper certificate validation
// ureq performs certificate validation by default
let response = ureq::post(&format!("{}/capture/", c.host))  // host should be https://
    .header("Content-Type", "application/json")
    .send_json(payload)?;

// CORRECT: Hardcode HTTPS URLs for installers
const RUSTUP_URL: &str = "https://sh.rustup.rs";
run("bash", &["-lc",
    "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"
])?;

CORRECT Pattern: TLS Version Enforcement

// CORRECT: Enforce modern TLS in curl commands
"curl --proto '=https' --tlsv1.2 -sSf https://example.com"
//     ^^^^^^^^^^^^^^^ only HTTPS
//                     ^^^^^^^^^^^ TLS 1.2 minimum

ANTI-PATTERN: Insecure Network Requests

// DANGEROUS: HTTP endpoints
let host = "http://api.example.com";  // WRONG: No TLS

// DANGEROUS: Disabling certificate verification
// (ureq doesn't easily allow this, but some HTTP clients do)

// DANGEROUS: Downloading scripts without verification
run("bash", &["-c", "curl http://example.com/script.sh | bash"])?;  // WRONG: HTTP + piped execution

6. Safe External Command Output Parsing

The Risk

Parsing command output incorrectly can lead to logic errors or information disclosure.

CORRECT Pattern: Lossy UTF-8 Conversion

// CORRECT: Use lossy conversion to handle malformed output safely
if !out.status.success() {
    return Err(InstallError::CommandFailed {
        cmd: cmd.to_string(),
        code: out.status.code(),
        stderr: String::from_utf8_lossy(&out.stderr).into(),  // Safe conversion
    });
}

// CORRECT: Version checking with contains (not parsing)
pub fn cmd_satisfies(cmd: &str, min_prefix: &str) -> bool {
    if let Ok(out) = Command::new(cmd).arg("--version").output() {
        let s = String::from_utf8_lossy(&out.stdout);
        return s.contains(min_prefix);  // Simple substring check
    }
    false
}

CORRECT Pattern: Defensive Parsing

// CORRECT: Handle parsing failures gracefully
let config: CliConfig = {
    let config_content = fs::read_to_string(&config_file_path).unwrap_or_default();
    if config_content.trim().is_empty() {
        CliConfig::default()  // Safe fallback
    } else {
        toml::from_str(&config_content).unwrap_or_default()  // Safe fallback on parse error
    }
};

ANTI-PATTERN: Unsafe Parsing

// DANGEROUS: Assuming output is valid UTF-8
let output = String::from_utf8(out.stdout).unwrap();  // WRONG: Can panic

// DANGEROUS: Complex regex on untrusted output without limits
let re = Regex::new(r"version (\d+)\.(\d+)\.(\d+)").unwrap();
// Consider: What if output is 10MB of garbage?

// DANGEROUS: Executing parsed output
let version = parse_version(&output);
run("install", &[&format!("package@{}", version)])?;  // WRONG: Trusting parsed data

7. Script Download and Execution Security

The Risk

Downloading and executing scripts from the internet is inherently dangerous.

CORRECT Pattern: Pinned Versions and HTTPS

// CORRECT: Pin to specific versions when downloading scripts
const NVM_VERSION: &str = "v0.39.7";
const NVM_INSTALL_URL: &str =
    "https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh";

fn install_posix() -> Result<(), InstallError> {
    run("bash", &["-lc",
        &format!("curl -fsSL {} | bash", NVM_INSTALL_URL)
    ])?;
    Ok(())
}

CORRECT Pattern: Verify Script Source

// CORRECT: Only use official, well-known installer URLs
// rustup: https://sh.rustup.rs (official Rust project)
// nvm: https://raw.githubusercontent.com/nvm-sh/nvm/... (official repo)
// Homebrew: https://raw.githubusercontent.com/Homebrew/install/...

// Document the trust chain in comments
/// Install Rust via rustup using the official installer.
/// Source: https://rustup.rs (maintained by Rust project)
fn install_unix() -> Result<(), InstallError> {
    run("bash", &["-lc",
        "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"
    ])
}

ANTI-PATTERN: Arbitrary Script Execution

// DANGEROUS: User-provided URLs
fn install_from_url(url: &str) -> Result<(), Error> {
    run("bash", &["-c", &format!("curl {} | bash", url)])?;  // WRONG
    Ok(())
}

// DANGEROUS: Unpinned versions
"curl -fsSL https://example.com/install.sh | bash"  // WRONG: Gets latest, could change

8. Error Handling and Information Disclosure

The Risk

Detailed error messages can leak sensitive system information.

CORRECT Pattern: Structured Errors Without Sensitive Data

// CORRECT: Error types that don't leak paths or system details
#[derive(thiserror::Error, Debug)]
pub enum InstallError {
    #[error("unsupported platform")]
    Unsupported,
    #[error("prerequisite missing: {0}")]
    Prereq(&'static str),  // Static string, not dynamic path
    #[error("invalid permissions: {0}")]
    InvalidPermissions(&'static str),
    #[error("command failed: {cmd} (code: {code:?})\n{stderr}")]
    CommandFailed {
        cmd: String,  // Command name only
        code: Option<i32>,
        stderr: String,  // May contain sensitive info - log carefully
    },
}

CORRECT Pattern: Safe Error Logging to Telemetry

// CORRECT: Sanitize errors before sending to telemetry
pub fn capture_exception(
    message: &str,
    exception_type: &str,
    stack_trace: Option<String>,
    context: serde_json::Map<String, serde_json::Value>,
) {
    // Filter or redact sensitive information from stack traces
    let safe_stack = stack_trace.map(|s| redact_paths(&s));

    // Don't include full file paths in telemetry
    let mut safe_context = context.clone();
    safe_context.remove("full_path");
    safe_context.remove("home_directory");

    // Send sanitized data
    capture("$exception", props);
}

ANTI-PATTERN: Verbose Error Messages

// DANGEROUS: Including full paths in errors
eprintln!("Failed to read: {}", full_path);  // WRONG: Exposes filesystem structure

// DANGEROUS: Including environment in errors
eprintln!("Error with env: {:?}", std::env::vars());  // WRONG: Leaks secrets

// DANGEROUS: Stack traces to users
panic!("Unexpected error: {:?}", e);  // WRONG: May contain sensitive info

9. Dependency Security

Guidelines

  1. Minimize Dependencies: Prefer stdlib over external crates when feasible
  2. Audit Dependencies: Use
    cargo audit
    and
    cargo deny
    regularly
  3. Pin Versions: Use exact versions in Cargo.toml for security-critical dependencies
  4. Review Features: Only enable necessary crate features

Current Dependencies Security Notes

# Security-relevant dependencies in Jarvy:

# ureq - HTTP client
# - Uses rustls by default (no OpenSSL dependency issues)
# - Certificate validation enabled by default
ureq = { version = "3.1.2", features = ["json"] }

# serde/toml - Config parsing
# - Well-audited, widely used
# - Deserialize untrusted input with care
serde = { version = "1.0.204", features = ["derive"] }
toml = "0.9.5"

# machineid-rs - Hardware fingerprinting
# - Review what data it collects
# - Ensure it doesn't collect PII
machineid-rs = "1.2"

10. Testing Security

CORRECT Pattern: Test Mode Isolation

// CORRECT: Disable dangerous operations in tests
pub fn run(cmd: &str, args: &[&str]) -> Result<Output, InstallError> {
    // Fast tests skip external commands entirely
    if std::env::var_os("JARVY_FAST_TEST").is_some() {
        return Err(InstallError::Prereq("skipped in fast test mode"));
    }

    // Unit tests don't run external commands by default
    #[cfg(test)]
    {
        if std::env::var_os("JARVY_RUN_EXTERNAL_CMDS_IN_TEST").is_none() {
            return Err(InstallError::Prereq("external commands disabled in tests"));
        }
    }

    // Actual command execution...
}

// CORRECT: Test mode avoids interactive prompts
fn user_select() {
    if std::env::var("JARVY_TEST_MODE").as_deref() == Ok("1") {
        println!("TEST: user_select invoked");
        return;  // Skip interactive UI
    }
    // ...
}

Security Checklist for Code Reviews

When reviewing PRs, verify:

  • No user input is interpolated into shell command strings
  • All
    Command::new()
    calls use argument arrays, not concatenated strings
  • Sudo usage is controlled by user configuration
  • Config file paths are validated/canonicalized
  • Telemetry events don't contain PII or secrets
  • HTTP requests use HTTPS with certificate validation
  • Downloaded scripts are from pinned, official sources
  • Error messages don't leak sensitive paths or environment data
  • New dependencies are audited for security
  • Tests don't execute real system commands without explicit opt-in

Reporting Security Issues

If you discover a security vulnerability in Jarvy:

  1. Do not open a public GitHub issue
  2. Email security concerns to the maintainers privately
  3. Include:
    • Description of the vulnerability
    • Steps to reproduce
    • Potential impact assessment
    • Suggested fix (if any)