install_package_secure
Install packages with automated security validation. Checks official repos first, then evaluates AUR package trust and PKGBUILD to block unsafe installations.
Instructions
[LIFECYCLE] Install a package with comprehensive security checks. Workflow: 1. Check official repos first (safer) 2. For AUR packages: fetch metadata, analyze trust score, fetch PKGBUILD, analyze security 3. Block installation if critical security issues found 4. Check for AUR helper (paru > yay) 5. Install with --noconfirm if all checks pass. Only works on Arch Linux. Requires sudo access and paru/yay for AUR packages.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| package_name | Yes | Name of package to install (checks official repos first, then AUR) |
Implementation Reference
- src/arch_ops_server/aur.py:647-916 (handler)The main handler function for install_package_secure. Implements the full secure installation workflow: verifies sudo, checks official repos first, fetches AUR metadata and PKGBUILD, runs security analysis, checks AUR helper (paru/yay), and installs with --noconfirm if all checks pass.
async def install_package_secure(package_name: str) -> Dict[str, Any]: """ Install a package with comprehensive security checks. Workflow: 1. Check if package exists in official repos first (safer) 2. For AUR packages: a. Fetch package metadata and analyze trust b. Fetch and analyze PKGBUILD for security issues c. Only proceed if security checks pass 3. Check for AUR helper availability (paru > yay) 4. Install with --noconfirm if all checks pass Args: package_name: Package name to install Returns: Dict with installation status and security analysis """ logger.info(f"Starting secure installation workflow for: {package_name}") # Only supported on Arch Linux if not IS_ARCH: return create_error_response( "NotSupported", "Package installation is only supported on Arch Linux systems", "This server is not running on Arch Linux" ) result = { "package": package_name, "installed": False, "security_checks": {}, "messages": [] } # ======================================================================== # STEP 0: Verify sudo is configured properly # ======================================================================== logger.info("[STEP 0/5] Verifying sudo configuration...") # Test if sudo password is cached or passwordless sudo is configured # Use skip_sudo_check=True to avoid recursive check test_exit_code, _, test_stderr = await run_command( ["sudo", "-n", "true"], timeout=5, check=False, skip_sudo_check=True ) if test_exit_code != 0: result["messages"].append("⚠️ SUDO PASSWORD REQUIRED") result["messages"].append("") result["messages"].append("Package installation requires sudo privileges.") result["messages"].append("Please choose one of these options:") result["messages"].append("") result["messages"].append("Option 1: Configure passwordless sudo for pacman:") result["messages"].append(" sudo visudo -f /etc/sudoers.d/arch-package-install") result["messages"].append(" Add: your_username ALL=(ALL) NOPASSWD: /usr/bin/pacman") result["messages"].append("") result["messages"].append("Option 2: Cache sudo password temporarily:") result["messages"].append(" Run: sudo -v") result["messages"].append(" Then retry the installation") result["messages"].append("") result["messages"].append("Option 3: Install manually in terminal:") result["messages"].append(f" sudo pacman -S {package_name}") result["security_checks"]["decision"] = "SUDO_REQUIRED" return result result["messages"].append("✅ Sudo privileges verified") # ======================================================================== # STEP 1: Check if package is in official repos first # ======================================================================== logger.info(f"[STEP 1/5] Checking if '{package_name}' is in official repos...") result["messages"].append("🔍 Checking official repositories first...") from .pacman import get_official_package_info official_pkg = await get_official_package_info(package_name) # If found in official repos, install directly with pacman if not official_pkg.get("error"): logger.info(f"Package '{package_name}' found in official repos - installing via pacman") result["messages"].append(f"✅ Package found in official repository: {official_pkg.get('repository', 'unknown')}") result["is_official"] = True result["security_checks"]["source"] = "official_repository" result["security_checks"]["risk_level"] = "LOW" result["security_checks"]["recommendation"] = "✅ SAFE - Official repository package" # Install using sudo pacman -S --noconfirm try: result["messages"].append("📦 Installing from official repository...") exit_code, stdout, stderr = await run_command( ["sudo", "pacman", "-S", "--noconfirm", package_name], timeout=300, # 5 minutes for installation check=False ) if exit_code == 0: result["installed"] = True result["messages"].append(f"✅ Successfully installed {package_name} from official repository") logger.info(f"Successfully installed official package: {package_name}") else: result["messages"].append(f"❌ Installation failed: {stderr}") logger.error(f"pacman installation failed: {stderr}") # Check for sudo password issues if "password" in stderr.lower() or "sudo" in stderr.lower(): result["messages"].append("") result["messages"].append("⚠️ SUDO PASSWORD REQUIRED") result["messages"].append("To enable passwordless installation, run one of these commands:") result["messages"].append("1. For passwordless sudo (less secure):") result["messages"].append(" sudo visudo -f /etc/sudoers.d/arch-package-install") result["messages"].append(" Add: your_username ALL=(ALL) NOPASSWD: /usr/bin/pacman") result["messages"].append("2. Or run the installation manually in your terminal:") result["messages"].append(f" sudo pacman -S {package_name}") result["install_output"] = stdout result["install_errors"] = stderr return result except Exception as e: logger.error(f"Installation failed: {e}") return create_error_response( "InstallError", f"Failed to install official package: {str(e)}" ) # ======================================================================== # STEP 2: Package is in AUR - fetch and analyze metadata # ======================================================================== logger.info(f"[STEP 2/5] Package not in official repos - checking AUR...") result["messages"].append("⚠️ Package not in official repos - checking AUR...") result["is_official"] = False # Search AUR for package aur_info = await get_aur_info(package_name) if aur_info.get("error"): return create_error_response( "NotFound", f"Package '{package_name}' not found in official repos or AUR" ) # Extract actual package data (may be wrapped in warning) pkg_data = aur_info.get("data", aur_info) result["messages"].append(f"📦 Found in AUR: {pkg_data.get('name')} v{pkg_data.get('version')}") # Analyze package metadata for trust logger.info(f"[STEP 3/5] Analyzing package metadata for trust indicators...") result["messages"].append("🔍 Analyzing package metadata (votes, maintainer, age)...") metadata_analysis = analyze_package_metadata_risk(pkg_data) result["security_checks"]["metadata_analysis"] = metadata_analysis result["messages"].append(f"📊 Trust Score: {metadata_analysis['trust_score']}/100") result["messages"].append(f" {metadata_analysis['recommendation']}") # ======================================================================== # STEP 3: Fetch and analyze PKGBUILD # ======================================================================== logger.info(f"[STEP 4/5] Fetching and analyzing PKGBUILD for security issues...") result["messages"].append("🔍 Fetching PKGBUILD for security analysis...") try: pkgbuild_content = await get_pkgbuild(package_name) result["messages"].append(f"✅ PKGBUILD fetched ({len(pkgbuild_content)} bytes)") # Analyze PKGBUILD for security issues result["messages"].append("🛡️ Analyzing PKGBUILD for security threats...") pkgbuild_analysis = analyze_pkgbuild_safety(pkgbuild_content) result["security_checks"]["pkgbuild_analysis"] = pkgbuild_analysis result["messages"].append(f"🛡️ Risk Score: {pkgbuild_analysis['risk_score']}/100") result["messages"].append(f" {pkgbuild_analysis['recommendation']}") # Log findings if pkgbuild_analysis["red_flags"]: result["messages"].append(f" 🚨 {len(pkgbuild_analysis['red_flags'])} CRITICAL issues found!") for flag in pkgbuild_analysis["red_flags"][:3]: # Show first 3 result["messages"].append(f" - Line {flag['line']}: {flag['issue']}") if pkgbuild_analysis["warnings"]: result["messages"].append(f" ⚠️ {len(pkgbuild_analysis['warnings'])} warnings found") # Check if package is safe to install if not pkgbuild_analysis["safe"]: result["messages"].append("❌ INSTALLATION BLOCKED - Security analysis failed") result["messages"].append(" Package has critical security issues and will NOT be installed") result["security_checks"]["decision"] = "BLOCKED" result["security_checks"]["reason"] = "Critical security issues detected in PKGBUILD" logger.warning(f"Installation blocked for {package_name} due to security issues") return result # Additional check for high-risk warnings if len(pkgbuild_analysis["warnings"]) >= 5: result["messages"].append("⚠️ HIGH RISK - Multiple suspicious patterns detected") result["messages"].append(" Manual review recommended before installation") result["security_checks"]["decision"] = "REVIEW_RECOMMENDED" except ValueError as e: logger.error(f"Failed to fetch PKGBUILD: {e}") return create_error_response( "FetchError", f"Failed to fetch PKGBUILD for security analysis: {str(e)}" ) # ======================================================================== # STEP 4: Check for AUR helper # ======================================================================== logger.info(f"[STEP 5/5] Checking for AUR helper (paru/yay)...") result["messages"].append("🔧 Checking for AUR helper...") aur_helper = get_aur_helper() if not aur_helper: result["messages"].append("❌ No AUR helper found (paru or yay)") result["messages"].append(" Please install an AUR helper:") result["messages"].append(" - Recommended: paru (pacman -S paru)") result["messages"].append(" - Alternative: yay") result["security_checks"]["decision"] = "NO_HELPER" return result result["messages"].append(f"✅ Using AUR helper: {aur_helper}") result["aur_helper"] = aur_helper # ======================================================================== # STEP 5: Install package with AUR helper # ======================================================================== result["messages"].append(f"📦 Installing {package_name} via {aur_helper} (no confirmation)...") logger.info(f"Installing AUR package {package_name} with {aur_helper}") try: # Install with --noconfirm flag exit_code, stdout, stderr = await run_command( [aur_helper, "-S", "--noconfirm", package_name], timeout=600, # 10 minutes for AUR package build check=False ) if exit_code == 0: result["installed"] = True result["messages"].append(f"✅ Successfully installed {package_name} from AUR") result["security_checks"]["decision"] = "INSTALLED" logger.info(f"Successfully installed AUR package: {package_name}") else: result["messages"].append(f"❌ Installation failed with exit code {exit_code}") result["messages"].append(f" Error: {stderr}") result["security_checks"]["decision"] = "INSTALL_FAILED" logger.error(f"AUR installation failed for {package_name}: {stderr}") # Check for sudo password issues if "password" in stderr.lower() or "sudo" in stderr.lower(): result["messages"].append("") result["messages"].append("⚠️ SUDO PASSWORD REQUIRED") result["messages"].append("To enable passwordless installation for AUR packages:") result["messages"].append("1. For passwordless sudo for pacman:") result["messages"].append(" sudo visudo -f /etc/sudoers.d/arch-aur-install") result["messages"].append(" Add: your_username ALL=(ALL) NOPASSWD: /usr/bin/pacman") result["messages"].append("2. Or run the installation manually in your terminal:") result["messages"].append(f" {aur_helper} -S {package_name}") result["install_output"] = stdout result["install_errors"] = stderr except Exception as e: logger.error(f"Installation failed: {e}") result["messages"].append(f"❌ Installation exception: {str(e)}") result["security_checks"]["decision"] = "INSTALL_ERROR" return result - src/arch_ops_server/server.py:1127-1133 (registration)Tool registration handler in the MCP server call_tool dispatcher. Routes the 'install_package_secure' tool name to the install_package_secure async function, with a platform check for Arch Linux.
elif name == "install_package_secure": if not IS_ARCH: return [TextContent(type="text", text=create_platform_error_message("install_package_secure"))] package_name = arguments["package_name"] result = await install_package_secure(package_name) return [TextContent(type="text", text=json.dumps(result, indent=2))] - Tool input schema defined in the list_tools function. Defines the Tool object with name 'install_package_secure', description, input schema (requires package_name string), and annotations marking it as destructive.
Tool( name="install_package_secure", description="[LIFECYCLE] Install a package with comprehensive security checks. Workflow: 1. Check official repos first (safer) 2. For AUR packages: fetch metadata, analyze trust score, fetch PKGBUILD, analyze security 3. Block installation if critical security issues found 4. Check for AUR helper (paru > yay) 5. Install with --noconfirm if all checks pass. Only works on Arch Linux. Requires sudo access and paru/yay for AUR packages.", inputSchema={ "type": "object", "properties": { "package_name": { "type": "string", "description": "Name of package to install (checks official repos first, then AUR)" } }, "required": ["package_name"] }, annotations=ToolAnnotations(destructiveHint=True) ), - src/arch_ops_server/__init__.py:20-21 (registration)Import and export of install_package_secure from the aur module, making it available through the package's public API (also listed in __all__ at line 134).
install_package_secure, ) - src/arch_ops_server/utils.py:296-313 (helper)Helper function get_aur_helper() used by install_package_secure to detect available AUR helpers (paru preferred over yay).
def get_aur_helper() -> Optional[str]: """ Detect available AUR helper with priority: paru > yay. Returns: str: Name of available AUR helper ('paru' or 'yay'), or None if neither exists """ # Check in priority order if check_command_exists("paru"): logger.info("Found AUR helper: paru") return "paru" elif check_command_exists("yay"): logger.info("Found AUR helper: yay") return "yay" else: logger.warning("No AUR helper found (paru or yay)") return None - Tool metadata definition for install_package_secure, specifying category='lifecycle', platform='arch', permission='write', workflow='installation', and listing related and prerequisite tools.
"install_package_secure": ToolMetadata( name="install_package_secure", category="lifecycle", platform="arch", permission="write", workflow="installation", related_tools=[ "check_updates_dry_run", "verify_package_integrity", "query_package_history" ], prerequisite_tools=[ "get_official_package_info", "audit_package_security" ] ),