security by alinaqi/claude-bootstrap
npx skills add https://github.com/alinaqi/claude-bootstrap --skill security加载方式:base.md
适用于所有项目的安全最佳实践和自动化安全测试。
安全不是可选项。 每个项目在合并前都必须通过安全检查。假设所有输入都是恶意的,所有密钥一旦提交就会泄露,所有依赖项都存在漏洞。
每个项目都必须在 .gitignore 中包含以下内容:
# 环境文件 - 切勿提交
.env
.env.*
!.env.example
# 密钥
*.pem
*.key
*.p12
*.pfx
credentials.json
secrets.json
*-credentials.json
service-account*.json
# IDE 和操作系统
.idea/
.vscode/settings.json
.DS_Store
Thumbs.db
# 依赖项
node_modules/
__pycache__/
*.pyc
.venv/
venv/
# 构建输出
dist/
build/
*.egg-info/
# 可能包含敏感数据的日志
*.log
logs/
创建 .env.example,包含所有必需的变量(不包含值):
# .env.example - 复制到 .env 并填写值
# 仅限服务器端(切勿使用 VITE_ 或 NEXT_PUBLIC_ 前缀)
DATABASE_URL=
ANTHROPIC_API_KEY=
SUPABASE_SERVICE_ROLE_KEY=
# 客户端安全(公开、非敏感)
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
切勿将密钥放入客户端暴露的环境变量中:
| 框架 | 客户端暴露前缀 | 仅限服务器端 |
|---|---|---|
| Vite | VITE_* | 无前缀 |
| Next.js | NEXT_PUBLIC_* | 无前缀 |
| Create React App | REACT_APP_* | 不适用(无服务器) |
// 错误 - 密钥暴露给浏览器捆绑包!
const apiKey = import.meta.env.VITE_ANTHROPIC_API_KEY;
// 正确 - 仅客户端公开值
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
// 正确 - 密钥仅保留在服务器端
// 在 API 路由或服务器函数中:
const apiKey = process.env.ANTHROPIC_API_KEY;
Vercel 环境变量:
VITE_ 前缀的密钥是仅限服务器端的VITE_* 变量会被捆绑到客户端代码中在启动时验证环境:
// config/env.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
ANTHROPIC_API_KEY: z.string().min(1),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = envSchema.parse(process.env);
# config/env.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
anthropic_api_key: str
environment: str = "development"
class Config:
env_file = ".env"
settings = Settings()
添加到预提交钩子中:
适用于所有项目:
# .pre-commit-config.yaml(添加到现有配置中)
repos:
# 检测密钥
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
# 检查依赖项中的安全问题
- repo: local
hooks:
- id: security-check
name: security-check
entry: ./scripts/security-check.sh
language: script
pass_filenames: false
TypeScript/JavaScript:
// package.json 脚本
{
"scripts": {
"security:audit": "npm audit --audit-level=high",
"security:secrets": "npx secretlint '**/*'",
"security:deps": "npx better-npm-audit audit"
}
}
Python:
# 添加到开发依赖项
pip install safety bandit
# 命令
safety check # 检查依赖项的漏洞
bandit -r src/ # 静态安全分析
创建 scripts/security-check.sh:
#!/bin/bash
set -e
echo "Running security checks..."
# 检查暂存文件中的密钥
echo "Checking for secrets..."
if command -v detect-secrets &> /dev/null; then
detect-secrets scan --baseline .secrets.baseline
fi
# 检查 .env 是否未暂存
if git diff --cached --name-only | grep -E '^\.env$|^\.env\.' | grep -v '\.example$'; then
echo "ERROR: .env file is staged for commit!"
exit 1
fi
# 检查暂存文件中的常见密钥模式
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if echo "$STAGED_FILES" | xargs grep -l -E '(password|secret|api_key|apikey|token|private_key)\s*[:=]\s*["\047][^"\047]+["\047]' 2>/dev/null; then
echo "ERROR: Possible secrets found in staged files!"
exit 1
fi
# 特定语言检查
if [ -f "package.json" ]; then
echo "Checking npm dependencies..."
npm audit --audit-level=high || echo "Warning: npm audit found issues"
fi
if [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then
echo "Checking Python dependencies..."
if command -v safety &> /dev/null; then
safety check || echo "Warning: safety found issues"
fi
fi
echo "Security checks passed!"
chmod +x scripts/security-check.sh
创建 .github/workflows/security.yml:
name: Security
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# 每周一 UTC 时间上午 9 点运行
- cron: '0 9 * * 1'
jobs:
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
dependency-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Node.js 项目
- name: Setup Node
if: hashFiles('package.json') != ''
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
if: hashFiles('package.json') != ''
run: npm ci
- name: NPM Audit
if: hashFiles('package.json') != ''
run: npm audit --audit-level=high
# Python 项目
- name: Setup Python
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install safety
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
run: pip install safety
- name: Safety check
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
run: safety check
codeql:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ hashFiles('package.json') != '' && 'javascript-typescript' || 'python' }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
切勿使用字符串拼接:
// 错误 - 易受 SQL 注入攻击
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
// 正确 - 参数化查询
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
// 正确 - 使用 ORM(Kysely、Prisma、Drizzle)
const user = await db.selectFrom('users').where('id', '=', userId).execute();
# 错误 - 易受 SQL 注入攻击
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# 正确 - 参数化查询
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# 正确 - 使用 ORM(SQLAlchemy)
user = session.query(User).filter(User.id == user_id).first()
// 在渲染前始终对用户输入进行清理
import DOMPurify from 'dompurify';
// 错误 - 易受 XSS 攻击
element.innerHTML = userInput;
// 正确 - 已清理
element.innerHTML = DOMPurify.sanitize(userInput);
// 最佳 - 使用框架内置的转义功能(React 默认如此)
return <div>{userInput}</div>; // 在 React 中是安全的
// 危险 - 绕过 React 的保护
return <div dangerouslySetInnerHTML={{ __html: userInput }} />; // 避免!
// 使用 Zod 验证所有外部输入
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s]+$/),
age: z.number().int().min(0).max(150),
});
// 在路由处理器中
app.post('/users', async (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
// result.data 现在已类型化并经过验证
});
import path from 'path';
// 错误 - 易受路径遍历攻击
const filePath = `./uploads/${req.params.filename}`;
// 正确 - 验证并清理路径
const filename = path.basename(req.params.filename); // 去除 ../
const filePath = path.join('./uploads', filename);
// 验证它仍在允许的目录内
if (!filePath.startsWith(path.resolve('./uploads'))) {
throw new Error('Invalid path');
}
import jwt from 'jsonwebtoken';
// 令牌生成
function generateToken(userId: string): string {
return jwt.sign(
{ sub: userId },
process.env.JWT_SECRET!,
{
expiresIn: '15m', // 短期访问令牌
algorithm: 'HS256',
}
);
}
// 令牌验证
function verifyToken(token: string): { sub: string } {
return jwt.verify(token, process.env.JWT_SECRET!, {
algorithms: ['HS256'], // 明确指定允许的算法
}) as { sub: string };
}
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12; // 最少 10,推荐 12+
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(password: str, hashed: str) -> bool:
return pwd_context.verify(password, hashed)
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 每个窗口 100 个请求
standardHeaders: true,
legacyHeaders: false,
});
// 应用于认证路由
app.use('/api/auth', rateLimit({
windowMs: 60 * 1000, // 1 分钟
max: 5, // 每分钟 5 次尝试
message: '登录尝试过多,请稍后再试',
}));
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
},
}));
每次发布前运行:
## 安全清单
### 密钥与环境
- [ ] 代码中无密钥(运行 detect-secrets)
- [ ] .env 文件在 .gitignore 中
- [ ] .env.example 存在并包含所有必需变量
- [ ] 启动时已验证环境
### 依赖项
- [ ] npm audit / safety check 通过
- [ ] 依赖项中无已知漏洞
- [ ] 依赖项已更新(已启用 Dependabot)
### 输入验证
- [ ] 所有 API 输入均使用模式(Zod/Pydantic)验证
- [ ] 文件上传按类型和大小限制
- [ ] 已防止路径遍历
### 认证
- [ ] 密码使用 bcrypt 哈希(12+ 轮)
- [ ] JWT 使用短期过期时间
- [ ] 认证端点启用速率限制
- [ ] 登录时轮换会话令牌
### 数据库
- [ ] 仅使用参数化查询
- [ ] 最小权限数据库用户
- [ ] 不记录连接字符串
### 头部与 CORS
- [ ] 启用安全头部(helmet)
- [ ] CORS 限制为已知来源
- [ ] 生产环境仅使用 HTTPS
### 日志记录
- [ ] 日志中无密钥
- [ ] 日志中无 PII(或已适当掩码)
- [ ] 记录失败的认证尝试
VITE_*、NEXT_PUBLIC_* 或 REACT_APP_* 环境变量中(客户端暴露!)dangerouslySetInnerHTML 未进行清理eval() 或 new Function() 使用用户输入* 作为 CORS 来源每周安装次数
122
仓库
GitHub 星标数
529
首次出现
2026年1月20日
安全审计
安装于
opencode102
gemini-cli99
claude-code99
codex88
cursor85
antigravity81
Load with: base.md
Security best practices and automated security testing for all projects.
Security is not optional. Every project must pass security checks before merge. Assume all input is malicious, all secrets will leak if committed, and all dependencies have vulnerabilities.
Every project must have these in .gitignore:
# Environment files - NEVER commit
.env
.env.*
!.env.example
# Secrets
*.pem
*.key
*.p12
*.pfx
credentials.json
secrets.json
*-credentials.json
service-account*.json
# IDE and OS
.idea/
.vscode/settings.json
.DS_Store
Thumbs.db
# Dependencies
node_modules/
__pycache__/
*.pyc
.venv/
venv/
# Build outputs
dist/
build/
*.egg-info/
# Logs that might contain sensitive data
*.log
logs/
Create.env.example with all required vars (no values):
# .env.example - Copy to .env and fill in values
# Server-side only (NEVER prefix with VITE_ or NEXT_PUBLIC_)
DATABASE_URL=
ANTHROPIC_API_KEY=
SUPABASE_SERVICE_ROLE_KEY=
# Client-side safe (public, non-sensitive)
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=
NEVER put secrets in client-exposed env vars:
| Framework | Client-Exposed Prefix | Server-Only |
|---|---|---|
| Vite | VITE_* | No prefix |
| Next.js | NEXT_PUBLIC_* | No prefix |
| Create React App | REACT_APP_* | N/A (no server) |
// WRONG - Secret exposed to browser bundle!
const apiKey = import.meta.env.VITE_ANTHROPIC_API_KEY;
// CORRECT - Only public values client-side
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
// CORRECT - Secrets stay server-side only
// In API route or server function:
const apiKey = process.env.ANTHROPIC_API_KEY;
Vercel Environment Variables:
VITE_ prefix are server-onlyVITE_* vars are bundled into client codeValidate environment at startup:
// config/env.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
ANTHROPIC_API_KEY: z.string().min(1),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = envSchema.parse(process.env);
# config/env.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
anthropic_api_key: str
environment: str = "development"
class Config:
env_file = ".env"
settings = Settings()
Add to pre-commit hooks:
For all projects:
# .pre-commit-config.yaml (add to existing)
repos:
# Detect secrets
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
# Check for security issues in dependencies
- repo: local
hooks:
- id: security-check
name: security-check
entry: ./scripts/security-check.sh
language: script
pass_filenames: false
TypeScript/JavaScript:
// package.json scripts
{
"scripts": {
"security:audit": "npm audit --audit-level=high",
"security:secrets": "npx secretlint '**/*'",
"security:deps": "npx better-npm-audit audit"
}
}
Python:
# Add to dev dependencies
pip install safety bandit
# Commands
safety check # Check dependencies for vulnerabilities
bandit -r src/ # Static security analysis
Create scripts/security-check.sh:
#!/bin/bash
set -e
echo "Running security checks..."
# Check for secrets in staged files
echo "Checking for secrets..."
if command -v detect-secrets &> /dev/null; then
detect-secrets scan --baseline .secrets.baseline
fi
# Check .env is not staged
if git diff --cached --name-only | grep -E '^\.env$|^\.env\.' | grep -v '\.example$'; then
echo "ERROR: .env file is staged for commit!"
exit 1
fi
# Check for common secret patterns in staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if echo "$STAGED_FILES" | xargs grep -l -E '(password|secret|api_key|apikey|token|private_key)\s*[:=]\s*["\047][^"\047]+["\047]' 2>/dev/null; then
echo "ERROR: Possible secrets found in staged files!"
exit 1
fi
# Language-specific checks
if [ -f "package.json" ]; then
echo "Checking npm dependencies..."
npm audit --audit-level=high || echo "Warning: npm audit found issues"
fi
if [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then
echo "Checking Python dependencies..."
if command -v safety &> /dev/null; then
safety check || echo "Warning: safety found issues"
fi
fi
echo "Security checks passed!"
chmod +x scripts/security-check.sh
Create .github/workflows/security.yml:
name: Security
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Run weekly on Monday at 9am UTC
- cron: '0 9 * * 1'
jobs:
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
dependency-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Node.js projects
- name: Setup Node
if: hashFiles('package.json') != ''
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
if: hashFiles('package.json') != ''
run: npm ci
- name: NPM Audit
if: hashFiles('package.json') != ''
run: npm audit --audit-level=high
# Python projects
- name: Setup Python
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install safety
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
run: pip install safety
- name: Safety check
if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
run: safety check
codeql:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ hashFiles('package.json') != '' && 'javascript-typescript' || 'python' }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
Never use string concatenation:
// BAD - SQL injection vulnerable
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
// GOOD - Parameterized query
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
// GOOD - Using ORM (Kysely, Prisma, Drizzle)
const user = await db.selectFrom('users').where('id', '=', userId).execute();
# BAD - SQL injection vulnerable
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# GOOD - Parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# GOOD - Using ORM (SQLAlchemy)
user = session.query(User).filter(User.id == user_id).first()
// Always sanitize user input before rendering
import DOMPurify from 'dompurify';
// BAD - XSS vulnerable
element.innerHTML = userInput;
// GOOD - Sanitized
element.innerHTML = DOMPurify.sanitize(userInput);
// BEST - Use framework's built-in escaping (React does this by default)
return <div>{userInput}</div>; // Safe in React
// DANGER - Bypasses React's protection
return <div dangerouslySetInnerHTML={{ __html: userInput }} />; // Avoid!
// Validate ALL external input with Zod
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s]+$/),
age: z.number().int().min(0).max(150),
});
// In route handler
app.post('/users', async (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
// result.data is now typed and validated
});
import path from 'path';
// BAD - Path traversal vulnerable
const filePath = `./uploads/${req.params.filename}`;
// GOOD - Validate and sanitize path
const filename = path.basename(req.params.filename); // Strips ../
const filePath = path.join('./uploads', filename);
// Verify it's still within allowed directory
if (!filePath.startsWith(path.resolve('./uploads'))) {
throw new Error('Invalid path');
}
import jwt from 'jsonwebtoken';
// Token generation
function generateToken(userId: string): string {
return jwt.sign(
{ sub: userId },
process.env.JWT_SECRET!,
{
expiresIn: '15m', // Short-lived access tokens
algorithm: 'HS256',
}
);
}
// Token verification
function verifyToken(token: string): { sub: string } {
return jwt.verify(token, process.env.JWT_SECRET!, {
algorithms: ['HS256'], // Explicitly specify allowed algorithms
}) as { sub: string };
}
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12; // Minimum 10, recommended 12+
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(password: str, hashed: str) -> bool:
return pwd_context.verify(password, hashed)
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
});
// Apply to auth routes
app.use('/api/auth', rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 5, // 5 attempts per minute
message: 'Too many login attempts, please try again later',
}));
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
},
}));
Run before every release:
## Security Checklist
### Secrets & Environment
- [ ] No secrets in code (run detect-secrets)
- [ ] .env files in .gitignore
- [ ] .env.example exists with all required vars
- [ ] Environment validated at startup
### Dependencies
- [ ] npm audit / safety check passes
- [ ] No known vulnerabilities in dependencies
- [ ] Dependencies up to date (Dependabot enabled)
### Input Validation
- [ ] All API inputs validated with schema (Zod/Pydantic)
- [ ] File uploads restricted by type and size
- [ ] Path traversal prevented
### Authentication
- [ ] Passwords hashed with bcrypt (12+ rounds)
- [ ] JWTs use short expiration
- [ ] Rate limiting on auth endpoints
- [ ] Session tokens rotated on login
### Database
- [ ] Parameterized queries only
- [ ] Least privilege database user
- [ ] Connection strings not logged
### Headers & CORS
- [ ] Security headers enabled (helmet)
- [ ] CORS restricted to known origins
- [ ] HTTPS only in production
### Logging
- [ ] No secrets in logs
- [ ] No PII in logs (or properly masked)
- [ ] Failed auth attempts logged
VITE_*, NEXT_PUBLIC_*, or REACT_APP_* env vars (client-exposed!)dangerouslySetInnerHTML without sanitizationeval() or new Function() with user input* for CORS origins in productionWeekly Installs
122
Repository
GitHub Stars
529
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode102
gemini-cli99
claude-code99
codex88
cursor85
antigravity81
Azure Data Explorer (Kusto) 查询技能:KQL数据分析、日志遥测与时间序列处理
119,800 周安装