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ération | Statut des autorisations | Description |
|---|---|---|
| Lire les fichiers téléversés | Autorisé | Limité à /mnt/user-data/uploads/ |
| Écrire des fichiers de sortie | Autorisé | Limité à /mnt/user-data/outputs/ et /home/claude/ |
| Lire les fichiers système | Restricted | Montage en lecture seule, ne peut pas modifier les fichiers système |
| Accéder au réseau externe | Restricted | Seuls les domaines figurant sur la liste blanche sont autorisés |
| Exécuter des commandes système arbitraires | Restricted | Impossible d'utiliser sudo, impossible de modifier la configuration du système |
| Accéder aux données des autres utilisateurs | Interdit | Garanti 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
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éthode | Sécurité | Niveau de recommandation |
|---|---|---|
| Codées en dur dans les scripts | Extrêmement faible, sera commité dans Git | Interdit |
| Variables d'environnement | Moyen, isolation des processus | Recommandé (phase de développement) |
| Fichier .env (ajouté au .gitignore) | Moyen, stockage dans un fichier local | Recommandé (utilisation locale) |
| Gestionnaire de clés système (comme Vault) | Élevé, gestion centralisée | Recommandé (environnement de production) |
Exemple
# 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
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
Autres extensionsN'utilisez jamais
os.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.