Back to skill

Security audit

System Watchdog

Security checks for vulnerabilities and agentic risk

Overview

The skill does useful local resource monitoring, but it persistently records full process command lines, which can expose secrets passed as command arguments.

Install only if you are comfortable with a local watchdog seeing and retaining command lines for your running processes. Avoid running it with elevated privileges, consider setting SYSTEM_WATCHDOG_STATE to a protected location, and remove or redact the command field before regular use on systems where command arguments may contain credentials.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
check.sh:233
Finding

Full Process Command Lines Are Exposed and Persisted Without Redaction

Content
View full analysis

Vulnerability Details

File Location: check.sh, lines 233–258, 260, 384–400, and 404–430
Vulnerability Type: Sensitive information exposure through process enumeration and persistent state storage
Risk Level: Medium

Vulnerable Code

python
ps_out = run(["ps", "axo", "pid=,ppid=,pcpu=,rss=,etime=,comm=,args="])
processes = []
for line in ps_out.strip().split('\n'):
    parts = line.split(None, 6)
    if len(parts) < 7:
        continue
    pid, ppid, cpu, rss, elapsed, comm, args = parts
    pid = int(pid)
    ppid = int(ppid)
    cpu = float(cpu)
    rss_kb = int(rss)
    elapsed_secs = parse_elapsed(elapsed)
    command = args.strip()
    first_arg = command.split()[0] if command else ""
    name = os.path.basename(comm).strip() or os.path.basename(first_arg).strip() or comm.strip() or "unknown"
    # macOS sometimes truncates comm to 2 chars — fall back to first arg
    if len(name) <= 2 and first_arg:
        name = os.path.basename(first_arg).strip() or name
    processes.append({
        "pid": pid,
        "ppid": ppid,
        "name": name,
        "command": command,
        "cpu_pct": round(cpu, 1),
        "mem_mb": rss_kb // 1024,
        "elapsed_secs": elapsed_secs,
        "elapsed": human_elapsed(elapsed_secs),
    })

top_processes = sorted(processes, key=lambda p: (p["mem_mb"], p["cpu_pct"]), reverse=True)[:10]

The collected command lines are returned in the JSON output:

python
result = {
    "suspicious": len(issues) > 0,
    "verdict": verdict,
    "os": platform.system(),
    "summary": summary,
    "issues": issues,
    "top_processes": top_processes,
    "ignored_normals": ignored,
}
print(json.dumps(result, indent=2))

They are also stored for every enumerated process:

python
state = {
    "timestamp": int(time.time()),
    "system": {
        "swap_used_mb": swap_used_mb,
        
...[truncated 3998 chars]
Remediation
View remediation

Remediation Suggestions

  1. Stop requesting or retaining full process arguments. Use comm for executable identification and collect only PID, PPID, CPU, RSS, and elapsed time.

    python
    ps_out = run(["ps", "axo", "pid=,ppid=,pcpu=,rss=,etime=,comm="])
    
  2. Remove the command field from processes, top_processes, anomaly details, and persistent state. Persist only the values required for cross-run comparisons:

    python
    "processes": {
        str(p["pid"]): {
            "mem_mb": p["mem_mb"],
            "cpu_pct": p["cpu_pct"],
            "name": p["name"],
        }
        for p in processes
    }
    
  3. If arguments are operationally necessary, make their collection opt-in and redact credential-bearing options, URLs, authorization headers, and environment-like values before output or storage. Prefer an allowlist of explicitly safe arguments over pattern-based removal alone.

  4. Create the state directory and file with restrictive permissions. Use directory mode 0700, write through a temporary file opened with mode 0600, flush it, and atomically replace the destination.

  5. Correct permissions on existing installations and delete previously generated state or log files that may contain secrets.

  6. Document that monitoring output must not be sent to untrusted logs or consumers, and run the watchdog with the least-privileged account capable of collecting the required metrics.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill invokes a shell script, reads environment variables, and documents writing persistent state, but the manifest declares no explicit tool scope or allowed-tools constraints. That increases the attack surface because an agent may grant broader shell/file capabilities than necessary, making unintended command execution or filesystem access easier if the skill or its dependencies are modified or misused.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script persists a state file under the user's home directory and later stores per-process metadata including full command lines. Command-line arguments frequently contain sensitive material such as file paths, internal hostnames, access tokens, API keys, or credentials, so retaining them on disk creates an avoidable privacy and secret-exposure risk. In this monitoring context, the risk is more credible because the skill scans all processes and records them across runs, expanding visibility into unrelated workloads.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The skill states that it persists monitoring state under the user's home directory but does not explicitly warn the user that local data will be stored across runs. While the stored data appears operational rather than highly sensitive, silent persistence can create privacy and transparency issues and may retain process/resource history longer than a user expects.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.