Skip to content

Skills 权限与安全控制

Skills 可以访问文件系统、调用外部 API、执行脚本,这些能力如果被滥用会带来安全风险。

本篇介绍如何在设计阶段建立安全边界,防止 Skill 做出超出预期的操作。


Skills 的默认权限范围

在 Claude 的执行环境中,Skills 的权限由沙箱环境决定,并非无限制的。

操作类型权限状态说明
读取上传文件允许仅限 /mnt/user-data/uploads/
写入输出文件允许仅限 /mnt/user-data/outputs/ 和 /home/claude/
读取系统文件受限只读挂载,无法修改系统文件
访问外部网络受限仅允许访问白名单域名
执行任意系统命令受限不能使用 sudo,不能修改系统配置
访问其他用户数据禁止沙箱隔离保证

Skills 在设计上是"最小权限"原则的实践:Skill 只能访问它明确需要的资源。如果一个 Skill 需要超出上述范围的权限,应当重新考虑设计方案。


在 SKILL.md 中明确权限边界

在 Skill 文档中清晰声明它会访问哪些资源,让用户在使用前了解 Skill 的行为范围。

bash
## 权限说明

 Skill 会进行以下操作,请确认你已了解:

**文件访问**
- 读取:/mnt/user-data/uploads/ 下用户上传的文件
- 写入:/mnt/user-data/outputs/ 下的输出文件

**网络访问**
- 无(本 Skill 不访问任何外部网络)

**不会进行的操作**
- 不读取系统文件
- 不修改已上传的原始文件
- 不访问任何外部服务

防止路径穿越攻击

当脚本接受用户提供的文件路径时,需要验证路径是否在允许范围内,防止用户通过 ../ 访问到不应访问的目录。

实例

bash

# 文件路径:scripts/safe_path.py

import os

# 允许读取的目录

ALLOWED_READ_DIRS = [

    "/mnt/user-data/uploads",

    "/mnt/skills/public",

]

# 允许写入的目录

ALLOWED_WRITE_DIRS = [

    "/mnt/user-data/outputs",

    "/home/claude",

]

def is_safe_path(path: str, allowed_dirs: list) -> bool:

    """

    检查路径是否在允许的目录范围内

    防止 ../../../etc/passwd 类型的路径穿越攻击

    """

    # 解析为绝对路径(消除 .. 和符号链接)

    real_path = os.path.realpath(os.path.abspath(path))

    for allowed in allowed_dirs:

        real_allowed = os.path.realpath(allowed)

        # 检查 real_path 是否以 allowed 目录开头

        if real_path.startswith(real_allowed + os.sep) or real_path == real_allowed:

            return True

    return False

def safe_read_path(user_input: str) -> str:

    """验证读取路径,不合法时抛出异常"""

    if not is_safe_path(user_input, ALLOWED_READ_DIRS):

        raise PermissionError(

            f"拒绝访问:{user_input}\n"

            f"只允许读取以下目录:{ALLOWED_READ_DIRS}"

        )

    return os.path.realpath(user_input)

def safe_write_path(user_input: str) -> str:

    """验证写入路径,不合法时抛出异常"""

    if not is_safe_path(user_input, ALLOWED_WRITE_DIRS):

        raise PermissionError(

            f"拒绝写入:{user_input}\n"

            f"只允许写入以下目录:{ALLOWED_WRITE_DIRS}"

        )

    return os.path.realpath(user_input)

# 使用示例

if __name__ == "__main__":

    # 正常路径:通过

    ok_path = safe_read_path("/mnt/user-data/uploads/runoob.csv")

    print(f"通过:{ok_path}")

    # 穿越路径:拒绝

    try:

        bad_path = safe_read_path("/mnt/user-data/uploads/../../etc/passwd")

    except PermissionError as e:

        print(f"已拦截:{e}")
bash
通过:/mnt/user-data/uploads/runoob.csv
已拦截:拒绝访问:/mnt/user-data/uploads/../../etc/passwd
只允许读取以下目录:['/mnt/user-data/uploads', '/mnt/skills/public']

API 密钥的安全存储

不同的密钥管理方式,安全等级差异显著。

方式安全性推荐程度
硬编码在脚本中极低,会被提交到 Git禁止
环境变量中,进程隔离推荐(开发阶段)
.env 文件(加入 .gitignore)中,文件本地存储推荐(本地使用)
系统密钥管理器(如 Vault)高,集中管理推荐(生产环境)

实例

bash

# 文件路径:scripts/config.py

# 安全地从多个来源读取配置,按优先级降序查找

import os

def get_secret(key: str, required: bool = True) -> str:

    """

    按优先级从以下来源读取密钥:

    1. 环境变量(最高优先级)

    2. /home/claude/.skill_secrets 文件(本地密钥文件)

    3. 若 required=True 且找不到,抛出异常

    """

    # 1. 环境变量

    value = os.environ.get(key)

    if value:

        return value

    # 2. 本地密钥文件(每行格式:KEY=VALUE)

    secrets_file = "/home/claude/.skill_secrets"

    if os.path.exists(secrets_file):

        with open(secrets_file) as f:

            for line in f:

                line = line.strip()

                if line.startswith(f"{key}="):

                    return line[len(key)+1:]

    # 3. 未找到

    if required:

        raise EnvironmentError(

            f"缺少必要的密钥:{key}\n"

            f"请设置环境变量:export {key}='你的密钥'"

        )

    return ""

输入内容的安全过滤

当 Skill 将用户输入传递给 Shell 命令时,必须对输入进行转义,防止命令注入。

实例

python

# 文件路径:scripts/safe_exec.py

import subprocess

import shlex

def safe_shell_exec(template: str, user_input: str) -> str:

    """

    安全地将用户输入嵌入 Shell 命令

    错误做法:os.system(f"process {user_input}")   # 命令注入风险!

    正确做法:使用参数列表,让 subprocess 处理转义

    """

    # 使用列表而非字符串,subprocess 会自动处理转义

    cmd = ["python", "scripts/process.py", user_input]

    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

    return result.stdout

# 如果必须构建字符串命令,使用 shlex.quote 转义用户输入

def safe_string_exec(user_filename: str) -> str:

    safe_name = shlex.quote(user_filename)   # 自动添加引号并转义特殊字符

    cmd = f"wc -l {safe_name}"

    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

    return result.stdout

永远不要用 os.system(f"cmd {user_input}") 这种方式执行命令。用户输入中若包含 ; rm -rf / 之类的内容,会导致灾难性后果。始终使用 subprocess 的列表参数形式。

AI 思考中...

自定义 Skills 生态系统搭建

Skills 性能优化

基于 VitePress 构建,部署于 GitHub Pages