关于 security
面向处理用户数据、身份验证或外部输入的 Agent 与开发者的安全规范技能,提供安全编码指导、构建与审查清单、可检查规则,以及密钥和依赖项检查脚本。
主要功能
- 01输入验证与注入防护
- 02身份验证与授权指导
- 03数据保护与密钥处理
- 04API、上传与日志防护
- 05构建和代码审查清单
- 06规则及安全扫描脚本
适用场景
- 01在实现前规划安全措施
- 02审查代码中的安全违规
- 03检查代码库中的密钥与易受攻击依赖项
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ alexanderstephenthompson/ claude-hub --skill security安装后在 Claude Code 中输入/security确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/threat-model-template.mdsecurity-checklist.mdreferences/owasp-top-10.mdinput-validation.mdauth-patterns.mdscripts/scan_secrets.pycheck_dependencies.py详细介绍
安全技能
版本: 1.0 来源: 安全标准
安全必须从一开始就融入设计,而不是事后再补。这些标准适用于所有处理用户数据、身份验证或外部输入的代码。
问题
AI Agent 默认会优先让代码运行起来,而不是确保代码安全。如果没有明确的安全标准,每次会话都会选择最省事的方式——用字符串拼接构造查询、为图方便硬编码机密信息、为求速度授予宽泛权限。这些选择并非出于恶意;当提示词中没有安全要求时,它们只是阻力最小的做法。这些标准将安全模式设为默认路径。
使用方式
- 构建者: 在编写任何涉及用户输入、身份验证或外部服务的代码之前,请阅读
## Builder Checklist。必须在设计中融入安全,而不是事后打补丁。 - 重构者: 使用
## Enforced Rules查找安全违规项。阅读说明性章节,了解修复指导。 - 两者: 说明性章节是权威标准。检查清单和规则表是对相同内容的精简呈现。
核心原则
- 安全设计 — 从一开始就融入安全,而不是事后考虑
- 纵深防御 — 采用多层安全措施,不依赖单一防护点
- 最小权限 — 仅授予完成任务所需的最低访问权限
- 安全失败 — 错误不应暴露漏洞或敏感数据
- 零信任 — 绝不信任输入,始终进行验证
- 假设已遭入侵 — 以攻击者会入侵系统为前提进行设计,将影响范围降至最低
OWASP Top 10(2021)
需要防范的严重漏洞:
| # | 漏洞 | 防范措施 |
|---|---|---|
| 1 | 访问控制失效 | 对每个请求检查授权 |
| 2 | 加密失效 | 使用强加密,绝不自行实现加密方案 |
| 3 | 注入 | 使用参数化查询、验证输入 |
| 4 | 不安全的设计 | 进行威胁建模、采用安全架构 |
| 5 | 安全配置错误 | 使用安全默认设置、妥善处理错误 |
| 6 | 易受攻击的组件 | 保持依赖项更新、定期审计 |
| 7 | 身份验证失效 | 使用强身份验证、MFA 和安全的会话管理 |
| 8 | 完整性失效 | 验证代码和数据完整性、对发布版本签名 |
| 9 | 日志记录失效 | 记录安全事件、保护日志数据 |
| 10 | SSRF | 验证并通过允许列表限制服务端 URL |
输入验证
黄金法则
绝不信任用户输入。 所有输入都可能是恶意的。
验证策略
1. Validate → 2. Sanitize → 3. Encode (for output context)
SQL 注入防范
# ❌ NEVER - String concatenation
query = f"SELECT * FROM users WHERE id = {user_id}"
# ✅ ALWAYS - Parameterized queries
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
# ✅ ALWAYS - ORM with parameters
User.objects.filter(id=user_id)
XSS 防范
// ❌ NEVER - Direct HTML insertion
element.innerHTML = userInput;
document.write(userInput);
// ✅ ALWAYS - Text content (auto-escapes)
element.textContent = userInput;
// ✅ ALWAYS - Template literals with escaping
const escaped = escapeHtml(userInput);
命令注入防范
# ❌ NEVER - Shell execution with user input
os.system(f"ls {user_path}")
subprocess.call(f"convert {filename}", shell=True)
# ✅ ALWAYS - Argument arrays (no shell)
subprocess.run(["ls", user_path], shell=False)
subprocess.run(["convert", filename], shell=False)
输入验证模式
import re
def validate_email(email: str) -> bool:
"""Validate email format."""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email)) and len(email) <= 254
def validate_username(username: str) -> bool:
"""Validate username: alphanumeric, 3-30 chars."""
pattern = r'^[a-zA-Z0-9_]{3,30}$'
return bool(re.match(pattern, username))
def validate_url(url: str, allowed_domains: list[str]) -> bool:
"""Validate URL against allowlist."""
from urllib.parse import urlparse
parsed = urlparse(url)
return (
parsed.scheme in ('http', 'https') and
parsed.netloc in allowed_domains
)
身份验证与授权
密码要求
| 要求 | 最低标准 |
|---|---|
| 长度 | 12 个字符 |
| 复杂度 | 达到长度要求后无需额外要求 |
| 常见密码 | 禁用排名前 10,000 的常见密码 |
| 已泄露密码 | 使用 HaveIBeenPwned 检查 |
密码存储
# ✅ Use bcrypt, Argon2, or scrypt
import bcrypt
def hash_password(password: str) -> bytes:
"""Hash password with bcrypt."""
salt = bcrypt.gensalt(rounds=12)
return bcrypt.hashpw(password.encode(), salt)
def verify_password(password: str, hashed: bytes) -> bool:
"""Verify password against hash."""
return bcrypt.checkpw(password.encode(), hashed)
绝不要:
- 以明文存储密码
- 使用 MD5 或 SHA1 处理密码
- 自行实现哈希算法
会话管理
# Session security settings
SESSION_CONFIG = {
"cookie_secure": True, # HTTPS only
"cookie_httponly": True, # No JavaScript access
"cookie_samesite": "Lax", # CSRF protection
"session_lifetime": 3600, # 1 hour max
"regenerate_on_login": True # Prevent session fixation
}
授权检查
# ✅ Check authorization on EVERY request
def get_document(user, document_id):
document = Document.get(document_id)
# Always verify ownership/access
if document.owner_id != user.id and not user.is_admin:
raise PermissionError("Access denied")
return document
数据保护
加密要求
| 数据类型 | 静态存储 | 传输中 |
|---|---|---|
| 密码 | 哈希(bcrypt/Argon2) | TLS 1.2+ |
| PII(姓名、电子邮件、地址) | AES-256 | TLS 1.2+ |
| 支付数据 | AES-256 + PCI DSS | TLS 1.3 |
| 健康数据 | AES-256 + HIPAA | TLS 1.3 |
| 会话令牌 | 不适用 | TLS 1.2+ |
| API 密钥 | AES-256 | TLS 1.2+ |
敏感数据处理
# ✅ Mask sensitive data in logs
def log_user_action(user, action, data):
safe_data = {
"user_id": user.id,
"email": mask_email(user.email), # j***@example.com
"action": action,
# Never log: passwords, tokens, full credit cards
}
logger.info("User action", extra=safe_data)
def mask_email(email: str) -> str:
"""Mask email for logging."""
local, domain = email.split("@")
return f"{local[0]}***@{domain}"
def mask_card(card: str) -> str:
"""Show only last 4 digits."""
return f"****-****-****-{card[-4:]}"
数据保留
- 不再需要时删除数据
- 实施数据过期策略
- 提供用户数据导出/删除功能(GDPR)
- 安全擦除已删除的数据(而不仅仅是标记为已删除)
API 安全
速率限制
# Implement rate limiting per endpoint
RATE_LIMITS = {
"/api/login": "5/minute", # Prevent brute force
"/api/register": "3/minute", # Prevent spam
"/api/password-reset": "3/hour",
"/api/*": "100/minute", # General limit
}
CORS 配置
# ✅ Specific origins only
CORS_CONFIG = {
"origins": [
"https://app.example.com",
"https://admin.example.com",
],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_credentials": True,
"max_age": 3600,
}
# ❌ NEVER in production
CORS_CONFIG = {
"origins": "*", # Allows any origin!
}
API 响应安全
# ❌ Never expose internal errors
def handle_error(error):
# Don't send: stack traces, SQL errors, file paths
return {"error": "An error occurred"}, 500
# ✅ Log details, return generic message
def handle_error(error):
logger.error(f"Internal error: {error}", exc_info=True)
return {"error": "Internal server error"}, 500
安全标头
SECURITY_HEADERS = {
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Content-Security-Policy": "default-src 'self'",
"Referrer-Policy": "strict-origin-when-cross-origin",
}
密钥管理
环境变量
# ✅ Store secrets in environment
export DATABASE_URL="postgres://..."
export API_KEY="..."
export JWT_SECRET="..."
# ❌ NEVER commit secrets to code
DATABASE_URL = "postgres://user:password@host/db" # In code!
密钥要求
| 密钥类型 | 轮换周期 | 存储方式 |
|---|---|---|
| API 密钥 | 90 天 | Vault/环境变量 |
| 数据库密码 | 90 天 | Vault/环境变量 |
| JWT 密钥 | 30 天 | Vault/环境变量 |
| 加密密钥 | 1 年 | HSM/KMS |
| 用户密码 | 泄露时 | 在数据库中哈希存储 |
.gitignore
# Secrets - NEVER commit
.env
.env.local
.env.*.local
*.pem
*.key
credentials.json
secrets.yaml
config/secrets/*
文件上传安全
验证清单
def validate_upload(file) -> bool:
"""Validate uploaded file."""
# 1. Check file size
MAX_SIZE = 10 * 1024 * 1024 # 10MB
if file.size > MAX_SIZE:
raise ValueError("File too large")
# 2. Check extension against allowlist
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.pdf'}
ext = Path(file.name).suffix.lower()
if ext not in ALLOWED_EXTENSIONS:
raise ValueError("File type not allowed")
# 3. Verify magic bytes (don't trust extension)
magic_bytes = file.read(8)
file.seek(0)
if not is_valid_magic(magic_bytes, ext):
raise ValueError("File content doesn't match extension")
# 4. Generate safe filename
safe_name = generate_safe_filename(file.name)
# 5. Store outside web root
storage_path = UPLOAD_DIR / safe_name # Not in /public!
return True
安全文件名生成
import uuid
import re
from pathlib import Path
def generate_safe_filename(original: str) -> str:
"""Generate safe filename, preserving extension."""
ext = Path(original).suffix.lower()
# Use UUID, not original filename
return f"{uuid.uuid4()}{ext}"
日志记录与监控
应记录的内容
# ✅ Log these security events
SECURITY_EVENTS = [
"login_success",
"login_failure",
"logout",
"password_change",
"password_reset_request",
"permission_denied",
"invalid_token",
"rate_limit_exceeded",
"suspicious_input",
"admin_action",
]
不应记录的内容
# ❌ NEVER log these
NEVER_LOG = [
"password",
"password_hash",
"credit_card",
"ssn",
"api_key",
"session_token",
"jwt_token",
"private_key",
]
结构化安全日志
def log_security_event(event_type: str, user_id: str, details: dict):
"""Log security event with context."""
logger.info("security_event", extra={
"event_type": event_type,
"user_id": user_id,
"timestamp": datetime.utcnow().isoformat(),
"ip_address": get_client_ip(),
"user_agent": get_user_agent(),
**sanitize_details(details),
})
构建者清单
在编写处理用户数据、身份验证或外部输入的代码之前,请根据这些约束核对你的计划。构建者在编写代码前阅读本节;重构者则使用“强制规则”表和完整说明。
每次发布前
- 代码或版本控制中没有密钥
- 所有用户输入均已验证和清理
- SQL 查询使用参数化语句
- 所有受保护的端点均已启用身份验证
- 所有资源均已进行授权检查
- 强制使用 HTTPS(不使用 HTTP)
- 已配置安全标头
- 已启用速率限制
- 错误消息不会泄露敏感信息
- 依赖项已更新并经过审计
- 文件上传已验证并安全存储
- 日志记录了安全事件
- CORS 配置严格
代码审查安全重点
- 所有入口点均进行输入验证
- 对输出进行编码以防止 XSS
- 已进行访问控制检查
- 没有硬编码密钥
- 错误处理得当(不显示堆栈跟踪)
- 使用安全的默认设置
- 第三方库确有必要且可信
强制规则
这些规则由 check.js(clean-team)进行确定性检查。更新这些标准时,请同步更新对应的 check.js 规则,反之亦然。
| 规则 ID | 严重性 | 检查内容 |
|---|---|---|
no-document-write | 错误 | 使用 document.write()(存在 DOM 注入风险) |
no-hardcoded-secrets | 错误 | 字符串字面量中的 API 密钥、令牌、密码 |
no-innerHTML | 警告 | 赋值给 .innerHTML(存在 XSS 风险) |
参考资料
references/owasp-top-10.md— OWASP 漏洞详细指南references/input-validation.md— 完整的输入验证模式references/auth-patterns.md— 身份验证和授权模式
资源文件
assets/security-checklist.md— 发布前安全检查清单assets/threat-model-template.md— 威胁建模模板
脚本
scripts/scan_secrets.py— 扫描代码中意外提交的密钥scripts/check_dependencies.py— 检查依赖项是否存在已知漏洞
评论
支持 Markdown