Skip to main content
Glama

audit_package_security

Read-only

Audit AUR packages for security risks by analyzing PKGBUILD files and evaluating package metadata. Identify red flags and assess trustworthiness before installation.

Instructions

[SECURITY] Comprehensive security audit for AUR packages. Actions: pkgbuild_analysis (scan PKGBUILD for 50+ red flags), metadata_risk (evaluate trustworthiness via votes/maintainer/age). Examples: audit_package_security(action='pkgbuild_analysis', pkgbuild_content='...'), audit_package_security(action='metadata_risk', package_name='yay'). ⚠️ Always audit AUR packages before installing.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesType of security audit
pkgbuild_contentNoPKGBUILD content for analysis
package_nameNoPackage name for metadata analysis
package_infoNoPre-fetched package metadata

Implementation Reference

  • Main handler function for audit_package_security. Dispatches to analyze_pkgbuild_safety() for 'pkgbuild_analysis' action or analyze_package_metadata_risk() for 'metadata_risk' action. Handles parameter validation and error responses.
    async def audit_package_security(
        action: Literal["pkgbuild_analysis", "metadata_risk"],
        pkgbuild_content: Optional[str] = None,
        package_name: Optional[str] = None,
        package_info: Optional[dict] = None
    ) -> dict:
        """
        Unified security audit tool for AUR packages.
        
        Args:
            action: Type of security audit
            pkgbuild_content: PKGBUILD content (for pkgbuild_analysis)
            package_name: Package name (for metadata_risk)
            package_info: Pre-fetched package metadata (optional, for metadata_risk)
        
        Returns:
            Security analysis results
        """
        if action == "pkgbuild_analysis":
            if not pkgbuild_content:
                return create_error_response(
                    "pkgbuild_content is required for pkgbuild_analysis",
                    error_type="validation_error"
                )
            result = analyze_pkgbuild_safety(pkgbuild_content)
            result["action"] = "pkgbuild_analysis"
            return result
        
        elif action == "metadata_risk":
            if not package_name and not package_info:
                return create_error_response(
                    "Either package_name or package_info is required for metadata_risk",
                    error_type="validation_error"
                )
            
            if package_info:
                result = analyze_package_metadata_risk(package_info)
            else:
                # Fetch package info first
                search_result = await search_aur(package_name, limit=1)
                if "error" in search_result or not search_result.get("results"):
                    return create_error_response(
                        f"Could not find package '{package_name}' in AUR",
                        error_type="not_found"
                    )
                result = analyze_package_metadata_risk(search_result["results"][0])
            
            result["action"] = "metadata_risk"
            return add_aur_warning(result)
        
        else:
            return create_error_response(
                f"Unknown action: {action}",
                error_type="validation_error"
            )
  • MCP Tool registration with input schema: action (enum: pkgbuild_analysis, metadata_risk), pkgbuild_content (string), package_name (string), package_info (object). Required: action. Read-only annotation.
    Tool(
        name="audit_package_security",
        description="[SECURITY] Comprehensive security audit for AUR packages. Actions: pkgbuild_analysis (scan PKGBUILD for 50+ red flags), metadata_risk (evaluate trustworthiness via votes/maintainer/age). Examples: audit_package_security(action='pkgbuild_analysis', pkgbuild_content='...'), audit_package_security(action='metadata_risk', package_name='yay'). ⚠️ Always audit AUR packages before installing.",
        inputSchema={
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["pkgbuild_analysis", "metadata_risk"],
                    "description": "Type of security audit"
                },
                "pkgbuild_content": {
                    "type": "string",
                    "description": "PKGBUILD content for analysis"
                },
                "package_name": {
                    "type": "string",
                    "description": "Package name for metadata analysis"
                },
                "package_info": {
                    "type": "object",
                    "description": "Pre-fetched package metadata"
                }
            },
            "required": ["action"]
        },
        annotations=ToolAnnotations(readOnlyHint=True)
    ),
  • Tool dispatch handler in server.py call_tool() that receives audit_package_security calls and delegates to the handler in aur.py with extracted arguments.
    elif name == "audit_package_security":
        action = arguments["action"]
        pkgbuild_content = arguments.get("pkgbuild_content", None)
        package_name = arguments.get("package_name", None)
        package_info = arguments.get("package_info", None)
        result = await audit_package_security(action, pkgbuild_content, package_name, package_info)
        return [TextContent(type="text", text=json.dumps(result, indent=2))]
  • Import/export of audit_package_security from aur module (also exported in __all__ at line 133).
    audit_package_security,
  • analyze_package_metadata_risk() helper: evaluates AUR package trustworthiness via votes, maintainer, age, update frequency. Returns trust_score (0-100), risk_factors, trust_indicators, and recommendation.
    def analyze_package_metadata_risk(package_info: Dict[str, Any]) -> Dict[str, Any]:
        """
        Analyze AUR package metadata for security and trustworthiness indicators.
        
        Evaluates:
        - Package popularity and community trust (votes)
        - Maintainer status (orphaned packages)
        - Update frequency (out-of-date, abandoned packages)
        - Package age and maturity
        - Maintainer history
        
        Args:
            package_info: Package info dict from AUR RPC (formatted or raw)
        
        Returns:
            Dict with metadata risk analysis including:
            - trust_score: 0-100 (higher = more trustworthy)
            - risk_factors: list of identified risks
            - trust_indicators: list of positive indicators
            - recommendation: trust recommendation
        """
        from datetime import datetime, timedelta
        
        risk_factors = []
        trust_indicators = []
        
        logger.debug(f"Analyzing metadata for package: {package_info.get('name', 'unknown')}")
        
        # ========================================================================
        # EXTRACT METADATA
        # ========================================================================
        votes = package_info.get("votes", package_info.get("NumVotes", 0))
        popularity = package_info.get("popularity", package_info.get("Popularity", 0.0))
        maintainer = package_info.get("maintainer", package_info.get("Maintainer"))
        out_of_date = package_info.get("out_of_date", package_info.get("OutOfDate"))
        last_modified = package_info.get("last_modified", package_info.get("LastModified"))
        first_submitted = package_info.get("first_submitted", package_info.get("FirstSubmitted"))
        
        # ========================================================================
        # ANALYZE VOTING/POPULARITY
        # ========================================================================
        if votes == 0:
            risk_factors.append({
                "category": "popularity",
                "severity": "HIGH",
                "issue": "Package has zero votes - untested by community"
            })
        elif votes < 5:
            risk_factors.append({
                "category": "popularity",
                "severity": "MEDIUM",
                "issue": f"Low vote count ({votes}) - limited community validation"
            })
        elif votes >= 50:
            trust_indicators.append({
                "category": "popularity",
                "indicator": f"High vote count ({votes}) - well-trusted by community"
            })
        elif votes >= 20:
            trust_indicators.append({
                "category": "popularity",
                "indicator": f"Moderate vote count ({votes}) - some community validation"
            })
        
        # Popularity scoring
        if popularity < 0.001:
            risk_factors.append({
                "category": "popularity",
                "severity": "MEDIUM",
                "issue": f"Very low popularity score ({popularity:.4f}) - rarely used"
            })
        elif popularity >= 1.0:
            trust_indicators.append({
                "category": "popularity",
                "indicator": f"High popularity score ({popularity:.2f}) - widely used"
            })
        
        # ========================================================================
        # ANALYZE MAINTAINER STATUS
        # ========================================================================
        if not maintainer or maintainer == "None":
            risk_factors.append({
                "category": "maintainer",
                "severity": "CRITICAL",
                "issue": "Package is ORPHANED - no active maintainer"
            })
        else:
            trust_indicators.append({
                "category": "maintainer",
                "indicator": f"Active maintainer: {maintainer}"
            })
        
        # ========================================================================
        # ANALYZE OUT-OF-DATE STATUS
        # ========================================================================
        if out_of_date:
            # Check if out_of_date is a boolean or timestamp
            if isinstance(out_of_date, bool) and out_of_date:
                risk_factors.append({
                    "category": "maintenance",
                    "severity": "MEDIUM",
                    "issue": "Package is flagged as out-of-date"
                })
            elif isinstance(out_of_date, (int, float)):
                # It's a timestamp
                try:
                    ood_date = datetime.fromtimestamp(out_of_date)
                    ood_days = (datetime.now() - ood_date).days
                    risk_factors.append({
                        "category": "maintenance",
                        "severity": "MEDIUM" if ood_days < 90 else "HIGH",
                        "issue": f"Out-of-date for {ood_days} days since {ood_date.strftime('%Y-%m-%d')}"
                    })
                except Exception:
                    risk_factors.append({
                        "category": "maintenance",
                        "severity": "MEDIUM",
                        "issue": "Package is flagged as out-of-date"
                    })
        
        # ========================================================================
        # ANALYZE LAST MODIFICATION TIME
        # ========================================================================
        if last_modified:
            try:
                # Handle both timestamp formats
                if isinstance(last_modified, str):
                    # Try to parse from formatted string
                    last_mod_date = datetime.strptime(last_modified.split()[0], "%Y-%m-%d")
                else:
                    # It's a Unix timestamp
                    last_mod_date = datetime.fromtimestamp(last_modified)
                
                days_since_update = (datetime.now() - last_mod_date).days
                
                if days_since_update > 730:  # 2 years
                    risk_factors.append({
                        "category": "maintenance",
                        "severity": "HIGH",
                        "issue": f"Not updated in {days_since_update} days (~{days_since_update//365} years) - possibly abandoned"
                    })
                elif days_since_update > 365:  # 1 year
                    risk_factors.append({
                        "category": "maintenance",
                        "severity": "MEDIUM",
                        "issue": f"Not updated in {days_since_update} days (~{days_since_update//365} year) - low activity"
                    })
                elif days_since_update <= 30:
                    trust_indicators.append({
                        "category": "maintenance",
                        "indicator": f"Recently updated ({days_since_update} days ago) - actively maintained"
                    })
            except Exception as e:
                logger.debug(f"Failed to parse last_modified: {e}")
        
        # ========================================================================
        # ANALYZE PACKAGE AGE
        # ========================================================================
        if first_submitted:
            try:
                # Handle both timestamp formats
                if isinstance(first_submitted, str):
                    first_submit_date = datetime.strptime(first_submitted.split()[0], "%Y-%m-%d")
                else:
                    first_submit_date = datetime.fromtimestamp(first_submitted)
                
                package_age_days = (datetime.now() - first_submit_date).days
                
                if package_age_days < 7:
                    risk_factors.append({
                        "category": "age",
                        "severity": "HIGH",
                        "issue": f"Very new package ({package_age_days} days old) - needs community review time"
                    })
                elif package_age_days < 30:
                    risk_factors.append({
                        "category": "age",
                        "severity": "MEDIUM",
                        "issue": f"New package ({package_age_days} days old) - limited track record"
                    })
                elif package_age_days >= 365:
                    trust_indicators.append({
                        "category": "age",
                        "indicator": f"Mature package ({package_age_days//365}+ years old) - established track record"
                    })
            except Exception as e:
                logger.debug(f"Failed to parse first_submitted: {e}")
        
        # ========================================================================
        # CALCULATE TRUST SCORE
        # ========================================================================
        # Start with base score of 50
        trust_score = 50
        
        # Adjust based on votes (max +30)
        if votes >= 100:
            trust_score += 30
        elif votes >= 50:
            trust_score += 20
        elif votes >= 20:
            trust_score += 10
        elif votes >= 5:
            trust_score += 5
        elif votes == 0:
            trust_score -= 20
        
        # Adjust based on popularity (max +10)
        if popularity >= 5.0:
            trust_score += 10
        elif popularity >= 1.0:
            trust_score += 5
        elif popularity < 0.001:
            trust_score -= 10
        
        # Penalties for risk factors
        for risk in risk_factors:
            if risk["severity"] == "CRITICAL":
                trust_score -= 30
            elif risk["severity"] == "HIGH":
                trust_score -= 15
            elif risk["severity"] == "MEDIUM":
                trust_score -= 10
        
        # Clamp between 0 and 100
        trust_score = max(0, min(100, trust_score))
        
        # ========================================================================
        # GENERATE RECOMMENDATION
        # ========================================================================
        if trust_score >= 70:
            recommendation = "✅ TRUSTED - Package has good community validation and maintenance"
        elif trust_score >= 50:
            recommendation = "⚠️  MODERATE TRUST - Package is acceptable but verify PKGBUILD carefully"
        elif trust_score >= 30:
            recommendation = "⚠️  LOW TRUST - Package has significant risk factors, extra caution needed"
        else:
            recommendation = "❌ UNTRUSTED - Package has critical trust issues, avoid unless necessary"
        
        logger.info(f"Package metadata analysis: trust_score={trust_score}, "
                    f"{len(risk_factors)} risk factors, {len(trust_indicators)} trust indicators")
        
        return {
            "trust_score": trust_score,
            "risk_factors": risk_factors,
            "trust_indicators": trust_indicators,
            "recommendation": recommendation,
            "summary": {
                "votes": votes,
                "popularity": round(popularity, 4),
                "is_orphaned": not maintainer or maintainer == "None",
                "is_out_of_date": bool(out_of_date),
                "total_risk_factors": len(risk_factors),
                "total_trust_indicators": len(trust_indicators)
            }
        }
  • analyze_pkgbuild_safety() helper: scans PKGBUILD for dangerous commands, obfuscation, network activity, binary downloads and other security threats. Returns safe (bool), red_flags, warnings, risk_score.
    def analyze_pkgbuild_safety(pkgbuild_content: str) -> Dict[str, Any]:
        """
        Perform comprehensive safety analysis on PKGBUILD content.
        
        Checks for:
        - Dangerous commands (rm -rf /, dd, fork bombs, etc.)
        - Obfuscated code (base64, eval, encoding tricks)
        - Network activity (reverse shells, data exfiltration)
        - Binary downloads and execution
        - Privilege escalation attempts
        - Cryptocurrency mining patterns
        - Source URL validation
        - Suspicious file operations
        
        Args:
            pkgbuild_content: Raw PKGBUILD text
        
        Returns:
            Dict with detailed safety analysis results including:
            - safe: boolean
            - red_flags: critical security issues
            - warnings: suspicious patterns
            - info: informational notices
            - risk_score: 0-100 (higher = more dangerous)
            - recommendation: action recommendation
        """
        import re
        from urllib.parse import urlparse
        
        red_flags = []  # Critical security issues
        warnings = []   # Suspicious but not necessarily malicious
        info = []       # Informational notices
        
        lines = pkgbuild_content.split('\n')
        logger.debug(f"Analyzing PKGBUILD with {len(lines)} lines")
        
        # ========================================================================
        # CRITICAL PATTERNS - Definitely malicious
        # ========================================================================
        dangerous_patterns = [
            # Destructive commands
            (r"rm\s+-rf\s+/[^a-zA-Z]", "CRITICAL: rm -rf / or /something detected - system destruction"),
            (r"\bdd\b.*if=/dev/(zero|random|urandom).*of=/dev/sd", "CRITICAL: dd overwriting disk detected"),
            (r":\(\)\{.*:\|:.*\}", "CRITICAL: Fork bomb detected"),
            (r"\bmkfs\.", "CRITICAL: Filesystem formatting detected"),
            (r"fdisk.*-w", "CRITICAL: Partition table modification detected"),
            
            # Reverse shells and backdoors
            (r"/dev/tcp/\d+\.\d+\.\d+\.\d+/\d+", "CRITICAL: Reverse shell via /dev/tcp detected"),
            (r"nc\s+-[^-]*e\s+/bin/(ba)?sh", "CRITICAL: Netcat reverse shell detected"),
            (r"bash\s+-i\s+>&\s+/dev/tcp/", "CRITICAL: Interactive reverse shell detected"),
            (r"python.*socket.*connect", "CRITICAL: Python socket connection (potential backdoor)"),
            (r"perl.*socket.*connect", "CRITICAL: Perl socket connection (potential backdoor)"),
            
            # Malicious downloads and execution
            (r"curl[^|]*\|\s*(ba)?sh", "CRITICAL: Piping curl to shell (remote code execution)"),
            (r"wget[^|]*\|\s*(ba)?sh", "CRITICAL: Piping wget to shell (remote code execution)"),
            (r"curl.*-o.*&&.*chmod\s+\+x.*&&\s*\./", "CRITICAL: Download, make executable, and run pattern"),
            
            # Crypto mining patterns
            (r"xmrig|minerd|cpuminer|ccminer", "CRITICAL: Cryptocurrency miner detected"),
            (r"stratum\+tcp://", "CRITICAL: Mining pool connection detected"),
            (r"--donate-level", "CRITICAL: XMRig miner option detected"),
            
            # Rootkit/malware installation
            (r"chattr\s+\+i", "CRITICAL: Making files immutable (rootkit technique)"),
            (r"/etc/ld\.so\.preload", "CRITICAL: LD_PRELOAD manipulation (rootkit technique)"),
            (r"HISTFILE=/dev/null", "CRITICAL: History clearing (covering tracks)"),
        ]
        
        # ========================================================================
        # SUSPICIOUS PATTERNS - Require careful review
        # ========================================================================
        suspicious_patterns = [
            # Obfuscation techniques
            (r"base64\s+-d", "Obfuscation: base64 decoding detected"),
            (r"xxd\s+-r", "Obfuscation: hex decoding detected"),
            (r"\beval\b", "Obfuscation: eval usage (can execute arbitrary code)"),
            (r"\$\(.*base64.*\)", "Obfuscation: base64 in command substitution"),
            (r"openssl\s+enc\s+-d", "Obfuscation: encrypted content decoding"),
            (r"echo.*\|.*sh", "Obfuscation: piping echo to shell"),
            (r"printf.*\|.*sh", "Obfuscation: piping printf to shell"),
            
            # Suspicious permissions and ownership
            (r"chmod\s+[0-7]*7[0-7]*7", "Dangerous: world-writable permissions"),
            (r"chown\s+root", "Suspicious: changing ownership to root"),
            (r"chmod\s+[u+]*s", "Suspicious: setuid/setgid (privilege escalation risk)"),
            
            # Suspicious file operations
            (r"mktemp.*&&.*chmod", "Suspicious: temp file creation with permission change"),
            (r">/dev/null\s+2>&1", "Suspicious: suppressing all output (hiding activity)"),
            (r"nohup.*&", "Suspicious: background process that persists"),
            
            # Network activity
            (r"curl.*-s.*-o", "Network: silent download detected"),
            (r"wget.*-q.*-O", "Network: quiet download detected"),
            (r"nc\s+-l", "Network: netcat listening mode (potential backdoor)"),
            (r"socat", "Network: socat usage (advanced networking tool)"),
            (r"ssh.*-R\s+\d+:", "Network: SSH reverse tunnel detected"),
            
            # Data exfiltration
            (r"curl.*-X\s+POST.*--data", "Data exfiltration: HTTP POST with data"),
            (r"tar.*\|.*ssh", "Data exfiltration: tar over SSH"),
            (r"scp.*-r.*\*", "Data exfiltration: recursive SCP"),
            
            # Systemd/init manipulation
            (r"systemctl.*enable.*\.service", "System: enabling systemd service"),
            (r"/etc/systemd/system/", "System: systemd unit file modification"),
            (r"update-rc\.d", "System: SysV init modification"),
            (r"@reboot", "System: cron job at reboot"),
            
            # Kernel module manipulation
            (r"modprobe", "System: kernel module loading"),
            (r"insmod", "System: kernel module insertion"),
            (r"/lib/modules/", "System: kernel module directory access"),
            
            # Compiler/build chain manipulation
            (r"gcc.*-fPIC.*-shared", "Build: creating shared library (could be malicious)"),
            (r"LD_PRELOAD=", "Build: LD_PRELOAD manipulation (function hijacking)"),
        ]
        
        # ========================================================================
        # INFORMATIONAL PATTERNS - Good to know but not necessarily bad
        # ========================================================================
        info_patterns = [
            (r"sudo\s+", "Info: sudo usage detected"),
            (r"git\s+clone", "Info: git clone detected"),
            (r"make\s+install", "Info: make install detected"),
            (r"pip\s+install", "Info: pip install detected"),
            (r"npm\s+install", "Info: npm install detected"),
            (r"cargo\s+install", "Info: cargo install detected"),
        ]
        
        # ========================================================================
        # SCAN PATTERNS LINE BY LINE
        # ========================================================================
        for i, line in enumerate(lines, 1):
            # Skip comments and empty lines for pattern matching
            stripped_line = line.strip()
            if stripped_line.startswith('#') or not stripped_line:
                continue
            
            # Check dangerous patterns (red flags)
            for pattern, message in dangerous_patterns:
                if re.search(pattern, line, re.IGNORECASE):
                    logger.warning(f"Red flag found at line {i}: {message}")
                    red_flags.append({
                        "line": i,
                        "content": line.strip()[:100],  # Limit length for output
                        "issue": message,
                        "severity": "CRITICAL"
                    })
            
            # Check suspicious patterns
            for pattern, message in suspicious_patterns:
                if re.search(pattern, line, re.IGNORECASE):
                    logger.info(f"Warning found at line {i}: {message}")
                    warnings.append({
                        "line": i,
                        "content": line.strip()[:100],
                        "issue": message,
                        "severity": "WARNING"
                    })
            
            # Check informational patterns
            for pattern, message in info_patterns:
                if re.search(pattern, line, re.IGNORECASE):
                    info.append({
                        "line": i,
                        "content": line.strip()[:100],
                        "issue": message,
                        "severity": "INFO"
                    })
        
        # ========================================================================
        # ANALYZE SOURCE URLs
        # ========================================================================
        source_urls = re.findall(r'source=\([^)]+\)|source_\w+=\([^)]+\)', pkgbuild_content, re.MULTILINE)
        suspicious_domains = []
        
        # Known suspicious TLDs and patterns
        suspicious_tlds = ['.tk', '.ml', '.ga', '.cf', '.gq', '.cn', '.ru']
        suspicious_url_patterns = [
            (r'bit\.ly|tinyurl|shorturl', "URL shortener (hides true destination)"),
            (r'pastebin|hastebin|paste\.ee', "Paste site (common for malware hosting)"),
            (r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', "Raw IP address (suspicious)"),
        ]
        
        for source_block in source_urls:
            # Extract URLs from source array
            urls = re.findall(r'https?://[^\s\'"]+', source_block)
            
            for url in urls:
                try:
                    parsed = urlparse(url)
                    domain = parsed.netloc.lower()
                    
                    # Check for suspicious TLDs
                    if any(domain.endswith(tld) for tld in suspicious_tlds):
                        warnings.append({
                            "line": 0,
                            "content": url,
                            "issue": f"Suspicious domain TLD: {domain}",
                            "severity": "WARNING"
                        })
                        suspicious_domains.append(domain)
                    
                    # Check for suspicious URL patterns
                    for pattern, message in suspicious_url_patterns:
                        if re.search(pattern, url, re.IGNORECASE):
                            warnings.append({
                                "line": 0,
                                "content": url,
                                "issue": message,
                                "severity": "WARNING"
                            })
                except Exception as e:
                    logger.debug(f"Failed to parse URL {url}: {e}")
        
        # ========================================================================
        # DETECT BINARY DOWNLOADS
        # ========================================================================
        binary_extensions = ['.bin', '.exe', '.AppImage', '.deb', '.rpm', '.jar', '.apk']
        for ext in binary_extensions:
            if ext in pkgbuild_content.lower():
                warnings.append({
                    "line": 0,
                    "content": "",
                    "issue": f"Binary file type detected: {ext}",
                    "severity": "WARNING"
                })
        
        # ========================================================================
        # CALCULATE RISK SCORE
        # ========================================================================
        # Risk scoring: red_flags = 50 points each, warnings = 5 points each, cap at 100
        risk_score = min(100, (len(red_flags) * 50) + (len(warnings) * 5))
        
        # ========================================================================
        # GENERATE RECOMMENDATION
        # ========================================================================
        if len(red_flags) > 0:
            recommendation = "❌ DANGEROUS - Critical security issues detected. DO NOT INSTALL."
            safe = False
        elif len(warnings) >= 5:
            recommendation = "⚠️  HIGH RISK - Multiple suspicious patterns detected. Review carefully before installing."
            safe = False
        elif len(warnings) > 0:
            recommendation = "⚠️  CAUTION - Some suspicious patterns detected. Manual review recommended."
            safe = True  # Technically safe but needs review
        else:
            recommendation = "✅ SAFE - No critical issues detected. Standard review still recommended."
            safe = True
        
        logger.info(f"PKGBUILD analysis complete: {len(red_flags)} red flags, {len(warnings)} warnings, risk score: {risk_score}")
        
        return {
            "safe": safe,
            "red_flags": red_flags,
            "warnings": warnings,
            "info": info,
            "risk_score": risk_score,
             "suspicious_domains": list(set(suspicious_domains)),
             "recommendation": recommendation,
             "summary": {
                 "total_red_flags": len(red_flags),
                 "total_warnings": len(warnings),
                 "total_info": len(info),
                 "lines_analyzed": len(lines)
             }
         }
  • add_aur_warning() utility that wraps results with prominent safety warning about AUR packages being user-produced and potentially unsafe.
                return create_error_response(
                    "NotFound",
                    f"AUR package '{package_name}' not found"
                )
            
            package_info = _format_package_info(results[0], detailed=True)
            
            logger.info(f"Successfully fetched info for {package_name}")
            
            # Wrap with safety warning
            return add_aur_warning(package_info)
            
    except httpx.TimeoutException:
        logger.error(f"AUR info fetch timed out for: {package_name}")
        return create_error_response(
            "TimeoutError",
            f"AUR info fetch timed out for package: {package_name}"
        )
    except httpx.HTTPStatusError as e:
        logger.error(f"AUR info HTTP error: {e}")
        return create_error_response(
            "HTTPError",
            f"AUR info fetch failed with status {e.response.status_code}",
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare readOnlyHint=true, so the description's 'security audit' label is consistent. The description adds details about what each action does (e.g., scanning for red flags, evaluating trustworthiness). However, it does not disclose whether network calls are needed for metadata analysis or other runtime behaviors. It is adequate but not comprehensive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, starting with a clear heading and bullet points for actions. It includes examples and a warning. Every sentence adds value. It is well-structured and not overly verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of an output schema, the description should indicate what the tool returns (e.g., a report, JSON, or list of issues). It does not. The tool has four parameters, including a nested object, but the description does not explain the return format or provide guidance on using the package_info parameter. The overall context is incomplete for a comprehensive audit tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema covers all parameters with descriptions. The description adds significant context beyond the schema: 'scan PKGBUILD for 50+ red flags' for pkgbuild_analysis and 'evaluate trustworthiness via votes/maintainer/age' for metadata_risk. Examples also clarify parameter usage. This goes beyond the schema's minimal descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it performs security audits for AUR packages with two specific actions (pkgbuild_analysis and metadata_risk). Examples further clarify usage. It distinguishes from siblings like verify_package_integrity by focusing on AUR. However, it could be more explicit about differentiating from other analysis tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage before installing AUR packages with '⚠️ Always audit AUR packages before installing.' It provides examples for each action but does not explicitly state when not to use or mention alternatives like verify_package_integrity for official packages. The guidance is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nihalxkumar/arch-linux-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server