Skills Permissions and Security Control
Skills can access the file system, call external APIs, and execute scripts. If abused, these capabilities can introduce security risks.
This article explains how to establish security boundaries at the design stage to prevent a Skill from performing operations beyond expectations.
Default Permission Scope of Skills
In Claude's execution environment, Skills' permissions are determined by the sandbox environment and are not unlimited.
| Operation Type | Permission Status | Description |
|---|---|---|
| Read uploaded files | Allowed | Limited to /mnt/user-data/uploads/ |
| Write output files | Allowed | Limited to /mnt/user-data/outputs/ and /home/claude/ |
| Read system files | Restricted | Read-only mount, cannot modify system files |
| Access external network | Restricted | Only whitelisted domains are allowed |
| Execute arbitrary system commands | Restricted | Cannot use sudo, cannot modify system configuration |
| Access other users' data | Prohibited | Guaranteed by sandbox isolation |
Skills are a practice of the "least privilege" principle in design: a Skill can only access the resources it explicitly needs. If a Skill requires permissions beyond the above scope, the design approach should be reconsidered.
Clearly Define Permission Boundaries in SKILL.md
Clearly declare in the Skill documentation which resources it will access, so users can understand the Skill's scope of behavior before using it.
## 权限说明 本 Skill 会进行以下操作,请确认你已了解: **文件访问** - 读取:/mnt/user-data/uploads/ 下用户上传的文件 - 写入:/mnt/user-data/outputs/ 下的输出文件 **网络访问** - 无(本 Skill 不访问任何外部网络) **不会进行的操作** - 不读取系统文件 - 不修改已上传的原始文件 - 不访问任何外部服务
Preventing Path Traversal Attacks
When a script accepts a user-provided file path, it must verify whether the path is within the allowed range, preventing users from../accessing directories they should not access.
Example
import os
# Directories allowed for reading
ALLOWED_READ_DIRS = [
"/mnt/user-data/uploads",
"/mnt/skills/public",
]
# Directories allowed for writing
ALLOWED_WRITE_DIRS = [
"/mnt/user-data/outputs",
"/home/claude",
]
def is_safe_path(path: str, allowed_dirs: list) -> bool:
"""
Check whether the path is within the allowed directory scope
Prevent path traversal attacks like ../../../etc/passwd
"""
# Resolve to absolute path (eliminate .. and symbolic links)
real_path = os.path.realpath(os.path.abspath(path))
for allowed in allowed_dirs:
real_allowed = os.path.realpath(allowed)
# Check whether real_path starts with the allowed directory
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:
"""Validate the read path, raise an exception if invalid"""
if not is_safe_path(user_input, ALLOWED_READ_DIRS):
raise PermissionError(
f"Access denied: {user_input}\n"
f"Only the following directories are allowed for reading: {ALLOWED_READ_DIRS}"
)
return os.path.realpath(user_input)
def safe_write_path(user_input: str) -> str:
"""Validate the write path, raise an exception if invalid"""
if not is_safe_path(user_input, ALLOWED_WRITE_DIRS):
raise PermissionError(
f"Write denied: {user_input}\n"
f"Only the following directories are allowed for writing: {ALLOWED_WRITE_DIRS}"
)
return os.path.realpath(user_input)
# Usage example
if __name__ == "__main__":
# Normal path: pass
ok_path = safe_read_path("/mnt/user-data/uploads/example.csv")
print(f"Passed: {ok_path}")
# Traversal path: rejected
try:
bad_path = safe_read_path("/mnt/user-data/uploads/../../etc/passwd")
except PermissionError as e:
print(f"Blocked: {e}")
通过:/mnt/user-data/uploads/example.csv 已拦截:拒绝访问:/mnt/user-data/uploads/../../etc/passwd 只允许读取以下目录:['/mnt/user-data/uploads', '/mnt/skills/public']
Secure Storage of API Keys
Different key management methods vary significantly in security level.
| Method | Security | Recommendation Level |
|---|---|---|
| Hardcoded in scripts | Extremely low, will be committed to Git | Prohibited |
| Environment variables | Medium, process isolation | Recommended (development stage) |
| .env file (added to .gitignore) | Medium, local file storage | Recommended (local use) |
| System key manager (such as Vault) | High, centralized management | Recommended (production environment) |
Example
# Safely read configuration from multiple sources, searching in descending order of priority
import os
def get_secret(key: str, required: bool = True) -> str:
"""
Read keys from the following sources in order of priority:
1. Environment variables (highest priority)
2. /home/claude/.skill_secrets file (local key file)
3. If required=True and not found, raise an exception
"""
# 1. Environment variables
value = os.environ.get(key)
if value:
return value
# 2. Local key file (each line format: 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. Not found
if required:
raise EnvironmentError(
f"Missing required key: {key}\n"
f"Please set the environment variable: export {key}='your key'"
)
return ""
Secure Filtering of Input Content
When a Skill passes user input to Shell commands, the input must be escaped to prevent command injection.
Example
import subprocess
import shlex
def safe_shell_exec(template: str, user_input: str) -> str:
"""
Safely embed user input into Shell commands
Wrong approach: os.system(f"process {user_input}") # Command injection risk!
Correct approach: use an argument list, let subprocess handle escaping
"""
# Use a list instead of a string; subprocess will automatically handle escaping
cmd = ["python", "scripts/process.py", user_input]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return result.stdout
# If you must build a string command, use shlex.quote to escape user input
def safe_string_exec(user_filename: str) -> str:
safe_name = shlex.quote(user_filename) # Automatically add quotes and escape special characters
cmd = f"wc -l {safe_name}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return result.stdout
Other ExtensionsNever use
os.system(f"cmd {user_input}")this way to execute commands. If user input contains; rm -rf /or similar content, it will lead to disastrous consequences. Always use subprocess's list argument form.