Back to skill

Security audit

Skill Doctor

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but it needs Review because it recommends persistent plaintext API-key storage without enforcing private file permissions.

Before installing, treat --deep as optional and avoid putting a valuable Anthropic API key in ~/.skill-doctor/config.json unless you manually restrict the directory and file permissions. Prefer an environment variable or credential store if possible, and do not use the documented --break-system-packages install path for matplotlib; use a virtual environment instead.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/checkup.py:27
Finding

Anthropic API Key Stored Without Enforced Restrictive Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/checkup.py, lines 27-42; references/deep-analysis-setup.md, lines 31-39
Vulnerability Type: Plaintext credential storage with insecure default permissions
Risk Level: Medium

Vulnerable Code

python
def ensure_dirs():
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    if not CONFIG_FILE.exists():
        CONFIG_FILE.write_text(json.dumps(
            {"slugs": [], "plugins": [], "anthropic_api_key": None}, indent=2
        ))


def load_config():
    ensure_dirs()
    try:
        return json.loads(CONFIG_FILE.read_text())

The documented configuration directs users to place the credential directly in this file:

json
{
  "slugs": ["your-skill-slug"],
  "plugins": ["@you/your-plugin"],
  "anthropic_api_key": "sk-ant-..."
}

Technical Analysis

Path.mkdir() and Path.write_text() do not explicitly enforce owner-only permissions. Their effective permissions depend on the process umask. Under a common umask of 022, the directory may be created as 0755 and the configuration file as 0644, allowing other local users to read the plaintext Anthropic API key.

The documentation claims that the file is created with “default user-only permissions by the OS,” but the implementation does not guarantee that property. It also does not inspect or repair the permissions of an existing configuration file. This violates least-privilege credential handling and creates a local credential-disclosure risk.

Sending the key in the x-api-key HTTPS header to Anthropic is necessary for the explicitly enabled --deep feature and is not itself unauthorized exfiltration. The vulnerability is the persistent plaintext storage without enforced access controls.

Attack Path

  1. A user runs Skill Doctor, causing ~/.skill-doctor/config.json to be created under the user's current umas ...[truncated 992 chars]
Remediation
View remediation

Remediation Suggestions

  1. Create the configuration directory with mode 0700 and the configuration file with mode 0600.
  2. After creation, explicitly apply chmod(0o700) to the directory and chmod(0o600) to the file rather than relying on the current umask.
  3. Validate permissions whenever the configuration is loaded. Refuse to use an overly permissive credential file or repair it after warning the user.
  4. Prefer obtaining the key from an environment variable such as ANTHROPIC_API_KEY or from an operating-system credential store.
  5. If file-based storage remains supported, write updates atomically through an owner-only temporary file and then replace the destination.
  6. Correct the documentation so it does not claim that the operating system automatically guarantees user-only permissions.

Example hardening:

python
import os

def ensure_dirs():
    CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)
    os.chmod(CONFIG_DIR, 0o700)

    STATE_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)
    os.chmod(STATE_DIR, 0o700)

    if not CONFIG_FILE.exists():
        fd = os.open(CONFIG_FILE, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
        with os.fdopen(fd, "w") as config:
            json.dump(
                {"slugs": [], "plugins": [], "anthropic_api_key": None},
                config,
                indent=2,
            )
    else:
        os.chmod(CONFIG_FILE, 0o600)

T08 · Insecure Dependencies

Note
Location
README.md:49
Finding

Unpinned Package Installation Into the System-Managed Python Environment

Content
View full analysis

Vulnerability Details

File Location: README.md, line 49; scripts/chart.py, lines 20-26
Vulnerability Type: Unpinned third-party dependency and unsafe installation guidance
Risk Level: Low

Vulnerable Code and Documentation

markdown
- Optional: `matplotlib` for `--chart` (`pip install matplotlib --break-system-packages`)

The script repeats the same recommendation:

python
except ImportError:
    print(
        "⚠️  matplotlib not installed — skipping --chart.\n"
        "   Install with: pip install matplotlib --break-system-packages",
        file=sys.stderr,
    )
    return

Technical Analysis

The installation command does not pin a reviewed version or verify package integrity. It therefore installs whichever matplotlib release the user's configured package index resolves at execution time, along with unconstrained transitive dependencies.

The --break-system-packages option bypasses protections intended to prevent pip from modifying a Python environment managed by the operating system. This can create dependency conflicts and broaden the consequences of a malicious or compromised package installation.

The chart feature legitimately requires a plotting dependency, but modifying the system-managed Python environment is not the minimum privilege necessary. A virtual environment with pinned, hash-verified dependencies would provide the functionality with substantially less supply-chain and system-integrity risk.

Attack Path

  1. A user requests the optional chart feature and sees the installation instruction.
  2. The user executes pip install matplotlib --break-system-packages.
  3. pip resolves the latest available package and transitive dependencies from the user's configured index.
  4. If an upstream package account, package release, configured mirror, or local package-index configuration is compromised, malicious installation code or package content is d ...[truncated 884 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the --break-system-packages recommendation.
  2. Provide a virtual-environment installation workflow:
bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements-chart.txt
  1. Add a reviewed dependency file with an exact version pin, such as matplotlib==<reviewed-version>.
  2. For reproducible releases, pin transitive dependencies and include hashes using a lock file or a hash-checked requirements file.
  3. Document the trusted package source and recommend python -m pip so installation targets the intended interpreter.
  4. Keep charting optional and fail safely, as the current code already does, when the dependency is unavailable.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (6)

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

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

python
"   Rule-based diagnostics above are still complete and reliable.",
            file=sys.stderr,
        )
        return

    prompt = build_prompt(structured_results)
    payload = json.dumps({

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill instructs use of shell commands, local file reads/writes under ~/.skill-doctor, and optional network access to Anthropic, but it declares no explicit tool scope or permissions. That mismatch can cause an agent platform to grant broader capabilities than users expect or execute the skill without clear guardrails, increasing the chance of unintended file modification, shell execution, or outbound data transfer.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% confidence
Finding

The skill persists configuration and historical state in ~/.skill-doctor, including tracked slugs/plugins and potentially an anthropic_api_key reference, creating session persistence across runs. Persistent local state is not inherently malicious here, but it can retain sensitive metadata longer than expected and may expose secrets or user activity if file permissions are weak, backups are shared, or other local processes can read the directory.

Content

Scanner excerpt · SKILL.md (reported line 31)May include surrounding context.

Confirm a config directory exists for storing diagnostic history (used for trend detection across runs):

bash
mkdir -p ~/.skill-doctor
[ -f ~/.skill-doctor/config.json ] || echo '{"slugs":[],"plugins":[],"anthropic_api_key":null}' > ~/.skill-doctor/config.json

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/checkup.py (reported line 58)May include surrounding context.

python
def fetch_skill(slug: str) -> dict | None:
    """Fetch live data for a skill via clawhub inspect."""
    try:
        result = subprocess.run(
            ["clawhub", "inspect", slug, "--json"],
            capture_output=True, text=True, timeout=30
        )

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/checkup.py (reported line 83)May include surrounding context.

python
def fetch_plugin(name: str) -> dict | None:
    """Fetch live data for a plugin via clawhub package inspect."""
    try:
        result = subprocess.run(
            ["clawhub", "package", "inspect", name, "--json"],
            capture_output=True, text=True, timeout=30
        )

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/deep_analysis.py (reported line 16)May include surrounding context.

python
import urllib.request
import urllib.error

API_URL = "https://api.anthropic.com/v1/messages"
MODEL = "claude-sonnet-4-6"