Contrôle des permissions et de la sécurité des compétences

Les Skills peuvent accéder au système de fichiers, appeler des API externes et exécuter des scripts. En cas d'abus, ces capacités peuvent introduire des risques de sécurité.

Cet article explique comment établir des limites de sécurité dès la phase de conception afin d'empêcher un Skill d'effectuer des opérations au-delà des attentes.


Portée des autorisations par défaut des Skills

Dans l’environnement d’exécution de Claude, les autorisations des Skills sont déterminées par l’environnement sandbox et ne sont pas illimitées.

Type d'opérationStatut des autorisationsDescription
Lire les fichiers téléversésAutoriséLimité à /mnt/user-data/uploads/
Écrire des fichiers de sortieAutoriséLimité à /mnt/user-data/outputs/ et /home/claude/
Lire les fichiers systèmeRestrictedMontage en lecture seule, ne peut pas modifier les fichiers système
Accéder au réseau externeRestrictedSeuls les domaines figurant sur la liste blanche sont autorisés
Exécuter des commandes système arbitrairesRestrictedImpossible d'utiliser sudo, impossible de modifier la configuration du système
Accéder aux données des autres utilisateursInterditGaranti par l'isolation du bac à sable

Les Skills sont une mise en pratique du principe de "moindre privilège" en conception : un Skill ne peut accéder qu'aux ressources dont il a explicitement besoin. Si un Skill nécessite des autorisations au-delà du périmètre ci-dessus, l'approche de conception doit être reconsidérée.


Définir clairement les limites de permissions dans SKILL.md

Déclarer clairement dans la documentation du Skill à quelles ressources il accédera, afin que les utilisateurs puissent comprendre le périmètre de comportement du Skill avant de l'utiliser.

## 权限说明

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

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

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

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

Prévenir les attaques par traversée de chemin

Lorsqu'un script accepte un chemin de fichier fourni par l'utilisateur, il doit vérifier si le chemin se trouve dans le périmètre autorisé, empêchant ainsi les utilisateurs de../accéder à des répertoires auxquels ils ne devraient pas accéder.

Exemple

# Chemin du fichier : scripts/safe_path.py
import os

# Répertoires autorisés pour la lecture
ALLOWED_READ_DIRS = [
    "/mnt/user-data/uploads",
    "/mnt/skills/public",
]

# Répertoires autorisés en écriture
ALLOWED_WRITE_DIRS = [
    "/mnt/user-data/outputs",
    "/home/claude",
]

def is_safe_path(path: str, allowed_dirs: list) -> bool:
    """
Vérifier si le chemin se trouve dans le périmètre autorisé des répertoires

Empêcher les attaques par traversée de chemin comme ../../../etc/passwd
    """

    # Résoudre en chemin absolu (éliminer .. et les liens symboliques)
    real_path = os.path.realpath(os.path.abspath(path))

    for allowed in allowed_dirs:
        real_allowed = os.path.realpath(allowed)
        # Vérifier si real_path commence par le répertoire autorisé
        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:
    """Valider le chemin de lecture, lever une exception s'il est invalide"""
    if not is_safe_path(user_input, ALLOWED_READ_DIRS):
        raise PermissionError(
            f"Accès refusé : {user_input}\n"
            f"Seuls les répertoires suivants sont autorisés en lecture : {ALLOWED_READ_DIRS}"
        )
    return os.path.realpath(user_input)

def safe_write_path(user_input: str) -> str:
    """Valider le chemin d'écriture, lever une exception s'il est invalide"""
    if not is_safe_path(user_input, ALLOWED_WRITE_DIRS):
        raise PermissionError(
            f"Écriture refusée : {user_input}\n"
            f"Seuls les répertoires suivants sont autorisés en écriture : {ALLOWED_WRITE_DIRS}"
        )
    return os.path.realpath(user_input)

# Exemple d'utilisation
if __name__ == "__main__":
    # Chemin normal : réussi
    ok_path = safe_read_path("/mnt/user-data/uploads/example.csv")
    print(f"Réussi : {ok_path}")

    # Chemin de traversée : rejeté
    try:
        bad_path = safe_read_path("/mnt/user-data/uploads/../../etc/passwd")
    except PermissionError as e:
        print(f"Bloqué : {e}")
通过:/mnt/user-data/uploads/example.csv
已拦截:拒绝访问:/mnt/user-data/uploads/../../etc/passwd
只允许读取以下目录:['/mnt/user-data/uploads', '/mnt/skills/public']

Stockage sécurisé des clés API

Les différentes méthodes de gestion des clés varient considérablement en termes de niveau de sécurité.

MéthodeSécuritéNiveau de recommandation
Codées en dur dans les scriptsExtrêmement faible, sera commité dans GitInterdit
Variables d'environnementMoyen, isolation des processusRecommandé (phase de développement)
Fichier .env (ajouté au .gitignore)Moyen, stockage dans un fichier localRecommandé (utilisation locale)
Gestionnaire de clés système (comme Vault)Élevé, gestion centraliséeRecommandé (environnement de production)

Exemple

# Chemin du fichier : scripts/config.py
# Lire en toute sécurité la configuration depuis plusieurs sources, en recherchant par ordre de priorité décroissante

import os

def get_secret(key: str, required: bool = True) -> str:
    """
Lire les clés à partir des sources suivantes par ordre de priorité :
1. Variables d'environnement (priorité la plus élevée)
2. Fichier /home/claude/.skill_secrets (fichier de clé local)
3. Si required=True et qu'aucune valeur n'est trouvée, lever une exception
    """

    # 1. Variables d'environnement
    value = os.environ.get(key)
    if value:
        return value

    # 2. Fichier de clé local (format de chaque ligne : 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. Non trouvé
    if required:
        raise EnvironmentError(
            f"Clé requise manquante : {key}\n"
            f"Veuillez définir la variable d'environnement : export {key}='your key'"
        )
    return ""

Filtrage sécurisé du contenu d'entrée

Lorsqu'une compétence transmet une entrée utilisateur à des commandes Shell, l'entrée doit être échappée pour empêcher l'injection de commandes.

Exemple

# Chemin du fichier : scripts/safe_exec.py
import subprocess
import shlex

def safe_shell_exec(template: str, user_input: str) -> str:
    """
Intégrer en toute sécurité les entrées utilisateur dans les commandes Shell

Mauvaise approche : os.system(f"process {user_input}") # Risque d'injection de commande !
Approche correcte : utilisez une liste d'arguments, laissez subprocess gérer l'échappement
    """

    # Utilisez une liste au lieu d'une chaîne ; subprocess gérera automatiquement l'échappement
    cmd = ["python", "scripts/process.py", user_input]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    return result.stdout

# Si vous devez construire une commande sous forme de chaîne, utilisez shlex.quote pour échapper l'entrée utilisateur
def safe_string_exec(user_filename: str) -> str:
    safe_name = shlex.quote(user_filename)   # Ajouter automatiquement des guillemets et échapper les caractères spéciaux
    cmd = f"wc -l {safe_name}"
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    return result.stdout

N'utilisez jamaisos.system(f"cmd {user_input}")cette manière d'exécuter des commandes. Si l'entrée utilisateur contient; rm -rf /ou un contenu similaire, cela entraînera des conséquences désastreuses. Utilisez toujours la forme d'argument de liste de subprocess.

Autres extensions