Security Guide2026-02-05

Best Practices for Building Secure Agent Skills [2026 Security Guide]

TeamSecurity Team

Why Security Matters for Agent Skills

AI agents execute code based on unpredictable LLM outputs. A single overlooked vulnerability can lead to:

  • Data exfiltration - Leaked API keys, passwords, or PII
  • Remote code execution (RCE) - Attackers running arbitrary commands
  • Denial of service - Resource exhaustion or infinite loops
  • Privilege escalation - Agents accessing restricted systems

At AgentSkillsHub.dev, 23% of scanned skills contain at least one critical vulnerability. Don't be part of that statistic.

The 10 Security Commandments

1. Never Trust LLM Output

LLMs can be manipulated via prompt injection. Always validate inputs:

# BAD
def delete_file(path: str):
    os.remove(path)  # No validation!

# GOOD
import os.path

def delete_file(path: str):
    # Whitelist allowed directory
    allowed_dir = "/workspace/temp"
    abs_path = os.path.abspath(path)

    if not abs_path.startswith(allowed_dir):
        raise ValueError("Path outside allowed directory")

    if not os.path.exists(abs_path):
        raise FileNotFoundError("File does not exist")

    os.remove(abs_path)

2. Use Environment Variables for Secrets

Never hardcode API keys, passwords, or tokens:

# BAD
OPENAI_KEY = "sk-proj-abc123..."

# GOOD
import os

OPENAI_KEY = os.environ.get('OPENAI_KEY')
if not OPENAI_KEY:
    raise EnvironmentError("OPENAI_KEY not set")

3. Pin All Dependencies

Prevent typosquatting and supply chain attacks:

# requirements.txt
requests==2.31.0
pydantic==2.5.3
# NOT: requests>=2.0  (too broad)

4. Sandbox File System Access

Restrict file operations to a specific directory:

import os.path

SANDBOX_DIR = "/workspace"

def is_safe_path(path: str) -> bool:
    abs_path = os.path.abspath(path)
    return abs_path.startswith(SANDBOX_DIR)

def read_file(path: str) -> str:
    if not is_safe_path(path):
        raise PermissionError("Access denied")
    with open(path, 'r') as f:
        return f.read()

5. Avoid Shell Execution

Never use os.system() or subprocess with shell=True:

# BAD
os.system(f"cat {filename}")

# GOOD
import subprocess
subprocess.run(["cat", filename], shell=False, capture_output=True)

6. Rate Limit External API Calls

Prevent abuse and cost overruns:

import time
from collections import deque

class RateLimiter:
    def __init__(self, max_calls: int, period: int):
        self.max_calls = max_calls
        self.period = period
        self.calls = deque()

    def allow_request(self) -> bool:
        now = time.time()
        # Remove old calls
        while self.calls and self.calls[0] < now - self.period:
            self.calls.popleft()

        if len(self.calls) < self.max_calls:
            self.calls.append(now)
            return True
        return False

limiter = RateLimiter(max_calls=10, period=60)  # 10 calls per minute

7. Log Security Events

Track suspicious activity for auditing:

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def sensitive_operation(user_id: str):
    logger.info(f"Sensitive operation by user {user_id}")
    # ... operation logic

8. Implement Timeouts

Prevent infinite loops and resource exhaustion:

import requests

def fetch_data(url: str):
    response = requests.get(url, timeout=5)  # 5 second timeout
    return response.json()

9. Validate Output Before Returning

Don't accidentally leak sensitive data:

def sanitize_output(data: dict) -> dict:
    # Remove sensitive keys
    sensitive_keys = ['password', 'api_key', 'token', 'secret']
    return {k: v for k, v in data.items() if k not in sensitive_keys}

10. Test with Adversarial Inputs

Try to break your own skill:

# Test cases
test_inputs = [
    "../../../etc/passwd",  # Path traversal
    "'; DROP TABLE users;--",  # SQL injection
    "__import__('os').system('rm -rf /')",  # Code injection
    "A" * 1000000,  # Resource exhaustion
]

for malicious_input in test_inputs:
    try:
        your_function(malicious_input)
    except Exception as e:
        print(f"Blocked: {e}")

Security Checklist

Before publishing your skill:

  • [ ] No hardcoded secrets
  • [ ] All inputs validated
  • [ ] Dependencies pinned
  • [ ] File access sandboxed
  • [ ] No shell=True in subprocess
  • [ ] API rate limiting implemented
  • [ ] Timeouts on all network requests
  • [ ] Security events logged
  • [ ] Output sanitized
  • [ ] Tested with adversarial inputs

Automated Security Scanning

Use our free security scanner to catch vulnerabilities:

Scan your skill now ->

Additional Resources

How to apply this guidance in real workflows

Security advice is only useful when it changes implementation behavior. After reading this article, convert the recommendations into a short operational checklist for your team. Start by identifying where the discussed risk appears in your stack today, then assign one owner for validation and one owner for rollout. Shared ownership prevents common drift where findings are acknowledged but never implemented.

Next, classify actions by urgency. Immediate controls should block critical failure paths, such as unsafe command execution, secret leakage, or unreviewed external integrations. Secondary actions can improve observability, documentation quality, and long-term resilience. Separating urgent controls from structural improvements keeps momentum high while still building durable safeguards.

Teams adopting AI agent tooling often underestimate configuration risk. Even when a package is well maintained, local setup can introduce weak points through permissive environment variables, broad network access, or unclear update practices. Use this article as a trigger to review runtime boundaries: what the tool can read, what it can execute, and what data it can send externally.

A simple post-read implementation loop

1) Capture the top three risks in plain language. 2) Add one measurable control for each risk. 3) Run a small pilot with logs enabled. 4) Review outcomes after one week and adjust policy before broad rollout. This loop keeps decisions evidence based and avoids overreaction. It also creates a repeatable pattern that works across different tools and changing vendor landscapes.

Finally, document exceptions explicitly. If you accept a risk for business reasons, record the reason, mitigation, and review date. Transparent exception handling is a major trust signal for internal stakeholders and external auditors. It also improves future decision speed because teams can reference prior reasoning instead of reopening the same debate every release cycle.

If you run recurring retrospectives, archive lessons learned from each implementation cycle. A lightweight internal knowledge base turns individual fixes into team capability and steadily lowers incident frequency over time.

Are your skills safe?

Don't guess. Run our free security scanner now.

Open Scanner