Back to skill

Security audit

System Doctor

Security checks for vulnerabilities and agentic risk

Overview

The skill is a mostly coherent system diagnostic tool, but it quietly runs broader diagnostics and a google.com DNS lookup even when the user selects a narrower check, despite claiming to be fully offline.

Review before installing in monitored, private, or air-gapped environments. The tool should be changed so each --check option invokes only its matching diagnostic, and the google.com DNS test should be removed or made explicit and opt-in. Pin psutil if using the macOS fallback.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/sys_doctor.py:428
Finding

Selected Diagnostic Scope Is Bypassed and Causes Undocumented Network Activity

Content
View full analysis

Vulnerability Details

File Location: scripts/sys_doctor.py, lines 223-231 and 428-445
Vulnerability Type: Excessive data collection beyond the selected diagnostic scope
Risk Level: Medium

Vulnerable Code

python
# DNS resolution test
try:
    start = time.time()
    result = subprocess.run(
        ["getent", "hosts", "google.com"],
        capture_output=True, text=True, timeout=5
    )
    elapsed = time.time() - start
    if result.returncode == 0:
        results["dns_resolution_ms"] = f"{elapsed*1000:.0f}"
except Exception:
    pass
python
check_map = {
    "disk": {"disk": check_disk()},
    "memory": {"memory": check_memory()},
    "cpu": {"cpu": check_cpu()},
    "network": {"network": check_network()},
    "services": {"services": check_services()},
    "all": check_all(),
}

data = check_map.get(args.check, check_map["all"])
if args.check == "all":
    data = check_all()

Technical Analysis

Python evaluates all dictionary values when the dictionary is constructed. The check_map values therefore invoke every diagnostic function immediately, rather than invoking only the function corresponding to args.check.

For example, running the script with --check disk still executes:

  • check_memory()
  • check_cpu()
  • check_network()
  • check_services()
  • check_all()

check_all() additionally collects system information and logged-in-user information. As a result, all diagnostic categories can be collected even when the user explicitly requests only one category. Only the selected category is subsequently placed in the visible output, making the additional activity non-obvious.

Execution of check_network() also invokes getent hosts google.com. This can issue a DNS query through the host's configured resolver. That behavior conflicts with the documentation in SKILL.md, which describes each --check option as checking only the selected category and states that the utility makes no external API calls ...[truncated 2019 chars]

Remediation
View remediation

Remediation Suggestions

Replace eagerly evaluated dictionary values with function references and invoke only the requested checker:

python
check_map = {
    "disk": check_disk,
    "memory": check_memory,
    "cpu": check_cpu,
    "network": check_network,
    "services": check_services,
}

if args.check == "all":
    data = check_all()
else:
    checker = check_map[args.check]
    data = {args.check: checker()}

Additional hardening should include:

  1. Remove the DNS-resolution test from default execution.
  2. If connectivity testing is required, place it behind an explicit option such as --test-dns.
  3. Allow the target hostname to be configured rather than hardcoding google.com.
  4. Clearly document that the DNS test generates network traffic.
  5. Ensure check_all() is invoked only once.
  6. Add automated tests that mock each checker and verify that --check disk, --check memory, and other scoped modes invoke exactly one diagnostic function.
  7. Update the offline-operation claim if any network-dependent check remains enabled.

T08 · Insecure Dependencies

Note
Location
SKILL.md:80
Finding

Unpinned Third-Party Dependency Installation Instruction

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 80-81
Vulnerability Type: Unpinned third-party dependency and mutable package resolution
Risk Level: Low

Vulnerable Code

markdown
- **Linux** (primary) — uses `/proc/`, `df`, `free`, `ip`, `systemctl`
- **macOS** — partial support via `psutil` fallback (install: `pip install psutil`)

Technical Analysis

The documentation instructs users to install psutil without specifying a reviewed version or verifying package integrity. The command resolves whichever version is current on the user's configured Python package index at installation time.

Although psutil is a legitimate package and no malicious dependency was found in the audited project, an unpinned installation instruction introduces mutable supply-chain behavior. Installation results can vary over time and across configured indexes. Relevant risks include:

  • A future compromised or malicious package release
  • A compromised or attacker-controlled package index
  • Unexpected behavior from an incompatible future release
  • Environment-specific dependency substitution through index configuration

The script imports psutil only in the non-Linux memory-checking fallback. The risk therefore primarily affects users who follow the macOS installation instruction.

Attack Path

  1. A macOS or other non-Linux user follows the documented setup command:
    bash
    pip install psutil
    
  2. pip queries the package indexes configured in the user's environment.
  3. The resolver selects the currently available package version without a project-enforced version or hash.
  4. If the configured index or selected release is compromised, package installation or later import can execute attacker-controlled code under the installing user's privileges.
  5. If installation is performed with elevated privileges, the resulting impact can extend to system-wide Python files and other resources writable by that account.

Impact Assessme

...[truncated 630 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pin psutil to a reviewed version:
    bash
    python3 -m pip install "psutil==REVIEWED_VERSION"
    
  2. Provide a locked requirements file with integrity hashes:
    text
    psutil==REVIEWED_VERSION \
        --hash=sha256:REVIEWED_DISTRIBUTION_HASH
    
  3. Recommend installation in an isolated virtual environment:
    bash
    python3 -m venv .venv
    . .venv/bin/activate
    python3 -m pip install --require-hashes -r requirements.txt
    
  4. Document the expected package index and advise users not to install from untrusted mirrors.
  5. Avoid recommending privileged or system-wide pip installation.
  6. Periodically review and update the pinned version and hashes through a controlled dependency-update process.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (11)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill advertises and demonstrates shell execution and file-writing behavior (running a Python diagnostic script and generating HTML output), but it does not declare any explicit tool scope such as allowed-tools or permissions. This creates a trust and containment gap: an agent or runtime may permit broader execution than reviewers expect, increasing the chance of unintended command execution or writes on the host system.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 57)May include surrounding context.

python
def _get_logged_users():
    try:
        result = subprocess.run(["who", "-q"], capture_output=True, text=True, timeout=5)
        lines = result.stdout.strip().split("\n")
        if lines and "users=" in lines[-1]:
            count = lines[-1].split("=")[1]

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 71)May include surrounding context.

python
def check_disk():
    results = []
    try:
        result = subprocess.run(
            ["df", "-h", "--type=ext4", "--type=xfs", "--type=btrfs", "--type=ext3"],
            capture_output=True, text=True, timeout=10
        )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 110)May include surrounding context.

python
results = {}
    if platform.system() == "Linux":
        try:
            result = subprocess.run(["free", "-h"], capture_output=True, text=True, timeout=5)
            lines = result.stdout.strip().split("\n")
            for line in lines[1:]:
                parts = line.split()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 177)May include surrounding context.

python
pass
    # Temperature (Linux with lm-sensors)
    try:
        result = subprocess.run(["sensors", "-j"], capture_output=True, text=True, timeout=5)
        if result.returncode == 0:
            results["sensors_available"] = True
        else:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 202)May include surrounding context.

python
# Get default gateway
    try:
        if platform.system() == "Linux":
            result = subprocess.run(["ip", "route", "show", "default"], capture_output=True, text=True, timeout=5)
            if result.stdout.strip():
                results["default_gateway"] = result.stdout.strip()
    except FileNotFoundError:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 207)May include surrounding context.

python
results["default_gateway"] = result.stdout.strip()
    except FileNotFoundError:
        try:
            result = subprocess.run(["route", "-n"], capture_output=True, text=True, timeout=5)
            lines = result.stdout.strip().split("\n")
            for line in lines[2:]:
                parts = line.split()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 219)May include surrounding context.

python
# Interface info via ip
    try:
        result = subprocess.run(["ip", "-br", "addr"], capture_output=True, text=True, timeout=5)
        interfaces = []
        for line in result.stdout.strip().split("\n"):
            parts = line.split()

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The network diagnostic performs a DNS lookup for google.com, which creates an unnecessary outbound dependency and can disclose that the tool was run, along with timing and environment details, to external infrastructure. In sensitive, air-gapped, or compliance-constrained server environments, this behavior is more dangerous because operators may expect only local inspection from a 'system doctor' skill.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
87% confidence
Finding

Although the subprocess itself is not command-injection-prone, this code performs an external lookup for google.com, causing the diagnostic tool to make outbound network requests to a third party. That can leak execution metadata, create privacy/compliance issues in restricted environments, and make a supposedly local health check depend on external infrastructure.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 236)May include surrounding context.

python
# DNS resolution test
    try:
        start = time.time()
        result = subprocess.run(
            ["getent", "hosts", "google.com"],
            capture_output=True, text=True, timeout=5
        )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sys_doctor.py (reported line 254)May include surrounding context.

python
# Check systemd services
    if platform.system() == "Linux":
        try:
            result = subprocess.run(
                ["systemctl", "list-units", "--type=service", "--state=running", "--no-pager"],
                capture_output=True, text=True, timeout=10
            )

Static analysis

No suspicious patterns detected.