Back to skill

Security audit

skill-admission-gate

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local security-scanner skill, but its own gate can fail open and its declared permissions do not match the tools and filesystem behavior it uses.

Review before installing or using this as an admission gate. It should not be trusted as a CI blocker until accepted-risk approvals are moved outside the scanned package, batch mode fails closed on BLOCK, symlinks are contained or rejected, and the manifest is corrected to match actual local execution/file access while removing unnecessary WebFetch.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scan_skill.py:115
Finding

Package-Controlled Allowlist Can Suppress Blocking Findings

Content
View full analysis

Vulnerability Details

File Location: scripts/scan_skill.py, lines 115–129 and 164–172
Vulnerability Type: Untrusted risk-acceptance policy
Risk Level: High

Vulnerable Code

python
def load_allow(root):
    """Read the accepted-risk declaration from the scan root."""
    p = os.path.join(root, ".admission-allow.json")
    if not os.path.isfile(p):
        return {}, None
    try:
        d = json.load(open(p, encoding="utf-8-sig"))
    except Exception as e:
        return {}, {"error": str(e)[:200]}
    m = {}
    for it in d.get("allow", []):
        r = it.get("rule")
        if r:
            m.setdefault(r, []).append(it)
    return m, {
        "file": ".admission-allow.json",
        "entries": sum(len(v) for v in m.values())
    }
python
kept = []
for f in findings:
    if f["rule"] in allow:
        f["accepted_by"] = allow[f["rule"]][0].get("approved_by", "?")
        f["accepted_reason"] = allow[f["rule"]][0].get("reason", "")
        accepted.append(f)
    else:
        kept.append(f)
findings = kept

Technical Analysis

The scanner loads .admission-allow.json from the directory being reviewed. For third-party package assessment, that directory and all files within it are controlled by the package author.

The implementation treats any listed rule identifier as approved without validating:

  • The identity or authority of approved_by;
  • A cryptographic signature;
  • Required approval fields;
  • Whether the approval came from an external trusted policy;
  • Whether the approval is bound to the current package fingerprint;
  • Whether it applies to a particular file, line, or finding.

During verdict calculation, every finding whose rule appears in the package-supplied allowlist is moved into accepted_risks and removed from findings. The overall verdict is then calculated only from the remaining findings. Consequently, an untrusted package can decide which detections should not affect its own admission verdict.

...[truncated 1175 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not trust approval files stored inside the package under review.
  • Store accepted-risk policy in a separate reviewer-controlled directory or policy service.
  • Require approvals to be cryptographically signed by a configured trusted signer.
  • Bind every approval to:
    • The complete package fingerprint;
    • The exact rule identifier;
    • The affected file and finding;
    • An expiration date;
    • The reviewer identity.
  • Validate the allowlist against a strict schema and reject incomplete or unknown fields.
  • Treat package-provided approval files as evidence to display, not as authorization to alter the verdict.
  • Fail closed when signature or policy validation fails.
  • Add regression tests proving that a package cannot suppress its own BLOCK findings.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/scan_skill.py:214
Finding

Batch Mode Returns Success Despite Blocked Packages

Content
View full analysis

Vulnerability Details

File Location: scripts/scan_skill.py, lines 214–231
Vulnerability Type: Security-gate fail-open behavior
Risk Level: Medium

Vulnerable Code

python
summary = {
    "generated": datetime.date.today().isoformat(),
    "root": root.replace("\\", "/"),
    "counts": {
        "total": len(results),
        "PASS": sum(1 for r in results if r["verdict"] == "PASS"),
        "WARN": sum(1 for r in results if r["verdict"] == "WARN"),
        "BLOCK": sum(1 for r in results if r["verdict"] == "BLOCK"),
    },
    "results": results,
}
if a.json:
    json.dump(
        summary,
        open(a.json, "w", encoding="utf-8-sig"),
        ensure_ascii=False,
        indent=2
    )
print("\n合计 %d | PASS %d | WARN %d | BLOCK %d" % (
    summary["counts"]["total"],
    summary["counts"]["PASS"],
    summary["counts"]["WARN"],
    summary["counts"]["BLOCK"]
))
return 0

Technical Analysis

Batch mode correctly counts packages with a BLOCK verdict but unconditionally returns exit status 0. This differs from single-package mode, which returns 1 for a blocked result.

The documented workflow presents the exit status as suitable for CI admission gating. CI systems conventionally use a zero exit status to determine whether a security step succeeded. As a result, the textual or JSON result may report blocked packages while the controlling pipeline treats the scan as successful.

This is a fail-open control-flow defect rather than evidence of intentional bypass behavior.

Attack Path

  1. A CI or release process invokes the scanner with --batch.
  2. A third-party package within the scanned root triggers one or more BLOCK findings.
  3. The scanner records the package as blocked in the batch summary.
  4. Batch mode reaches the unconditional return 0.
  5. The CI system interprets the scan as successful.
  6. Subsequent installation, packaging, or release stages proceed despite the scanner's blocking verdict.

Impact Assessm

...[truncated 349 chars]

Remediation
View remediation

Remediation Suggestions

Return a nonzero status whenever any batch result is blocked:

python
return 1 if summary["counts"]["BLOCK"] > 0 else 0

Additionally:

  • Document whether WARN should also fail CI under a strict mode.
  • Define constants for exit statuses to keep single and batch behavior consistent.
  • Add automated tests for:
    • All-PASS batches;
    • WARN-only batches;
    • Mixed PASS and BLOCK batches;
    • Multiple blocked packages;
    • JSON-output and quiet-mode combinations.
  • Ensure downstream workflows verify both the exit status and the structured verdict where practical.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/scan_skill.py:86
Finding

Package Symlinks Can Redirect Scanning Outside the Target Directory

Content
View full analysis

Vulnerability Details

File Location: scripts/scan_skill.py, lines 86–92 and 139–153
Vulnerability Type: Out-of-scope file read through symbolic links
Risk Level: Medium

Vulnerable Code

python
def scan_file(path, rel):
    findings = []
    is_doc = os.path.splitext(path)[1].lower() in DOC_EXT
    try:
        txt = open(
            path,
            encoding="utf-8-sig",
            errors="replace"
        ).read()
    except Exception:
        return findings
python
for dp, dns, fns in os.walk(root):
    dns[:] = [d for d in dns if d not in SKIP_DIRS]
    for fn in sorted(fns):
        p = os.path.join(dp, fn)
        rel = os.path.relpath(p, root).replace("\\", "/")
        ext = os.path.splitext(fn)[1].lower()
        try:
            hashes.append({
                "path": rel,
                "sha256": sha256_file(p),
                "bytes": os.path.getsize(p)
            })
        except Exception:
            pass
        if ext not in TEXT_EXT:
            continue
        if rel == ".admission-allow.json":
            continue
        files += 1
        findings += scan_file(p, rel)

Technical Analysis

The scanner assumes that files discovered beneath the lexical target path belong to the reviewed package. It does not check whether a discovered entry is a symbolic link or whether its resolved target remains under the resolved scan root.

The following operations follow file symlinks:

  • open() in sha256_file();
  • os.path.getsize();
  • open().read() in scan_file().

The extension decision is based on the package-controlled symlink filename, not the resolved target. A package can therefore give a symlink a supported text extension and cause the scanner to read and pattern-scan an arbitrary file accessible to the scanner account.

Directory symlinks are not followed by the default os.walk() behavior, but file symlinks remain exploitable.

Attack Path

  1. An attacker creates a package containing ...[truncated 1101 chars]
Remediation
View remediation

Remediation Suggestions

  • Reject symbolic links by default when scanning untrusted packages.
  • Use os.lstat() before hashing or reading each entry and skip entries for which stat.S_ISLNK() is true.
  • Resolve the scan root and each candidate with os.path.realpath().
  • Enforce containment with os.path.commonpath():
python
resolved_root = os.path.realpath(root)
resolved_file = os.path.realpath(p)

if os.path.commonpath([resolved_root, resolved_file]) != resolved_root:
    # Record a rejected out-of-scope entry without opening it.
    continue
  • Handle cross-drive commonpath errors safely on Windows.
  • Open files using platform-supported no-follow protections where available to reduce check-to-use races.
  • Do not include external-file content, hashes, or sizes in reports.
  • Add tests for relative symlinks, absolute symlinks, chained symlinks, broken links, and links targeting sensitive files outside the scan root.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (18)

Scope Creep

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The manifest denies subprocess use, but allowed-tools includes Bash and the risk rationale explicitly mentions local execution. That contradiction means the effective capability surface is broader than the declared policy, which is dangerous for a security-review skill because it could execute local commands despite claiming otherwise.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill declares network access as false while also allowing WebFetch. This is a direct policy/capability mismatch that could enable unexpected outbound requests, creating risk of data leakage or supply-chain abuse under a misleading permission model.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill declares network access as false while also allowing WebFetch. This is a direct policy/capability mismatch that could enable unexpected outbound requests, creating risk of data leakage or supply-chain abuse under a misleading permission model.

Content

No source excerpt is available for this finding.

Instruction Override

High
Category
Prompt Injection
Confidence
80% confidence
Finding

This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Content

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

md
| | EXE-03,04 | WARN | `eval(`/`exec(`, post-base64-decode exec |
| **Exfiltration** | EXF-01,02 | **BLOCK** | reading private keys / credential files, reading local vaults and key-drop tools |
| | EXF-03,04 | WARN | reading `.env`/env credentials, POST/PUT to external endpoints |
| **Prompt injection** | INJ-01,02 | **BLOCK** | "ignore previous instructions", "do not tell the user" |
| | INJ-03~05 | WARN | silent execution, bypassing security/approvals, tricking users into pasting keys |
| **Obfuscation** | OBF-01 | WARN | overlong Base64 strings (suspected payload) |
| **Scope** | SCP-01 | WARN | writing absolute paths outside the skill dir |

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

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

Content

Scanner excerpt · scripts/scan_skill.py (reported line 35)May include surrounding context.

python
("EXE-04", "WARN",  "Base64 解码后执行", r"b64decode[^\n]{0,60}(exec|eval|system)|atob\([^\n]{0,60}\)"),

    # —— 数据外泄 ——
    ("EXF-01", "BLOCK", "读取私钥/凭据文件", r"id_rsa|\.ssh/|\.aws/credentials|\.netrc|\.pgpass|\.kdbx"),
    ("EXF-02", "BLOCK", "读取本地保险库/密钥投递工具", r"ucvault|key_drop|_localmem_memories"),
    ("EXF-03", "WARN",  "读取 .env / 环境凭据", r"\.env\b|os\.environ\[[^\]]*(KEY|TOKEN|SECRET|PASS)"),
    ("EXF-04", "WARN",  "向外部端点发送数据", r"requests\.(post|put)\s*\(|curl[^\n]{0,80}-[Xd]|Invoke-RestMethod[^\n]*-Method\s+(POST|PUT)"),

Credential Access

High
Category
Privilege Escalation
Confidence
80% confidence
Finding

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

Content

Scanner excerpt · scripts/scan_skill.py (reported line 35)May include surrounding context.

python
("EXE-04", "WARN",  "Base64 解码后执行", r"b64decode[^\n]{0,60}(exec|eval|system)|atob\([^\n]{0,60}\)"),

    # —— 数据外泄 ——
    ("EXF-01", "BLOCK", "读取私钥/凭据文件", r"id_rsa|\.ssh/|\.aws/credentials|\.netrc|\.pgpass|\.kdbx"),
    ("EXF-02", "BLOCK", "读取本地保险库/密钥投递工具", r"ucvault|key_drop|_localmem_memories"),
    ("EXF-03", "WARN",  "读取 .env / 环境凭据", r"\.env\b|os\.environ\[[^\]]*(KEY|TOKEN|SECRET|PASS)"),
    ("EXF-04", "WARN",  "向外部端点发送数据", r"requests\.(post|put)\s*\(|curl[^\n]{0,80}-[Xd]|Invoke-RestMethod[^\n]*-Method\s+(POST|PUT)"),

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 · scripts/scan_skill.py (reported line 37)May include surrounding context.

python
# —— 数据外泄 ——
    ("EXF-01", "BLOCK", "读取私钥/凭据文件", r"id_rsa|\.ssh/|\.aws/credentials|\.netrc|\.pgpass|\.kdbx"),
    ("EXF-02", "BLOCK", "读取本地保险库/密钥投递工具", r"ucvault|key_drop|_localmem_memories"),
    ("EXF-03", "WARN",  "读取 .env / 环境凭据", r"\.env\b|os\.environ\[[^\]]*(KEY|TOKEN|SECRET|PASS)"),
    ("EXF-04", "WARN",  "向外部端点发送数据", r"requests\.(post|put)\s*\(|curl[^\n]{0,80}-[Xd]|Invoke-RestMethod[^\n]*-Method\s+(POST|PUT)"),

    # —— 提示注入 / 隐蔽行为(主要在 SKILL.md / 指令文本)——

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This file presents all operational guidance in Chinese and does not indicate that users or agents may choose another language. The policy requires flagging language or locale constraints when the skill forces a specific language without opt-in or justification.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger phrases include broad natural-language terms such as '这个技能安全吗' and similar generic security-review wording, which can cause unintended invocation. In a security-sensitive skill, accidental activation can expose files, produce misleading gate decisions, or interfere with other workflows.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill documentation claims it is offline-runnable with no network use, yet the manifest allows the WebFetch tool. This creates a trust-boundary mismatch: reviewers or users may approve the skill under false assumptions while the runtime capability still permits outbound access.

Content

No source excerpt is available for this finding.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill grants unrestricted tool access without appropriate constraints. An agent with unfettered tool access can perform arbitrary actions including file modification, network requests, and code execution.

Content

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

md
### I. Why it is needed
The security problem of the skill ecosystem is **not "are there malicious packages" but "there is no admission gate"**:

- Skills can execute arbitrary code, access the filesystem and make network requests — their permission surface equals a local program;
- Yet their distribution often looks like "a block of documentation" — review intuition fails;
- Once installed, it runs on your machine as you.

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 · SKILL.md (reported line 123)May include surrounding context.

skill: my-skill verdict: WARN | BLOCK 0 | WARN 2 | accepted 0 | 6 files scanned package fingerprint: cd23896eee940857 [WARN] EXF-04 script.py:41 requests.post("https://api.example.com/collect", ...)

text
Exit codes: `0` = no BLOCK; `1` = BLOCK (usable as CI gate).

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 · SKILL.md (reported line 257)May include surrounding context.

skill: my-skill verdict: WARN | BLOCK 0 | WARN 2 | accepted 0 | 6 files scanned package fingerprint: cd23896eee940857 [WARN] EXF-04 script.py:41 requests.post("https://api.example.com/collect", ...)

text
Exit codes: `0` = no BLOCK; `1` = BLOCK (usable as CI gate).

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · SKILL.md (reported line 123)May include surrounding context.

skill: my-skill verdict: WARN | BLOCK 0 | WARN 2 | accepted 0 | 6 files scanned package fingerprint: cd23896eee940857 [WARN] EXF-04 script.py:41 requests.post("https://api.example.com/collect", ...)

text
Exit codes: `0` = no BLOCK; `1` = BLOCK (usable as CI gate).

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · SKILL.md (reported line 257)May include surrounding context.

skill: my-skill verdict: WARN | BLOCK 0 | WARN 2 | accepted 0 | 6 files scanned package fingerprint: cd23896eee940857 [WARN] EXF-04 script.py:41 requests.post("https://api.example.com/collect", ...)

text
Exit codes: `0` = no BLOCK; `1` = BLOCK (usable as CI gate).

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The content is entirely presented in Chinese and gives no indication that users or agents may choose another language. This can violate language/locale policy when a skill implicitly forces a specific language without documenting an opt-in or offering alternatives.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Line L13 defines activation with broad phrases such as “这个技能安全吗” and “能不能装”. These expressions are generic enough to overlap with ordinary discussion about software safety, making unintended invocation more likely because the scope and exclusions are not clearly bounded.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This Python file contains user-facing natural-language text entirely in Chinese, including the description and usage/help strings. For a general-purpose skill scanner, forcing a single language without opt-in or a documented regional justification is a locale policy issue under SQP-3.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.prompt_injection_instructions

Prompt-injection style instruction pattern detected.

Warn
Code
suspicious.prompt_injection_instructions
Location
SKILL.md:136