Security Engineer by daffy0208/ai-dev-standards
npx skills add https://github.com/daffy0208/ai-dev-standards --skill 'Security Engineer'安全不是可选项——从第一天起就将其构建其中。
安全是内建的,而非外挂的。
每个功能、每个端点、每个数据流都必须考虑安全影响。在生产环境中修复安全漏洞的成本是开发期间的10倍。
身份验证: 你是谁? 授权: 你能做什么?
JWT (JSON Web Tokens):
适用场景: 无状态 API、移动应用、微服务
方法: 使用密钥签名令牌,存储在 httpOnly cookie 或 Authorization 请求头中
安全性: 使用 RS256(而非 HS256),设置短有效期(15分钟访问令牌,7天刷新令牌)
// 示例:使用 JWT 的 Next.js API import { SignJWT, jwtVerify } from 'jose'
const secret = new TextEncoder().encode(process.env.JWT_SECRET!)
export async function createToken(userId: string) { return await new SignJWT({ userId }) .setProtectedHeader({ alg: 'HS256' }) .setExpirationTime('15m') .sign(secret) }
export async function verifyToken(token: string) { const { payload } = await jwtVerify(token, secret) return payload }
基于会话:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
OAuth 2.0 / OIDC:
RBAC (基于角色的访问控制):
// 定义角色
enum Role {
ADMIN = 'admin',
USER = 'user',
GUEST = 'guest'
}
// 检查权限
function requireRole(allowedRoles: Role[]) {
return (req, res, next) => {
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' })
}
next()
}
}
// 用法
app.delete('/api/users/:id', requireRole([Role.ADMIN]), deleteUser)
ABAC (基于属性):
关键原则:
绝不信任用户输入 - 验证、净化、转义所有内容。
❌ 错误做法(存在漏洞):
// 不要这样做!
const query = `SELECT * FROM users WHERE email = '${userInput}'`
db.query(query) // SQL 注入漏洞!
✅ 正确做法(参数化查询):
// 始终使用参数化查询
const query = 'SELECT * FROM users WHERE email = ?'
db.query(query, [userInput]) // 安全 - 参数化
// 使用 Prisma
const user = await prisma.user.findUnique({
where: { email: userInput } // 安全 - ORM 会处理
})
❌ 错误做法(存在漏洞):
// 不要这样做!
<div dangerouslySetInnerHTML={{ __html: userInput }} />
✅ 正确做法(转义):
// React 自动转义
;<div>{userInput}</div> // 安全
// 如果必须渲染 HTML,先进行净化
import DOMPurify from 'isomorphic-dompurify'
;<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
import { z } from 'zod'
const UserSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(8).max(100),
age: z.number().int().min(13).max(120),
website: z.string().url().optional()
})
// 验证
const result = UserSchema.safeParse(req.body)
if (!result.success) {
return res.status(400).json({ errors: result.error.issues })
}
const validData = result.data // 类型安全!
import multer from 'multer'
const upload = multer({
limits: {
fileSize: 5 * 1024 * 1024 // 最大 5MB
},
fileFilter: (req, file, cb) => {
// 白名单文件类型
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
if (!allowedTypes.includes(file.mimetype)) {
return cb(new Error('Invalid file type'))
}
cb(null, true)
}
})
// 生成随机文件名(不要信任用户输入)
const filename = crypto.randomUUID() + path.extname(file.originalname)
关键原则:
绝不提交密钥:
# .env (添加到 .gitignore!)
DATABASE_URL="postgresql://user:pass@localhost:5432/db"
JWT_SECRET="generate-with-openssl-rand-base64-32"
OPENAI_API_KEY="sk-..."
在代码中访问:
// 启动时验证环境变量
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET is required')
}
const config = {
jwtSecret: process.env.JWT_SECRET,
databaseUrl: process.env.DATABASE_URL
}
密钥管理(生产环境):
// Next.js 中间件
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// 防止点击劫持
response.headers.set('X-Frame-Options', 'DENY')
// 防止 MIME 类型嗅探
response.headers.set('X-Content-Type-Options', 'nosniff')
// XSS 防护
response.headers.set('X-XSS-Protection', '1; mode=block')
// 内容安全策略
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
)
// 仅 HTTPS
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
return response
}
// 正确配置 CORS
app.use(
cors({
origin:
process.env.NODE_ENV === 'production' ? 'https://yourdomain.com' : 'http://localhost:3000',
credentials: true, // 允许 cookie
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
})
)
关键原则:
❌ 切勿以明文存储密码或使用 MD5/SHA1!
✅ 使用 bcrypt 或 argon2:
import bcrypt from 'bcrypt'
// 哈希密码(注册时)
const saltRounds = 12
const hashedPassword = await bcrypt.hash(password, saltRounds)
await db.user.create({
email,
password: hashedPassword // 存储哈希值,切勿明文
})
// 验证密码(登录时)
const user = await db.user.findUnique({ where: { email } })
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) {
throw new Error('Invalid credentials')
}
敏感数据应被加密:
import crypto from 'crypto'
const algorithm = 'aes-256-gcm'
const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex')
function encrypt(text: string) {
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv(algorithm, key, iv)
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()])
const authTag = cipher.getAuthTag()
return {
iv: iv.toString('hex'),
encryptedData: encrypted.toString('hex'),
authTag: authTag.toString('hex')
}
}
function decrypt(encrypted: any) {
const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(encrypted.iv, 'hex'))
decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex'))
return decipher.update(encrypted.encryptedData, 'hex', 'utf8') + decipher.final('utf8')
}
// 加密敏感字段
const ssn = encrypt(user.ssn)
await db.user.update({ where: { id }, data: { ssnEncrypted: JSON.stringify(ssn) } })
始终使用 HTTPS:
mkcert 实现本地 HTTPS验证外部 API 证书:
// 在生产环境中不要禁用 SSL 验证!
fetch(url, {
// 不要这样做:agent: new https.Agent({ rejectUnauthorized: false })
})
关键原则:
防止暴力破解和 DDoS:
import rateLimit from 'express-rate-limit'
// 全局速率限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 每个窗口 100 次请求
message: 'Too many requests from this IP'
})
app.use(limiter)
// 对身份验证端点使用更严格的限制
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 每 15 分钟仅允许 5 次登录尝试
skipSuccessfulRequests: true
})
app.post('/api/auth/login', authLimiter, loginHandler)
记录所有与安全相关的事件:
async function auditLog(event: {
userId?: string
action: string
resource: string
ip: string
userAgent: string
success: boolean
metadata?: any
}) {
await db.auditLog.create({
data: {
...event,
timestamp: new Date()
}
})
}
// 用法
await auditLog({
userId: user.id,
action: 'LOGIN',
resource: 'auth',
ip: req.ip,
userAgent: req.headers['user-agent'],
success: true
})
不要在错误中泄露敏感信息:
❌ 错误做法:
// 暴露了数据库结构!
catch (error) {
res.status(500).json({ error: error.message })
}
✅ 正确做法:
catch (error) {
// 在服务器端记录完整错误
console.error('Error:', error)
// 向客户端发送通用消息
res.status(500).json({
error: 'Internal server error',
requestId: generateRequestId() // 用于支持
})
}
可集成的工具:
关键原则:
---|---|---
1 | 失效的访问控制 | 在服务器端验证权限,默认拒绝
2 | 加密机制失效 | 使用 TLS,哈希密码(bcrypt),加密 PII
3 | 注入 | 参数化查询,输入验证
4 | 不安全设计 | 威胁建模,安全需求
5 | 安全配置错误 | 安全默认值,移除未使用的功能
6 | 易受攻击的组件 | 保持依赖项更新,使用 Dependabot
7 | 身份验证和会话管理失效 | 强密码,MFA,安全的会话管理
8 | 软件和数据完整性失效 | 验证依赖项,对发布版本签名
9 | 日志记录和监控失效 | 记录安全事件,设置警报
10 | 服务器端请求伪造 | 验证 URL,白名单允许的域名
npm audit)❌ 错误做法:
// 隐藏 UI 并不能防止访问!
{
user.role === 'admin' && <DeleteButton />
}
✅ 正确做法:
// 始终在服务器端验证
app.delete('/api/users/:id', requireAdmin, deleteUser)
❌ 错误做法:
// 用户可以在请求中操纵 userId!
const userId = req.body.userId
await db.order.create({ data: { userId } })
✅ 正确做法:
// 从已验证的会话中获取 userId
const userId = req.user.id // 来自 JWT/会话
await db.order.create({ data: { userId } })
❌ 错误做法:
if (password.length < 6) throw new Error('Too short')
✅ 正确做法:
const passwordSchema = z
.string()
.min(8, '至少 8 个字符')
.regex(/[A-Z]/, '需要大写字母')
.regex(/[a-z]/, '需要小写字母')
.regex(/[0-9]/, '需要数字')
❌ 错误做法:
// 用户可以通过更改 ID 访问任何文档!
const doc = await db.document.findUnique({ where: { id: req.params.id } })
return res.json(doc)
✅ 正确做法:
// 验证所有权
const doc = await db.document.findFirst({
where: {
id: req.params.id,
userId: req.user.id // 确保用户拥有此文档
}
})
if (!doc) return res.status(404).json({ error: 'Not found' })
return res.json(doc)
// middleware.ts - 保护路由
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')
// 如果没有令牌,重定向到登录页
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/api/private/:path*']
}
import helmet from 'helmet'
import mongoSanitize from 'express-mongo-sanitize'
app.use(helmet()) // 安全响应头
app.use(express.json({ limit: '10mb' })) // 防止大负载
app.use(mongoSanitize()) // 防止 NoSQL 注入
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
async def get_current_user(token: str = Depends(security)):
try:
payload = verify_token(token.credentials)
return payload['user_id']
except:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/protected")
async def protected_route(user_id: str = Depends(get_current_user)):
return {"user_id": user_id}
' OR '1'='1<script>alert('XSS')</script>../../etc/passwd在以下情况下使用安全工程师技能:
技能:
api-designer - API 设计模式(与安全配对)testing-strategist - 安全测试策略deployment-advisor - 生产安全配置模式:
/STANDARDS/architecture-patterns/authentication-patterns.md/STANDARDS/best-practices/security-best-practices.md外部资源:
安全是每个人的责任。从第一天起就将其构建其中。 🔒
每周安装
0
仓库
GitHub 星标
18
首次出现
1970年1月1日
安全审计
Security is not optional - build it in from day one.
Security is built-in, not bolted-on.
Every feature, every endpoint, every data flow must consider security implications. Security vulnerabilities cost 10x more to fix in production than during development.
Authentication: Who are you? Authorization: What can you do?
JWT (JSON Web Tokens):
When: Stateless APIs, mobile apps, microservices
How: Sign tokens with secret, store in httpOnly cookies or Authorization header
Security: Use RS256 (not HS256), short expiry (15min access, 7d refresh)
// Example: Next.js API with JWT import { SignJWT, jwtVerify } from 'jose'
const secret = new TextEncoder().encode(process.env.JWT_SECRET!)
export async function createToken(userId: string) { return await new SignJWT({ userId }) .setProtectedHeader({ alg: 'HS256' }) .setExpirationTime('15m') .sign(secret) }
export async function verifyToken(token: string) { const { payload } = await jwtVerify(token, secret) return payload }
Session-Based:
OAuth 2.0 / OIDC:
RBAC (Role-Based Access Control):
// Define roles
enum Role {
ADMIN = 'admin',
USER = 'user',
GUEST = 'guest'
}
// Check permissions
function requireRole(allowedRoles: Role[]) {
return (req, res, next) => {
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' })
}
next()
}
}
// Usage
app.delete('/api/users/:id', requireRole([Role.ADMIN]), deleteUser)
ABAC (Attribute-Based):
Key Principles:
Never trust user input - validate, sanitize, escape everything.
❌ Bad (Vulnerable):
// DON'T DO THIS!
const query = `SELECT * FROM users WHERE email = '${userInput}'`
db.query(query) // SQL injection vulnerability!
✅ Good (Parameterized Queries):
// Always use parameterized queries
const query = 'SELECT * FROM users WHERE email = ?'
db.query(query, [userInput]) // Safe - parameterized
// With Prisma
const user = await prisma.user.findUnique({
where: { email: userInput } // Safe - ORM handles it
})
❌ Bad (Vulnerable):
// DON'T DO THIS!
<div dangerouslySetInnerHTML={{ __html: userInput }} />
✅ Good (Escaped):
// React automatically escapes
;<div>{userInput}</div> // Safe
// If you must render HTML, sanitize first
import DOMPurify from 'isomorphic-dompurify'
;<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
import { z } from 'zod'
const UserSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(8).max(100),
age: z.number().int().min(13).max(120),
website: z.string().url().optional()
})
// Validate
const result = UserSchema.safeParse(req.body)
if (!result.success) {
return res.status(400).json({ errors: result.error.issues })
}
const validData = result.data // Type-safe!
import multer from 'multer'
const upload = multer({
limits: {
fileSize: 5 * 1024 * 1024 // 5MB max
},
fileFilter: (req, file, cb) => {
// Whitelist file types
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
if (!allowedTypes.includes(file.mimetype)) {
return cb(new Error('Invalid file type'))
}
cb(null, true)
}
})
// Generate random filenames (don't trust user input)
const filename = crypto.randomUUID() + path.extname(file.originalname)
Key Principles:
Never commit secrets:
# .env (add to .gitignore!)
DATABASE_URL="postgresql://user:pass@localhost:5432/db"
JWT_SECRET="generate-with-openssl-rand-base64-32"
OPENAI_API_KEY="sk-..."
Access in code:
// Validate env vars on startup
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET is required')
}
const config = {
jwtSecret: process.env.JWT_SECRET,
databaseUrl: process.env.DATABASE_URL
}
Secret Management (Production):
// Next.js middleware
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Prevent clickjacking
response.headers.set('X-Frame-Options', 'DENY')
// Prevent MIME sniffing
response.headers.set('X-Content-Type-Options', 'nosniff')
// XSS Protection
response.headers.set('X-XSS-Protection', '1; mode=block')
// Content Security Policy
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
)
// HTTPS only
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
return response
}
// Configure CORS properly
app.use(
cors({
origin:
process.env.NODE_ENV === 'production' ? 'https://yourdomain.com' : 'http://localhost:3000',
credentials: true, // Allow cookies
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
})
)
Key Principles:
❌ Never store passwords in plain text or use MD5/SHA1!
✅ Use bcrypt or argon2:
import bcrypt from 'bcrypt'
// Hash password (on signup)
const saltRounds = 12
const hashedPassword = await bcrypt.hash(password, saltRounds)
await db.user.create({
email,
password: hashedPassword // Store hash, never plain text
})
// Verify password (on login)
const user = await db.user.findUnique({ where: { email } })
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) {
throw new Error('Invalid credentials')
}
Sensitive data should be encrypted:
import crypto from 'crypto'
const algorithm = 'aes-256-gcm'
const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex')
function encrypt(text: string) {
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv(algorithm, key, iv)
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()])
const authTag = cipher.getAuthTag()
return {
iv: iv.toString('hex'),
encryptedData: encrypted.toString('hex'),
authTag: authTag.toString('hex')
}
}
function decrypt(encrypted: any) {
const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(encrypted.iv, 'hex'))
decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex'))
return decipher.update(encrypted.encryptedData, 'hex', 'utf8') + decipher.final('utf8')
}
// Encrypt sensitive fields
const ssn = encrypt(user.ssn)
await db.user.update({ where: { id }, data: { ssnEncrypted: JSON.stringify(ssn) } })
Always use HTTPS:
mkcert for local HTTPSVerify external API certificates:
// Don't disable SSL verification in production!
fetch(url, {
// DON'T: agent: new https.Agent({ rejectUnauthorized: false })
})
Key Principles:
Prevent brute force and DDoS:
import rateLimit from 'express-rate-limit'
// Global rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Too many requests from this IP'
})
app.use(limiter)
// Stricter limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts per 15min
skipSuccessfulRequests: true
})
app.post('/api/auth/login', authLimiter, loginHandler)
Log all security-relevant events:
async function auditLog(event: {
userId?: string
action: string
resource: string
ip: string
userAgent: string
success: boolean
metadata?: any
}) {
await db.auditLog.create({
data: {
...event,
timestamp: new Date()
}
})
}
// Usage
await auditLog({
userId: user.id,
action: 'LOGIN',
resource: 'auth',
ip: req.ip,
userAgent: req.headers['user-agent'],
success: true
})
Don't leak sensitive info in errors:
❌ Bad:
// Exposes database structure!
catch (error) {
res.status(500).json({ error: error.message })
}
✅ Good:
catch (error) {
// Log full error server-side
console.error('Error:', error)
// Send generic message to client
res.status(500).json({
error: 'Internal server error',
requestId: generateRequestId() // For support
})
}
Tools to integrate:
Key Principles:
---|---|---
1 | Broken Access Control | Verify permissions server-side, default deny
2 | Cryptographic Failures | Use TLS, hash passwords (bcrypt), encrypt PII
3 | Injection | Parameterized queries, input validation
4 | Insecure Design | Threat modeling, security requirements
5 | Security Misconfiguration | Secure defaults, remove unused features
6 | Vulnerable Components | Keep dependencies updated, use Dependabot
7 | Auth & Session Issues | Strong passwords, MFA, secure session mgmt
8 | Software & Data Integrity | Verify dependencies, sign releases
9 | Logging & Monitoring Failures | Log security events, set up alerts
10 | SSRF | Validate URLs, whitelist allowed domains
npm audit)❌ Wrong:
// Hiding UI doesn't prevent access!
{
user.role === 'admin' && <DeleteButton />
}
✅ Right:
// Always verify on server
app.delete('/api/users/:id', requireAdmin, deleteUser)
❌ Wrong:
// User can manipulate userId in request!
const userId = req.body.userId
await db.order.create({ data: { userId } })
✅ Right:
// Get userId from authenticated session
const userId = req.user.id // From JWT/session
await db.order.create({ data: { userId } })
❌ Wrong:
if (password.length < 6) throw new Error('Too short')
✅ Right:
const passwordSchema = z
.string()
.min(8, 'At least 8 characters')
.regex(/[A-Z]/, 'Needs uppercase')
.regex(/[a-z]/, 'Needs lowercase')
.regex(/[0-9]/, 'Needs number')
❌ Wrong:
// User can access any document by changing ID!
const doc = await db.document.findUnique({ where: { id: req.params.id } })
return res.json(doc)
✅ Right:
// Verify ownership
const doc = await db.document.findFirst({
where: {
id: req.params.id,
userId: req.user.id // Ensure user owns this document
}
})
if (!doc) return res.status(404).json({ error: 'Not found' })
return res.json(doc)
// middleware.ts - Protect routes
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')
// Redirect to login if no token
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/api/private/:path*']
}
import helmet from 'helmet'
import mongoSanitize from 'express-mongo-sanitize'
app.use(helmet()) // Security headers
app.use(express.json({ limit: '10mb' })) // Prevent large payloads
app.use(mongoSanitize()) // Prevent NoSQL injection
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
async def get_current_user(token: str = Depends(security)):
try:
payload = verify_token(token.credentials)
return payload['user_id']
except:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/protected")
async def protected_route(user_id: str = Depends(get_current_user)):
return {"user_id": user_id}
' OR '1'='1<script>alert('XSS')</script>../../etc/passwdUse security-engineer skill when:
Skills:
api-designer - API design patterns (pairs with security)testing-strategist - Security testing strategiesdeployment-advisor - Production security configurationPatterns:
/STANDARDS/architecture-patterns/authentication-patterns.md/STANDARDS/best-practices/security-best-practices.mdExternal:
Security is everyone's responsibility. Build it in from day one. 🔒
Weekly Installs
0
Repository
GitHub Stars
18
First Seen
Jan 1, 1970
Security Audits
Azure RBAC 权限管理工具:查找最小角色、创建自定义角色与自动化分配
117,000 周安装