Skip to content

[AI审查][Security] /delete-ex 直接执行 rm -rf exes/{slug},slug 未经校验,存在路径穿越删除任意目录的风险 #27

Description

@josephmqiu

Important

🤖 AI 代码审查声明:本 issue 由 AI 模型 Claude Fable 5(Anthropic,运行于 Claude Code)在一次完整代码审查中发现并撰写,经 @josephmqiu 授权提交。审查基于当前 main 分支 HEAD(commit c5ece53)的源码逐行阅读。本问题属于本地误操作/注入风险,非远程可利用漏洞,仓库未提供私密报告渠道(无 SECURITY.md),故直接公开提交。

问题描述

/delete-ex {slug} 的实现是让 agent 直接执行模板拼接的 shell 命令(中文版 SKILL.md 第 350-354 行,英文版第 619-622 行):

rm -rf exes/{slug}

其中 {slug} 来自用户输入,没有任何校验、转义或路径约束

风险场景

  1. 路径穿越/delete-ex ../.. 会拼接出 rm -rf exes/../..,即删除项目上层目录。如果按 README 的全局安装方式(~/.claude/skills/create-ex)运行,上层目录就是用户的 ~/.claude
  2. 误操作放大:slug 含空格或特殊字符时(如用户直接输入了中文昵称而不是 slug),拼接出的命令可能命中意料之外的路径。rm -rf 没有确认、没有回收站。
  3. LLM 执行链的固有不确定性:这条指令是让语言模型去拼接并执行 rm -rf。模型对模板的理解偏差(错误替换、多余展开)都会直接变成不可逆的删除。让模型裸拼 rm -rf 本身就是应当避免的模式。

补充:其余 Python 工具的 --slug / --base-dir 参数同样未校验(如 version_manager.py:120-121 直接 Path(args.base_dir) / args.slug),只是它们不具破坏性,风险较低。

修复建议

  1. 把删除下沉到 Python 工具里做校验,SKILL.md 不再直接拼 rm -rf,改为:
python3 ${CLAUDE_SKILL_DIR}/tools/skill_writer.py --action delete --slug {slug} --base-dir ./exes
  1. 工具内做三重防御:
import re

def validate_slug(slug: str) -> str:
    if not re.fullmatch(r"[a-z0-9][a-z0-9_-]{0,63}", slug):
        raise ValueError(f"非法 slug:{slug!r}(只允许小写字母/数字/下划线/连字符)")
    return slug

def delete_skill(base_dir: Path, slug: str) -> None:
    validate_slug(slug)
    skill_dir = (base_dir / slug).resolve()
    base_resolved = base_dir.resolve()
    # 确认目标路径确实位于 base_dir 之内,防止路径穿越
    if base_resolved not in skill_dir.parents:
        raise ValueError(f"拒绝删除 base-dir 之外的路径:{skill_dir}")
    if not (skill_dir / "meta.json").exists():
        raise ValueError(f"{skill_dir} 不是一个前任 Skill 目录(缺少 meta.json),拒绝删除")
    shutil.rmtree(skill_dir)
    print(f"已删除 {skill_dir}")
  1. SKILL.md 层面:删除前要求 agent 先展示将被删除的完整绝对路径和 meta.json 中的 name,由用户明确确认后再执行。

如需要,我可以提交对应的 PR(新增 --action delete + 更新 SKILL.md 中英文两处)。


English TL;DR: /delete-ex instructs the agent to run rm -rf exes/{slug} with a fully unsanitized user-supplied slug — /delete-ex ../.. escapes the exes directory (with global install, that's inside ~/.claude). Suggested fix: move deletion into the Python tool with slug whitelist validation, resolved-path containment check, a meta.json existence check, and explicit user confirmation; never let the model template raw rm -rf. Local-misuse risk only, not remotely exploitable; filed publicly as the repo has no SECURITY.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions