Back to skill

Security audit

github-pages-publish

Security checks for vulnerabilities and agentic risk

Overview

The skill has a legitimate GitHub Pages publishing purpose, but its uploader can unintentionally publish hidden files or symlinked files outside the chosen folder to a public site.

Install only if you are comfortable with an agent publishing files to a public GitHub repository. Use a clean, reviewed deployment folder with no symlinks, hidden secrets, .env files, private keys, or customer/private data. Prefer a fine-grained GitHub token limited to the target repository with only needed write permissions, revoke it afterward, and assume files with the same remote path may be replaced.

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/deploy.py:101
Finding

Site Upload Can Follow File Symlinks and Publicly Expose Data Outside the Selected Directory

Content
View full analysis

Vulnerability Details

File Location: scripts/deploy.py:101-105, 112-120
Vulnerability Type: Unrestricted file traversal through symbolic links
Risk Level: High

Vulnerable Code

python
def upload_directory(owner, repo, dir_path, branch, token):
    """Incrementally upload a whole directory via the contents API.

    Creates/updates files present locally while leaving any remote files that
    are not in the local directory untouched. This is a per-file sync (no
    `git push --force`), so it can never wipe out pre-existing remote content.
    """
    count = 0
    for root, dirs, files in os.walk(dir_path):
        dirs[:] = [d for d in dirs if d != ".git"]
        for fname in files:
            local_path = os.path.join(root, fname)
            remote_path = os.path.relpath(local_path, dir_path).replace(os.sep, "/")
            upload_single_file(owner, repo, local_path, remote_path, branch, token)
            count += 1
    print("[OK] Uploaded {} file(s)".format(count))
python
def upload_single_file(owner, repo, local_file, remote_path, branch, token):
    """Upload one file to an existing repo via the contents API (no git needed).

    Auto-handles update-vs-create: fetches the file's SHA first if it exists.
    """
    with open(local_file, "rb") as f:
        raw = f.read()
    payload = {
        "message": "Add " + remote_path,
        "content": base64.b64encode(raw).decode("ascii"),
        "branch": branch,
    }

Technical Analysis

The directory uploader recursively selects every file other than content under a directory named .git. It does not reject symbolic-link files, inspect each file with lstat(), or verify that each resolved file path remains under the resolved deployment root.

Although os.walk() does not follow directory links by default, a symbolic link to a regular file still appears in files. The subsequent open(local_file, "rb") follows that link and reads its target. The target may th ...[truncated 2230 chars]

Remediation
View remediation

Remediation Suggestions

  1. Resolve the deployment root once with os.path.realpath() or pathlib.Path.resolve().
  2. Inspect every candidate with os.lstat() or Path.is_symlink() and reject symbolic links by default.
  3. Resolve every candidate file and verify containment using os.path.commonpath() rather than string-prefix checks.
  4. Reject any file whose resolved path is outside the resolved deployment root.
  5. Add deny-by-default exclusions for sensitive names and patterns, including .env, .env.*, private-key extensions, credential files, .git, and platform-specific secret stores.
  6. Require explicit confirmation before uploading hidden files or files matching secret-detection patterns.
  7. Display a complete upload manifest before network transmission, including warnings for hidden, unusually large, or potentially sensitive files.
  8. Apply the same resolved-path validation to --file, since a directly supplied file path may also be a symlink.
  9. Add automated tests covering file symlinks, directory symlinks, relative links, broken links, links escaping through parent directories, and normal in-root files.

A suitable containment check should follow this pattern:

python
site_root = os.path.realpath(dir_path)
candidate = os.path.join(root, fname)

if os.path.islink(candidate):
    raise ValueError("Symbolic links are not allowed: " + candidate)

resolved = os.path.realpath(candidate)
if os.path.commonpath([site_root, resolved]) != site_root:
    raise ValueError("File resolves outside deployment root: " + candidate)

For stronger assurance, open files with platform-supported no-follow semantics, such as os.O_NOFOLLOW, to reduce time-of-check/time-of-use risks where available.

Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • 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 (8)

Credential Access

High
Category
Privilege Escalation
Confidence
77% confidence
Finding

The skill explicitly instructs users to provide or use personal access tokens and discusses token sources from CLI arguments and environment variables. While token use is expected for GitHub deployment, encouraging broad PAT handling inside a skill increases credential exposure risk if the agent logs arguments, reads unrelated environment values, or prompts users to paste secrets into chat.

Content

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

md
- **刚部署 404**:首次部署需 1–3 分钟;用 REST API `GET /repos/{owner}/{repo}/pages` 查 `status`(building → built),轮询直到 built 再验证。确认仓库 public;确认 `index.html` 在发布分支根目录。
- **上传返回 403 `Resource not accessible by integration`**:GitHub MCP 连接器是只读权限,改用 PAT(通道 B)。
- **上传返回 403 `Resource not accessible by personal access token`**:PAT 缺写入权限——fine-grained token 需把 Contents 设为 Read and write,或改用 classic token 勾 `repo`。
- **更新不生效**:可能是浏览器/CDN 缓存,稍等或强制刷新;确认 push 到了正确的发布分支。
- **私有仓库能否用 Pages**:免费版不行,需 GitHub Pro 等付费订阅。
- **URL 路径**:项目页 URL 包含仓库名,路径区分大小写。

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

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

md
Creates/updates files present locally while leaving any remote files that
    are not in the local directory untouched. This is a per-file sync (no
    `git push --force`), so it can never wipe out pre-existing remote content.
    """
    count = 0
    for root, dirs, files in os.walk(dir_path):

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · scripts/deploy.py (reported line 92)May include surrounding context.

python
Creates/updates files present locally while leaving any remote files that
    are not in the local directory untouched. This is a per-file sync (no
    `git push --force`), so it can never wipe out pre-existing remote content.
    """
    count = 0
    for root, dirs, files in os.walk(dir_path):

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The example trigger phrases are broad, everyday requests like '把这个网页发到网上' and '生成一个能分享的网页链接', which can overlap with normal conversation and cause the skill to activate when the user did not explicitly intend a public deployment. In this skill’s context, accidental activation is more dangerous because the action publishes content to GitHub Pages, creating a public URL and potentially exposing non-sensitive-but-private material.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill describes behavior that reads environment variables, local files, and performs network operations, but it does not declare any explicit tool scope or permissions boundary. That creates an authorization gap: an agent runtime may permit broader-than-expected capability use, making it harder to constrain or review sensitive actions such as reading tokens from the environment and publishing content remotely.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill's display name and full description are written in Chinese, and the document continues entirely in Chinese without indicating that users may choose another language. This creates a natural-language locale constraint that is not documented as optional or region-specific, which matches the policy's language/locale violation criterion.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The docstring materially overstates safety: although the code does avoid deleting remote files that are absent locally, it absolutely can overwrite pre-existing remote content when a local file has the same path. In a deployment tool, that misleading claim can cause operators to use it under false assumptions and unintentionally replace published content.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
71% confidence
Finding

全文说明和示例均固定为中文,且未说明是否支持其他语言或允许用户选择语言。按 SQP-3,若技能在自然语言层面隐含强制特定语言而没有 opt-in 或合理限定,属于语言/locale 策略风险。

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.