Back to skill

Security audit

hekouwang-claude-skill-doctor-skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a disclosed SKILL.md checker, but its optional trigger-evaluation script can run untrusted skill descriptions through a real Claude CLI session with broad inherited access.

Use the static check.py workflow as the safer default. Do not run scripts/trigger_eval.py on third-party or untrusted skills unless you isolate it in a disposable sandbox with a minimal environment, least-privileged Claude/API credentials, and strict tool/network controls.

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

Error
Location
scripts/trigger_eval.py:97
Finding

Untrusted Skill Descriptions Execute as Instructions in a Tool-Enabled Agent

Content
View full analysis

Vulnerability Details

File Location: scripts/trigger_eval.py, lines 97–109 and 122–148
Vulnerability Type: Untrusted content executed as agent instructions without adequate sandboxing
Risk Level: High

Vulnerable Code

python
def _write_probe(skills_root: Path, name: str, description: str) -> None:
    """把一段 description 装成一个匿名的 project 级 skill。"""
    d = skills_root / name
    d.mkdir(parents=True, exist_ok=True)
    one_line = " ".join(description.split())
    (d / "SKILL.md").write_text(
        f"---\nname: {name}\ndescription: {one_line}\n---\n\n"
        f"# {name}\n\n本 skill 负责:{one_line}\n",
        encoding="utf-8",
    )
python
project_root = tempfile.mkdtemp(prefix=f"trigeval-{unique_id}-", dir=root_base)
skills_root = Path(project_root) / ".claude" / "skills"

try:
    _write_probe(skills_root, probe_name, description)
    for i, alt_desc in enumerate(distractors or [], start=1):
        _write_probe(skills_root, f"alt-{unique_id}-{i}", alt_desc)

    cmd = [
        "claude", "-p", query,
        "--output-format", "stream-json",
        "--verbose",
        "--include-partial-messages",
        # 只加载 project 级设置,隔离掉已装的真 skill(第 1 条)
        "--setting-sources", "project",
        # 只关心触没触发,不需要模型真把活干完
        "--disallowedTools", "Write", "Edit", "NotebookEdit",
    ]
    if model:
        cmd.extend(["--model", model])

    # 剥掉 CLAUDECODE,否则嵌套调用被守卫拦下
    env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"}

    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                            stderr=subprocess.DEVNULL,
                            cwd=project_root, env=env)

Technical Analysis

The evaluator accepts a Skill directory through --skill-path, extracts its frontmatter description, and copies that description into a newly generated probe Skill. The attacker-controlled description is placed in both the probe frontmatter and its instruction body.

The probe is then loaded by a real `cla ...[truncated 2658 chars]

Remediation
View remediation

Remediation Suggestions

  1. Do not execute untrusted descriptions as Skill instructions.

    • Keep the tested description as inert data.
    • Generate a fixed, trusted probe body that contains no content copied from the audited Skill.
    • If the host requires the description in frontmatter for discovery testing, ensure it is never repeated in an instruction body.
  2. Apply a strict tool allowlist.

    • Permit only the minimum capability required to observe whether the probe was selected.
    • Explicitly deny shell, filesystem reads, network access, MCP tools, and all other unrelated tools.
    • Do not rely on a short denylist because new or host-specific tools may remain available.
  3. Sanitize the subprocess environment.

    • Construct a minimal environment from an explicit allowlist rather than inheriting os.environ.
    • Remove API keys, cloud credentials, SSH-related variables, proxy credentials, and unrelated service tokens.
    • Supply authentication needed for the evaluator through an isolated, least-privileged account.
  4. Use operating-system isolation.

    • Run each evaluation in a disposable container or equivalent sandbox.
    • Mount no host directories other than explicitly required inert inputs.
    • Use a non-root user, a read-only filesystem, process restrictions, and resource limits.
    • Disable network access unless it is indispensable to the evaluation.
  5. Add a trust warning and safe default.

    • Clearly state that third-party Skill descriptions are untrusted instructions.
    • Require an explicit unsafe-mode option before evaluating untrusted Skills without full sandboxing.
    • Default to static analysis when adequate isolation is unavailable.
  6. Add adversarial regression tests.

    • Test descriptions that request shell execution, environment inspection, sensitive-file reads, and network transmission.
    • Verify that none of these actions can occur even when the probe is selected.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (39)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description is narrowly about a skill that evaluates one Agent Skill/SKILL.md for structural and quality best practices. The provided code chunk is not that checker itself; it is a wrapper script for a broader 'doctor suite'. Besides invoking the skill-doctor, it also invokes md-doctor and env-doctor, scans multiple directories for all skills in a project, treats external symlinked skills differently, and returns a suite-level pass/fail exit status suitable for gating. Those are materially broader and partly unrelated capabilities compared with the declared purpose. While the script does include skill auditing as one component, its primary behavior is orchestration of a multi-tool project/environment audit, so the description does not accurately represent this code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description presents a comprehensive SKILL.md/Agent Skill health checker that audits structure and quality best practices and can suggest or perform refactors. The supplied code chunk is instead a test script for validator behavior. It programmatically creates temporary example skills, runs check.py under different profiles, and asserts expected PASS/FAIL outcomes for specific validation rules (allowed frontmatter, TODO detection, naming constraints, and extension-field compatibility). That is a materially different primary purpose from the declared end-user auditing/refactoring skill, so this should be flagged as a mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

声明描述的是一个面向 Agent Skill/SKILL.md 质量审查与重构建议的“体检器”。但实际代码块并没有分析任何 skill 描述、结构、篇幅、拆分、安全项或可移植性;它只是一个测试脚本,专门验证另一个聚合脚本 run-all-doctors.sh 在若干错误场景下是否正确失败。虽然这可能属于某个更大“doctor”项目的测试基础设施,但就该代码块本身而言,其主要行为与声明用途明显不一致。

Content

No source excerpt is available for this finding.

Vague Triggers

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The activation text is broad enough to trigger on many generic 'review my skill' requests, which can cause the model to load and follow an overpowered or irrelevant instruction set when another skill would be more appropriate. In agent ecosystems, overly broad routing increases the chance of instruction hijacking, unintended file operations, or degraded decision quality because this skill positions itself as applicable to almost any skill-review request.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **署名**:报告结尾固定带 `—— 会勇禾口王的AI笔记 · @huiyonghkw`。命令行 `check.py` 的报告页脚已内置该署名。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **署名**:报告结尾固定带 `—— 会勇禾口王的AI笔记 · @huiyonghkw`。命令行 `check.py` 的报告页脚已内置该署名。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **署名**:报告结尾固定带 `—— 会勇禾口王的AI笔记 · @huiyonghkw`。命令行 `check.py` 的报告页脚已内置该署名。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **署名**:报告结尾固定带 `—— 会勇禾口王的AI笔记 · @huiyonghkw`。命令行 `check.py` 的报告页脚已内置该署名。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **署名**:报告结尾固定带 `—— 会勇禾口王的AI笔记 · @huiyonghkw`。命令行 `check.py` 的报告页脚已内置该署名。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **署名**:报告结尾固定带 `—— 会勇禾口王的AI笔记 · @huiyonghkw`。命令行 `check.py` 的报告页脚已内置该署名。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **署名**:报告结尾固定带 `—— 会勇禾口王的AI笔记 · @huiyonghkw`。命令行 `check.py` 的报告页脚已内置该署名。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
1. **确认目标**:用户没指明就用当前目录;说了某个 skill 就用那个 skill 目录的绝对路径(目录里要有 `SKILL.md`)。若传进来是 `~/.claude/skills/` 这种父目录,脚本会提示里面有哪些 skill,逐个体检。

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · check.py (reported line 27)May include surrounding context.

python
注意: 本脚本只做"机器能确定的部分"。description 触发质量、正文是不是"图书馆"、
是不是在替模型补它已会的知识——这些需人/模型读正文定夺,交给 SKILL.md 的定性复核。
脚本绝不读取任何 .env / *.key / *.pem 等密钥文件。
"""

import os

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/trigger_eval.py (reported line 144)May include surrounding context.

python
cmd.extend(["--model", model])

        # 剥掉 CLAUDECODE,否则嵌套调用被守卫拦下
        env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"}

        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                stderr=subprocess.DEVNULL,

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · check.py (reported line 496)May include surrounding context.

python
# 这些文件必须存在,但 Doctor 不能打开它们。
fake_prefix='sk-'
fake_suffix='ant-should-not-be-read-123456789012345'
printf 'api_key="%s%s"\n' "$fake_prefix" "$fake_suffix" > "$TMP/nested/.env"
printf 'api_key="%s%s"\n' "$fake_prefix" "$fake_suffix" > "$TMP/nested/private.secret.txt"

if ! python3 "$CHECK" "$TMP/nested" --json > "$TMP/nested.json"; then

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test-frontmatter.sh (reported line 21)May include surrounding context.

sh
# 这些文件必须存在,但 Doctor 不能打开它们。
fake_prefix='sk-'
fake_suffix='ant-should-not-be-read-123456789012345'
printf 'api_key="%s%s"\n' "$fake_prefix" "$fake_suffix" > "$TMP/nested/.env"
printf 'api_key="%s%s"\n' "$fake_prefix" "$fake_suffix" > "$TMP/nested/private.secret.txt"

if ! python3 "$CHECK" "$TMP/nested" --json > "$TMP/nested.json"; then

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test-frontmatter.sh (reported line 22)May include surrounding context.

sh
fake_prefix='sk-'
fake_suffix='ant-should-not-be-read-123456789012345'
printf 'api_key="%s%s"\n' "$fake_prefix" "$fake_suffix" > "$TMP/nested/.env"
printf 'api_key="%s%s"\n' "$fake_prefix" "$fake_suffix" > "$TMP/nested/private.secret.txt"

if ! python3 "$CHECK" "$TMP/nested" --json > "$TMP/nested.json"; then
  echo '❌ BOM + 嵌套 metadata 的合法 Skill 被阻断'

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding

Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding

Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding

Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding

Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding

Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The README says users can trigger the skill by 'directly using natural language' and gives examples like '帮我体检...这个 skill' and '我的 SKILL.md 规范吗', but it does not define a constrained trigger list or when similar everyday phrasing should not activate the skill. This ambiguity can cause unintended invocation overlap with general help or review requests.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

Mixing specific trigger examples with an open-ended catch-all makes invocation boundaries unclear, which can bias the host model toward selecting this skill for loosely related requests. In a multi-skill environment, ambiguous routing is dangerous because it can overshadow more specialized skills and expand the effective reach of the instructions beyond their intended scope.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.