YouTip LogoYouTip

Skills Security

Skills Permissions and Security Controls |

\\n\\n
\\n\\n

Skills can access the file system, call external APIs, and execute scripts. If these capabilities are misused, they can pose security risks.

\\n\\n

This article explains how to establish security boundaries during the design phase to prevent Skills from performing actions beyond their intended scope.

\\n\\n
\\n\\n

Default Permission Scope of Skills

\\n\\n

In Claude’s execution environment, Skills’ permissions are determined by the sandbox environment and are not unlimited.

\\n\\n\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
Operation TypePermission StatusDescription
Read uploaded filesAllowedOnly within /mnt/user-data/uploads/
Write output filesAllowedOnly within /mnt/user-data/outputs/ and /home/claude/
Read system filesRestrictedRead-only mount; system files cannot be modified
Access external networkRestrictedOnly whitelisted domains are allowed
Execute arbitrary system commandsRestrictedsudo is disallowed; system configuration cannot be modified
Access other users’ dataProhibitedGuaranteed by sandbox isolation
\\n\\n
\\n

Skills are designed following the principle of least privilege: a Skill can only access resources it explicitly requires. If a Skill needs permissions beyond the above scope, the design should be reconsidered.

\\n
\\n\\n
\\n\\n

Defining Permission Boundaries in SKILL.md

\\n\\n

Clearly declare which resources the Skill will access in its documentation, so users understand its behavior scope before using it.

\\n\\n

Permission Statement

\\n\\n

The Skill will perform the following operations. Please confirm that you understand them:

\\n\\n
    \\n
  • File Access\\n
      \\n
    • Read: user-uploaded files under /mnt/user-data/uploads/
    • \\n
    • Write: output files under /mnt/user-data/outputs/
    • \\n
    \\n
  • \\n
  • Network Access\\n
      \\n
    • None (this Skill does not access any external networks)
    • \\n
    \\n
  • \\n
  • Operations Not Performed\\n
      \\n
    • Does not read system files
    • \\n
    • Does not modify original uploaded files
    • \\n
    • Does not access any external services
    • \\n
    \\n
  • \\n
\\n\\n
\\n\\n

Preventing Path Traversal Attacks

\\n\\n

When scripts accept file paths provided by users, they must validate whether the path falls within allowed directories to prevent users from using ../ to access unauthorized directories.

\\n\\n

Example

\\n\\n
# File path: scripts/safe_path.py\\n\\nimport os\\n\\n# Allowed read directories\\n\\n ALLOWED_READ_DIRS =[\\n"/mnt/user-data/uploads",\\n"/mnt/skills/public",\\n]\\n\\n# Allowed write directory\\n\\n ALLOWED_WRITE_DIRS =[\\n"/mnt/user-data/outputs",\\n"/home/claude",\\n]\\n\\ndef is_safe_path(path: str, allowed_dirs: list) ->bool:\\n\\n"""\\n Check if path is within allowed directory scope\\nPrevent ../../../etc/passwd type of path traversal attack\\n\\n """\\n\\n# Resolve to absolute path (eliminate .. and symbolic links)\\n\\n real_path =os.path.realpath(os.path.abspath(path))\\n\\nfor allowed in allowed_dirs:\\n\\n real_allowed =os.path.realpath(allowed)\\n\\n# Check if real_path starts with the allowed directory\\n\\nif real_path.startswith(real_allowed + os.sep)or real_path == real_allowed:\\n\\nreturn True\\n\\nreturn False\\n\\ndef safe_read_path(user_input: str) ->str:\\n\\n"""Validate read path, throw exception if invalid"""\\n\\nif not is_safe_path(user_input, ALLOWED_READ_DIRS):\\n\\nraise PermissionError(\\n\\n f"Access denied:{user_input}n"\\n\\n f"Only allow reading from the following directories:{ALLOWED_READ_DIRS}"\\n\\n)\\n\\nreturn os.path.realpath(user_input)\\n\\ndef safe_write_path(user_input: str) ->str:\\n\\n"""Validate write path, throw exception if invalid"""\\n\\nif not is_safe_path(user_input, ALLOWED_WRITE_DIRS):\\n\\nraise PermissionError(\\n\\n f"Deny write:{user_input}n"\\n\\n f"Only allow writing to the following directories:{ALLOWED_WRITE_DIRS}"\\n\\n)\\n\\nreturn os.path.realpath(user_input)\\n\\n# Usage example\\n\\nif __name__ =="__main__":\\n\\n# Normal path: Pass\\n\\n ok_path = safe_read_path("/mnt/user-data/uploads/tutorial.csv")\\n\\nprint(f"Pass:{ok_path}")\\n\\n# Traversal path: Denied\\n\\ntry:\\n\\n bad_path = safe_read_path("/mnt/user-data/uploads/../../etc/passwd")\\n\\nexcept PermissionError as e:\\n\\nprint(f"Intercepted:{e}")\\n\\nPass: /mnt/user-data/uploads/tutorial.csv Intercepted:Access denied:/mnt/user-data/uploads/../../etc/passwd Only allow reading from the following directories:['/mnt/user-data/uploads', '/mnt/skills/public']\\n
\\n\\n
\\n\\n

Secure Storage of API Keys

\\n\\n

Different key management methods have significantly different security levels.

\\n\\n\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
MethodSecurity LevelRecommendation
Hardcoded in scriptVery low; exposed in Git commitsProhibited
Environment variablesModerate; process isolationRecommended (development phase)
.env file (added to .gitignore)Moderate; file stored locallyRecommended (local use)
System key manager (e.g., Vault)High; centralized managementRecommended (production environment)
\\n\\n

Example

\\n\\n
# File path: scripts/config.py\\n\\n# Safely read configuration from multiple sources, searching in descending order of priority\\n\\nimport os\\n\\ndef get_secret(key: str, required: bool=True) ->str:\\n\\n"""\\n\\n Read key from the following sources in order of priority:\\n\\n 1. Environment variables (highest priority)\\n\\n 2. /home/claude/.skill_secrets File (local key file)\\n\\n 3. If required=True and not found, throw an exception\\n\\n """\\n\\n# 1. Environment variables\\n\\n value =os.environ.get(key)\\n\\nif value:\\n\\nreturn value\\n\\n# 2. Local key file (format per line: KEY=VALUEοΌ‰\\n\\n secrets_file ="/home/claude/.skill_secrets"\\n\\nif os.path.exists(secrets_file):\\n\\nwith open(secrets_file)as f:\\n\\nfor line in f:\\n\\n line = line.strip()\\n\\nif line.startswith(f"{key}="):\\n\\nreturn line[len(key)+1:]\\n\\n# 3. Not found\\n\\nif required:\\n\\nraise EnvironmentError(\\n\\n f"Missing required key:{key}n"\\n\\n f"Please set Environment variables: export {key}='Your key'"\\n\\n)\\n\\nreturn""\\n
\\n\\n
\\n\\n

Secure Filtering of Input Content

\\n\\n

When a Skill passes user input to shell commands, the input must be escaped to prevent command injection.

\\n\\n

Example

\\n\\n
# File path: scripts/safe_exec.py\\n\\nimport subprocess\\n\\nimport shlex\\n\\ndef safe_shell_exec(template: str, user_input: str) ->str:\\n\\n"""\\n\\n Safely embed user input into Shell commands\\nWrong approach: os.system(f"process {user_input}")  # Command injection risk!\\n\\n Correct approach: Use an argument list, let subprocess handle escaping\\n\\n """\\n\\n# Use list instead of string, subprocess will handle escaping automatically\\n\\ncmd=["python","scripts/process.py", user_input]\\n\\n result =subprocess.run(cmd, capture_output=True, text=True, timeout=30)\\n\\nreturn result.stdout\\n\\n# If you must build string commands, use shlex.quote Escape user input\\n\\ndef safe_string_exec(user_filename: str) ->str:\\n\\n safe_name =shlex.quote(user_filename)# Automatically add quotes and escape special characters\\n\\ncmd= f"wc -l {safe_name}"\\n\\n result =subprocess.run(cmd, shell=True, capture_output=True, text=True)\\n\\nreturn result.stdout\\n\\n> Never use `os.system(f"cmd {user_input}")` Execute commands this way. If user input contains `; rm -rf /` and similar content can lead to catastrophic consequences. Always use the list argument form of subprocess.\\n
← Vue3 Blog Deploy VercelSkills Api Integration β†’