Security Considerations for Custom Skills: A Complete Guide to Safe MCP Development
Custom skills run inside Claude's execution environment and require strict input validation, secure secret management via environment variables, and accurate tool annotations to prevent unauthorized access, injection attacks, and information leakage.
When building custom skills for the Model Context Protocol (MCP) in the anthropics/skills repository, understanding security considerations for custom skills is essential to protect both your infrastructure and user data. These skills execute as tools within Claude's environment, making robust security practices critical for safe deployment.
Authentication and Authorization
The MCP Builder best practices documented in skills/mcp-builder/reference/mcp_best_practices.md mandate that skills implement proper authentication mechanisms to prevent unauthorized callers from invoking the skill or accessing protected resources.
OAuth 2.1 and API Key Management
For OAuth 2.1 implementations, use certificates from trusted authorities, validate access tokens rigorously, and accept only tokens explicitly intended for your server.
When using API keys, never hard-code them in source files. Instead, load them from environment variables and validate their presence on startup:
import os
from typing import Optional
def get_api_key() -> Optional[str]:
"""Return the API key from the environment or raise a clear error."""
key = os.getenv("MY_SKILL_API_KEY")
if not key:
raise RuntimeError(
"API key not configured. Set the MY_SKILL_API_KEY environment variable."
)
return key
This pattern prevents accidental secret leakage through Git history or package distribution.
Input Validation and Sanitization
The mcp_best_practices.md file emphasizes comprehensive input validation to block injection attacks and unauthorized file system access.
Preventing Directory Traversal
Sanitize every file-system path to prevent directory traversal attacks using sequences like ../../etc/passwd. Resolve user-provided paths against a base directory and verify the resolved path remains within allowed boundaries:
from pathlib import Path
import os
BASE_DIR = Path("/app/data") # Directory that the skill is allowed to touch
def safe_join(user_path: str) -> Path:
"""Resolve a user-provided path and ensure it stays inside BASE_DIR."""
candidate = (BASE_DIR / user_path).resolve()
if not str(candidate).startswith(str(BASE_DIR)):
raise ValueError("Attempted directory traversal detected.")
return candidate
URL and Command Validation
Validate URL formats against whitelisted domains before making external requests. For system calls, reject or escape dangerous characters to prevent command injection. Use schema validators such as Pydantic (Python) or Zod (TypeScript) to enforce type and format compliance on all inputs.
Error Handling and Information Disclosure
According to the error handling guidelines in skills/mcp-builder/reference/mcp_best_practices.md, skills must never expose stack traces or internal diagnostics to clients. Log security-relevant errors server-side only, and return concise, user-friendly messages that guide users without revealing implementation details.
DNS Rebinding Protection
When a skill hosts a local HTTP server for streaming data, bind to 127.0.0.1 instead of 0.0.0.0 to prevent external network exposure. Additionally, validate the Origin header on every request to ensure the request originates from the expected context, preventing cross-site request forgery via DNS rebinding attacks.
Tool Annotations for Safe Execution
The template/SKILL.md file and best practices document recommend declaring tool annotations that inform Claude about a skill's behavioral characteristics. These annotations help the model avoid unsafe patterns by understanding which operations might mutate state or cause side effects.
| Annotation | Default | Meaning |
|---|---|---|
readOnlyHint |
false |
Set to true when the skill never mutates external state. |
destructiveHint |
true |
Set to false for read-only or safe-only operations. |
idempotentHint |
false |
Set to true if repeated calls with identical arguments are safe. |
openWorldHint |
true |
Set to false if the skill does not contact external services. |
Declare these annotations in your SKILL.md file:
annotations:
readOnlyHint: false
destructiveHint: true
idempotentHint: false
openWorldHint: true
Secure Development Workflow
The anthropics/skills repository provides tooling to enforce security standards during development. Follow this workflow to maintain security considerations for custom skills throughout the build process:
- Start from the template – The
template/SKILL.mdfile outlines required sections, including a security checklist. - Never hard-code secrets – Use
os.getenv()(Python) orprocess.env(Node) and document required environment variables in your skill's README. - Run the quick validator – Execute
skills/skill-creator/scripts/quick_validate.pyto ensure required metadata and the presence of aSKILL.mdfile. - Package with
package_skill.py– Only after validation passes, useskills/skill-creator/scripts/package_skill.pyto create the.skillbundle, ensuring no stray files are included. - Add security tests – Cover input sanitization, authentication failures, and rate-limit handling in your test suite.
Summary
- Store API keys and secrets in environment variables, never in source code, and validate them on startup using patterns like
os.getenv(). - Sanitize all file paths using the
safe_joinpattern to prevent directory traversal, validate URLs against whitelists, and use schema validators like Pydantic or Zod for input validation. - Never expose stack traces or internal errors to clients; log security issues server-side only and return user-friendly error messages.
- Bind local servers to
127.0.0.1instead of0.0.0.0and validate Origin headers to prevent DNS rebinding attacks. - Declare accurate tool annotations in
SKILL.mdto help Claude understand your skill's safety characteristics regarding read-only operations, destructiveness, and external service contact. - Use the repository's validation scripts (
quick_validate.py) and packaging tools (package_skill.py) to enforce security standards before deployment.
Frequently Asked Questions
How should I store API keys in a custom skill?
Store API keys in environment variables using os.getenv() in Python or process.env in Node.js. Never hard-code secrets in your source files, as this risks exposing them through Git history or package distribution. Validate that the environment variable exists on startup and provide clear error messages if it is missing, as shown in the get_api_key() pattern.
What is the best way to prevent directory traversal attacks in file-handling skills?
Use the safe_join pattern implemented in the repository examples: resolve user-provided paths against a base directory using pathlib.Path.resolve(), then verify the resolved path starts with the base directory string. If the path escapes the allowed directory, raise a ValueError. This approach blocks attacks using ../../ sequences to access sensitive system files like /etc/passwd.
Why should I bind local HTTP servers to 127.0.0.1 instead of 0.0.0.0?
Binding to 0.0.0.0 exposes your server on all network interfaces, making it accessible from external machines and vulnerable to DNS rebinding attacks that bypass same-origin policies. Binding to 127.0.0.1 restricts access to localhost only. Combine this with Origin header validation to ensure requests originate from the expected context, preventing cross-site request forgery via DNS rebinding.
What are tool annotations and why do they matter for security?
Tool annotations are metadata declared in your SKILL.md file that inform Claude about a skill's behavioral characteristics, such as whether it is read-only (readOnlyHint), destructive (destructiveHint), idempotent (idempotentHint), or contacts external services (openWorldHint). While these are only hints, they help Claude avoid unsafe patterns by understanding which operations might mutate state or cause side effects, reducing the risk of accidental data loss or unauthorized actions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →