关于 security-audit
security-audit 是面向代码库审查者的安全审计技能,按阶段检查常见漏洞与配置风险,涵盖认证、输入处理、数据保护和依赖项。
主要功能
- 01分阶段梳理代码库攻击面
- 02检查认证与访问控制
- 03排查注入和不安全输入处理
- 04检查敏感数据与密钥管理
- 05审查依赖漏洞和供应链风险
适用场景
- 01上线前进行安全审查
- 02响应安全事件或漏洞报告
- 03主要功能变更后检查安全控制
- 04准备安全合规审计
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mgd34msu/ goodvibes-plugin --skill security-audit安装后在 Claude Code 中输入/security-audit确认已加载。
附带文件references/security-patterns.mdscripts/validate-security-audit.sh
详细介绍
资源
scripts/
validate-security-audit.sh
references/
security-patterns.md
安全审计
此技能将引导你对代码库进行全面的安全审计,以识别漏洞、不安全模式和配置问题。在进行安全审查、准备生产部署或响应安全事件时使用此技能。
何时使用此技能
- 进行部署前安全审查
- 响应安全事件或漏洞报告
- 定期审计现有代码库
- 添加重大功能后验证安全控制
- 准备安全合规审计(SOC 2、ISO 27001)
- 帮助新团队成员了解安全标准
审计方法
系统的安全审计包含以下阶段:
阶段 1:侦察
**目标:**了解应用架构、技术栈和攻击面。
使用 precision_grep 绘制代码库结构:
precision_grep:
queries:
- id: auth_patterns
pattern: "(session|token|auth|login|password|jwt)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: api_endpoints
pattern: "(router\\.(get|post|put|delete)|export.*GET|export.*POST)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: database_queries
pattern: "(prisma\\.|db\\.|query\\(|execute\\()"
glob: "**/*.{ts,tsx,js,jsx}"
- id: env_usage
pattern: "process\\.env\\."
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: minimal
识别关键组件:
- 身份验证流程(登录、注册、密码重置)
- 授权中间件和守卫
- API 端点(公开与已验证身份)
- 数据库访问模式
- 文件上传/下载处理程序
- 支付处理逻辑
- 管理面板或特权操作
阶段 2:身份验证审计
**目标:**验证身份验证实现是否安全。
检查会话管理是否薄弱
搜索会话配置问题:
precision_grep:
queries:
- id: session_config
pattern: "(session|cookie).*secure.*false|httpOnly.*false|sameSite.*(none|lax)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: jwt_secrets
pattern: "jwt\\.sign.*secret.*[\"'][^\"']{1,20}[\"']|new.*JwtStrategy"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: standard
常见漏洞:
- 会话 Cookie 未设置
httpOnly标志(会暴露于 XSS 攻击) - 会话 Cookie 未设置
secure标志(允许通过 HTTP 传输) - JWT 密钥强度不足(熵低于 32 字节)
- 会话没有过期时间(会话永不过期)
- 用户登出时未使会话失效
安全的会话示例:
import { cookies } from 'next/headers';
export async function createSession(userId: string) {
const sessionToken = await generateSecureToken();
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
await db.session.create({
data: {
token: sessionToken,
userId,
expiresAt,
},
});
cookies().set('session', sessionToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
expires: expiresAt,
path: '/',
});
}
export async function invalidateSession(sessionToken: string) {
await db.session.delete({ where: { token: sessionToken } });
cookies().delete('session');
}
检查密码安全问题
搜索密码处理薄弱之处:
precision_grep:
queries:
- id: password_storage
pattern: "password.*=.*(req\\.body|params|query)|password.*toString|password.*text"
glob: "**/*.{ts,tsx,js,jsx}"
- id: password_hashing
pattern: "(bcrypt|argon2|scrypt|pbkdf2)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: verbose
常见漏洞:
- 密码以明文存储
- 哈希算法强度不足(MD5、SHA1、未加盐的 SHA256)
- 没有密码复杂度要求
- 密码提示或找回问题
- 密码被记录到日志中,或暴露在错误消息中
安全的密码哈希:
import { hash, verify } from '@node-rs/argon2';
export async function hashPassword(password: string): Promise<string> {
return await hash(password, {
memoryCost: 19456, // 19 MB
timeCost: 2,
outputLen: 32,
parallelism: 1,
});
}
export async function verifyPassword(
hash: string,
password: string
): Promise<boolean> {
try {
return await verify(hash, password);
} catch {
return false;
}
}
检查 MFA 实现
搜索 MFA 模式:
precision_grep:
queries:
- id: mfa_usage
pattern: "(totp|authenticator|2fa|mfa|otp)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: minimal
MFA 最佳实践:
- 使用身份验证器应用实现 TOTP(基于时间的一次性密码)
- 提供用于账户恢复的备用代码
- 将 SMS OTP 作为备用方式(而非主要方式)
- 使用 WebAuthn/FIDO2 硬件密钥
- 对 OTP 验证实施速率限制
阶段 3:授权审计
**目标:**确保访问控制和权限检查得当。
检查是否缺少授权检查
识别 API 端点:
precision_grep:
queries:
- id: api_routes
pattern: "export async function (GET|POST|PUT|DELETE|PATCH)"
glob: "**/api/**/*.{ts,tsx,js,jsx}"
- id: auth_middleware
pattern: "(requireAuth|withAuth|authorize|checkPermission)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: locations
人工审查清单:
- 所有经过身份验证的端点是否都会验证用户身份?
- 端点是否会检查资源所有权(用户只能访问自己的数据)?
- 管理员路由是否受到角色检查的保护?
- 是否在服务端检查授权(而不只是客户端)?
常见漏洞:
- 不安全的直接对象引用(IDOR):
/api/users/123会返回任意用户 - 权限提升:普通用户可以访问管理员功能
- 缺少授权:端点仅依赖客户端检查
安全的授权模式:
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { NextResponse } from 'next/server';
export async function GET(
req: Request,
{ params }: { params: { id: string } }
) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const post = await db.post.findUnique({
where: { id: params.id },
select: { id: true, title: true, content: true, authorId: true },
});
if (!post) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
// Check ownership
if (post.authorId !== session.user.id) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
return NextResponse.json(post);
}
检查基于角色的访问控制(RBAC)
搜索角色定义:
precision_grep:
queries:
- id: role_checks
pattern: "(role.*===|role.*includes|hasRole|checkRole|permissions)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: matches
RBAC 实现:
type Role = 'user' | 'admin' | 'moderator';
type Permission =
| 'posts:read'
| 'posts:write'
| 'posts:delete'
| 'users:manage'
| 'settings:admin';
const rolePermissions: Record<Role, Permission[]> = {
user: ['posts:read', 'posts:write'],
moderator: ['posts:read', 'posts:write', 'posts:delete'],
admin: ['posts:read', 'posts:write', 'posts:delete', 'users:manage', 'settings:admin'],
};
export function hasPermission(role: Role, permission: Permission): boolean {
return rolePermissions[role].includes(permission);
}
export function requirePermission(permission: Permission) {
return async (req: Request) => {
const session = await auth();
if (!session || !hasPermission(session.user.role, permission)) {
throw new Error('Insufficient permissions');
}
};
}
阶段 4:输入验证审计
**目标:**防止注入攻击和恶意输入。
检查 SQL 注入
搜索不安全的数据库查询:
precision_grep:
queries:
- id: raw_sql
pattern: "(\\$executeRaw|\\$queryRaw|db\\.query|connection\\.query).*\\$\\{|.*`.*\\$\\{"
glob: "**/*.{ts,tsx,js,jsx}"
- id: string_concatenation
pattern: "(SELECT|INSERT|UPDATE|DELETE).*\\+.*req\\.(body|query|params)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: verbose
常见漏洞:
- 在 SQL 查询中拼接字符串
- 在原始 SQL 中使用未经清理的用户输入
- 根据用户输入动态指定表名/列名
安全的数据库查询:
// UNSAFE - SQL injection vulnerable
export async function getUserByEmail(email: string) {
const query = `SELECT * FROM users WHERE email = '${email}'`;
return await db.$queryRawUnsafe(query);
}
// SAFE - Parameterized query
export async function getUserByEmail(email: string) {
return await db.user.findUnique({
where: { email },
});
}
// SAFE - Raw query with parameters
export async function searchUsers(query: string) {
return await db.$queryRaw`
SELECT id, name, email
FROM users
WHERE name ILIKE ${'%' + query + '%'}
LIMIT 20
`;
}
检查 XSS(跨站脚本攻击)
搜索不安全的渲染方式:
precision_grep:
queries:
- id: dangerous_html
pattern: "(dangerouslySetInnerHTML|innerHTML|outerHTML)"
glob: "**/*.{tsx,jsx}"
- id: unescaped_output
pattern: "(v-html|\\[innerHTML\\])"
glob: "**/*.{vue,html}"
output:
format: locations
常见漏洞:
- 使用
dangerouslySetInnerHTML渲染未经清理的用户输入 - 使用
innerHTML插入用户提供的内容 - 在模板引擎中禁用自动转义
安全的渲染方式:
import DOMPurify from 'isomorphic-dompurify';
// UNSAFE - XSS vulnerable
export function UnsafeComment({ content }: { content: string }) {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
// SAFE - Auto-escaped by React
export function SafeComment({ content }: { content: string }) {
return <div>{content}</div>;
}
// SAFE - Sanitized HTML if needed
export function SafeRichComment({ html }: { html: string }) {
const sanitized = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'],
ALLOWED_ATTR: ['href'],
});
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
检查命令注入
搜索 shell 命令执行:
precision_grep:
queries:
- id: shell_exec
pattern: "(exec|spawn|execSync|spawnSync|execFile).*req\\.(body|query|params)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: verbose
常见漏洞:
- 将用户输入直接传递给 shell 命令
- 文件操作中的文件路径未经清理
安全地执行命令:
import { spawn } from 'child_process';
import { z } from 'zod';
const allowedCommands = ['convert', 'resize', 'compress'] as const;
const commandSchema = z.enum(allowedCommands);
// UNSAFE - Command injection vulnerable
export async function processImage(filename: string) {
exec(`convert ${filename} output.png`);
}
// SAFE - Validated input and array arguments
export async function processImage(command: string, filename: string) {
const validCommand = commandSchema.parse(command);
const sanitizedFilename = filename.replace(/[^a-zA-Z0-9._-]/g, '');
return new Promise((resolve, reject) => {
const child = spawn('imagemagick', [validCommand, sanitizedFilename, 'output.png']);
child.on('exit', (code) => {
if (code === 0) resolve(undefined);
else reject(new Error('Processing failed'));
});
});
}
检查路径遍历
搜索文件操作:
precision_grep:
queries:
- id: file_operations
pattern: "(readFile|writeFile|unlink|stat|createReadStream).*req\\.(body|query|params)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: locations
常见漏洞:
- 未验证用户控制的文件路径
- 缺少路径规范化处理(允许
../序列)
安全地处理文件:
import path from 'path';
import fs from 'fs/promises';
const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
// UNSAFE - Path traversal vulnerable
export async function getFile(filename: string) {
return await fs.readFile(`./uploads/${filename}`);
}
// SAFE - Path validation with defense against encoded traversals
export async function getFile(filename: string) {
// Decode URL-encoded sequences (e.g., %2e%2e%2f -> ../)
const decoded = decodeURIComponent(filename);
const safePath = path.normalize(decoded).replace(/^(\.\.\/)+/, '');
const fullPath = path.resolve(path.join(UPLOADS_DIR, safePath));
// Ensure resolved path is within UPLOADS_DIR (prevents prefix collision)
if (!fullPath.startsWith(UPLOADS_DIR + path.sep) && fullPath !== UPLOADS_DIR) {
throw new Error('Invalid file path');
}
return await fs.readFile(fullPath);
}
阶段 5:数据保护审计
**目标:**确保敏感数据经过加密并得到妥善处理。
检查静态数据加密
搜索敏感数据存储:
precision_grep:
queries:
- id: sensitive_fields
pattern: "(ssn|credit.*card|bank.*account|passport|drivers.*license)"
glob: "**/*.{ts,tsx,js,jsx,prisma}"
- id: encryption_usage
pattern: "(encrypt|decrypt|cipher|crypto)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: minimal
加密最佳实践:
- 对称加密使用 AES-256-GCM
- 将加密密钥存储在安全的密钥管理服务中(AWS KMS、HashiCorp Vault)
- 切勿将加密密钥提交到版本控制系统
- 定期轮换加密密钥
安全的加密实现:
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY;
if (!ENCRYPTION_KEY) throw new Error('ENCRYPTION_KEY environment variable is required');
const KEY = Buffer.from(ENCRYPTION_KEY, 'hex'); // 32 bytes
export function encrypt(plaintext: string): string {
const iv = randomBytes(16);
const cipher = createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// Return: iv:authTag:ciphertext
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}
export function decrypt(ciphertext: string): string {
const [ivHex, authTagHex, encrypted] = ciphertext.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = createDecipheriv(ALGORITHM, KEY, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
检查 PII 处理
搜索个人身份信息:
precision_grep:
queries:
- id: pii_fields
pattern: "(email|phone|address|name|dob|birth.*date)"
glob: "**/*.prisma"
- id: logging_pii
pattern: "(console\\.log|logger\\.(info|debug|warn)).*\\.(email|phone|ssn)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: locations
PII 保护清单:
- 尽量减少 PII 的收集(仅收集必要信息)
- 在日志和错误消息中隐藏 PII
- 实施数据保留政策(自动删除旧数据)
- 提供用户数据导出功能(符合 GDPR/CCPA 要求)
- 提供用户数据删除功能(被遗忘权)
检查机密管理
搜索硬编码机密:
precision_grep:
queries:
- id: hardcoded_secrets
pattern: "(api.*key.*=.*[\"'][a-zA-Z0-9]{20,}|secret.*=.*[\"'][a-zA-Z0-9]{20,}|password.*=.*[\"'][^\"']{8,})"
glob: "**/*.{ts,tsx,js,jsx}"
- id: committed_env
pattern: ".*"
glob: ".env"
output:
format: verbose
常见漏洞:
- 在源代码中硬编码 API 密钥
- 将
.env文件提交到版本控制系统 - 在客户端代码中暴露机密
- 未更改默认凭据
安全的机密管理:
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
NEXTAUTH_SECRET: z.string().min(32),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
OPENAI_API_KEY: z.string().startsWith('sk-'),
});
export const env = envSchema.parse({
DATABASE_URL: process.env.DATABASE_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
});
// Ensure .env is in .gitignore
// Use .env.example (without values) to document required variables
// Use secrets management in production (Vercel Env Vars, AWS Secrets Manager)
阶段 6:依赖项审计
**目标:**识别并修复存在漏洞的依赖项。
运行自动化漏洞扫描器
使用 npm audit:
precision_exec:
commands:
- cmd: "npm audit --json"
timeout_ms: 30000
verbosity: standard
检查过时的软件包:
precision_exec:
commands:
- cmd: "npm outdated --json"
timeout_ms: 10000
verbosity: minimal
确定修复优先级:
- **严重/高危:**部署前立即修复
- **中危:**在 30 天内修复
- **低危:**在定期维护期间修复
检查供应链攻击
验证锁文件完整性:
precision_exec:
commands:
- cmd: "npm audit signatures"
timeout_ms: 30000
verbosity: standard
清单:
- 提交锁文件(
package-lock.json、yarn.lock、pnpm-lock.yaml) - 在 PR 中审查依赖项变更
- 在 CI/CD 中使用
npm ci(而非npm install) - 在关键项目中固定依赖项版本
- 使用 Socket.dev 等工具分析依赖项
阶段 7:API 安全审计
**目标:**保护 API 端点免受常见攻击。
检查速率限制
搜索速率限制实现:
precision_grep:
queries:
- id: rate_limit_usage
pattern: "(rateLimit|rate.*limiter|Ratelimit|upstash.*ratelimit)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: minimal
实施速率限制:
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
import { NextResponse } from 'next/server';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
analytics: true,
});
export async function POST(req: Request) {
const ip = req.headers.get('x-forwarded-for') ?? 'unknown';
const { success, limit, reset, remaining } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': new Date(reset).toISOString(),
},
}
);
}
// Process request
return NextResponse.json({ success: true });
}
检查 CORS 配置
搜索 CORS 设置:
precision_grep:
queries:
- id: cors_config
pattern: "(Access-Control-Allow-Origin|cors\\(|corsOptions)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: matches
常见漏洞:
- 携带凭据时使用通配符 CORS(
Access-Control-Allow-Origin: *) - 允许的来源白名单过于宽泛
- 未处理预检请求
安全的 CORS 配置:
import { NextResponse } from 'next/server';
const ALLOWED_ORIGINS = [
'https://example.com',
'https://app.example.com',
];
export async function GET(req: Request) {
const origin = req.headers.get('origin');
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (origin && ALLOWED_ORIGINS.includes(origin)) {
headers['Access-Control-Allow-Origin'] = origin;
headers['Access-Control-Allow-Credentials'] = 'true';
}
return NextResponse.json({ data: 'response' }, { headers });
}
Webhook 签名验证
**最佳实践:**使用 crypto.timingSafeEqual() 比较 Webhook 签名,防止时序攻击:
import crypto from 'crypto';
export function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const expectedSignature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
const signatureBuffer = Buffer.from(signature);
const expectedBuffer = Buffer.from(expectedSignature);
// Prevent timing attacks - constant-time comparison
if (signatureBuffer.length !== expectedBuffer.length) return false;
return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);
}
CORS 预检处理
export async function OPTIONS(req: Request) {
const origin = req.headers.get('origin');
const headers: Record<string, string> = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400', // 24 hours
};
if (origin && ALLOWED_ORIGINS.includes(origin)) {
headers['Access-Control-Allow-Origin'] = origin;
headers['Access-Control-Allow-Credentials'] = 'true';
}
return new NextResponse(null, { status: 204, headers });
}
检查 CSRF 防护
搜索 CSRF 实现:
precision_grep:
queries:
- id: csrf_tokens
pattern: "(csrf|csrfToken|xsrf)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: minimal
CSRF 防护策略:
- 使用 SameSite Cookie(
sameSite: 'strict'或'lax') - 对更改状态的操作实施 CSRF 令牌
- 要求 API 请求携带自定义标头(例如
X-Requested-With) - 验证
Origin和Referer标头
检查内容安全策略(CSP)
搜索 CSP 标头:
precision_grep:
queries:
- id: csp_headers
pattern: "Content-Security-Policy"
glob: "**/*.{ts,tsx,js,jsx,json}"
output:
format: locations
实施 CSP:
// next.config.js
const ContentSecurityPolicy = `
default-src 'self';
// WARNING: 'unsafe-eval' and 'unsafe-inline' significantly weaken CSP. Use nonces or hashes instead.
script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.vercel-insights.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
`;
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/\n/g, ''),
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
],
},
];
},
};
阶段 8:基础设施安全审计
**目标:**强化部署基础设施。
检查 Docker 安全性
搜索 Dockerfile:
precision_read:
files:
- path: "Dockerfile"
extract: content
verbosity: minimal
Docker 安全检查清单:
- 使用最小化基础镜像(alpine、distroless)
- 以非 root 用户运行
- 使用多阶段构建来缩小攻击面
- 不要在镜像层中存储密钥(使用构建参数)
- 固定基础镜像版本(避免使用
latest标签) - 扫描镜像中的漏洞(
docker scan、Trivy)
安全的 Dockerfile:
FROM node:20-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# Create non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD ["node", "server.js"]
检查环境变量安全性
搜索环境文件的使用情况:
precision_glob:
patterns:
- ".env*"
- "*.env"
verbosity: count_only
验证 .gitignore:
precision_read:
files:
- path: ".gitignore"
extract: content
verbosity: minimal
环境安全检查清单:
.env文件已添加到.gitignore- 使用
.env.example(不含值)记录变量 - 在生产环境中使用密钥管理(而非
.env文件) - 分隔不同环境(开发、预发布、生产)
- 定期轮换密钥
检查 TLS 配置
验证是否强制使用 HTTPS:
precision_grep:
queries:
- id: https_redirect
pattern: "(https|ssl|tls|hsts|Strict-Transport-Security)"
glob: "**/*.{ts,tsx,js,jsx,json}"
output:
format: minimal
TLS 最佳实践:
- 强制使用 HTTPS(将 HTTP 重定向到 HTTPS)
- 启用 HSTS(Strict-Transport-Security)
- 使用 TLS 1.2+(禁用 TLS 1.0、1.1)
- 使用强加密套件
- 为移动应用实现证书固定
审计报告
按以下格式整理审计结果:
执行摘要
## Security Audit Report
**Application:** [Name]
**Audit Date:** [Date]
**Auditor:** [Name/Team]
**Scope:** [Components audited]
### Summary
- Total findings: X
- Critical: Y
- High: Z
- Medium: A
- Low: B
### Risk Assessment
[Overall risk level: Critical/High/Medium/Low]
详细发现
对于每个漏洞:
### [SEVERITY] Finding #X: [Title]
**Category:** [Authentication/Authorization/Input Validation/etc.]
**Severity:** [Critical/High/Medium/Low]
**CWE:** [CWE-XXX if applicable]
**Description:**
[Clear description of the vulnerability]
**Location:**
- File: `path/to/file.ts`
- Lines: 42-58
**Impact:**
[What can an attacker do? What data is at risk?]
**Proof of Concept:**
```typescript
// Example exploit code or reproduction steps
修复措施:
// Secure code example
参考资料:
- [OWASP 链接]
- [CWE 链接]
### Severity Classification
| Severity | Criteria |
|----------|----------|
| **Critical** | Remote code execution, authentication bypass, sensitive data exposure |
| **High** | Privilege escalation, SQL injection, XSS in critical flows |
| **Medium** | Information disclosure, CSRF, weak authentication |
| **Low** | Minor information leaks, missing security headers |
## Precision Tool Workflows
### Full Security Scan Workflow
Run a comprehensive security scan using precision_grep:
```yaml
precision_grep:
queries:
# Authentication issues
- id: weak_auth
pattern: "(password.*plain|password.*clear|md5|sha1)\\("
glob: "**/*.{ts,tsx,js,jsx}"
# SQL injection
- id: sql_injection
pattern: "(\\$queryRaw|\\$executeRaw).*\\$\\{|query.*\\+.*params"
glob: "**/*.{ts,tsx,js,jsx}"
# XSS
- id: xss
pattern: "(dangerouslySetInnerHTML|innerHTML|v-html)"
glob: "**/*.{tsx,jsx,vue}"
# Command injection
- id: command_injection
pattern: "(exec|spawn).*\\(.*req\\.(body|query)"
glob: "**/*.{ts,tsx,js,jsx}"
# Path traversal
- id: path_traversal
pattern: "(readFile|writeFile).*req\\.(body|query|params)"
glob: "**/*.{ts,tsx,js,jsx}"
# Hardcoded secrets
- id: hardcoded_secrets
pattern: "(api.*key.*=.*[\"'][a-zA-Z0-9]{20,}|sk_live)"
glob: "**/*.{ts,tsx,js,jsx}"
# Insecure cookies
- id: insecure_cookies
pattern: "(httpOnly.*false|secure.*false|sameSite.*none)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: locations
批量安全审计
使用 discover + batch 高效执行审计:
discover:
queries:
- id: auth_files
type: glob
patterns: ["**/auth/**/*.{ts,tsx,js,jsx}", "**/api/auth/**/*.{ts,tsx,js,jsx}"]
- id: api_routes
type: glob
patterns: ["**/api/**/*.{ts,tsx,js,jsx}"]
- id: db_files
type: grep
pattern: "(prisma|db|database)"
glob: "**/*.{ts,tsx,js,jsx}"
verbosity: files_only
然后批量读取并分析:
precision_read:
files: [/* Use discovered files */]
extract: symbols
symbol_filter: ["function", "class"]
verbosity: standard
自动化安全测试
将安全检查集成到 CI/CD 中:
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --audit-level=high
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'CRITICAL,HIGH'
- name: Run OWASP ZAP scan
uses: zaproxy/[email protected]
with:
target: 'http://localhost:3000'
常见安全错误
1. 信任客户端验证
问题: 依赖客户端检查来保障安全。
解决方案: 始终在服务端进行验证。
// UNSAFE - Client-side only
export function ClientForm() {
const [email, setEmail] = useState('');
const isValid = email.includes('@');
return (
<form onSubmit={() => fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify({ email }),
})}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button disabled={!isValid}>Subscribe</button>
</form>
);
}
// SAFE - Server-side validation
export async function POST(req: Request) {
const body = await req.json();
const schema = z.object({
email: z.string().email(),
});
const result = schema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ error: result.error.flatten() },
{ status: 400 }
);
}
// Process valid email
await subscribeUser(result.data.email);
return NextResponse.json({ success: true });
}
2. 在 API 响应中暴露敏感数据
问题: 返回了超出所需范围的数据。
解决方案: 使用显式的 select 来限制字段。
// UNSAFE - Returns password hash
export async function GET(req: Request) {
const user = await db.user.findUnique({ where: { id: userId } });
return NextResponse.json(user);
}
// SAFE - Excludes sensitive fields
export async function GET(req: Request) {
const user = await db.user.findUnique({
where: { id: userId },
select: {
id: true,
name: true,
email: true,
avatar: true,
// passwordHash excluded
},
});
return NextResponse.json(user);
}
3. 记录敏感数据
问题: 将个人身份信息(PII)或密钥写入日志。
解决方案: 记录日志前对敏感数据进行脱敏。
// UNSAFE - Logs password
export async function login(email: string, password: string) {
console.log('Login attempt:', { email, password });
// ...
}
// SAFE - Redacts password
export async function login(email: string, password: string) {
console.log('Login attempt:', { email, password: '[REDACTED]' });
// ...
}
审计后续行动
完成审计后:
- 按严重性和可利用性确定发现的优先级
- 为每项发现创建工单(链接到审计报告)
- 立即修复严重/高危问题
- 运行验证脚本,确认修复措施有效
- 在修复后安排后续审计
- 根据经验教训更新安全文档
- 针对发现的常见漏洞对团队进行培训
验证
完成审计修复后运行验证脚本:
./scripts/validate-security-audit.sh /path/to/project
该脚本会检查:
- 常见漏洞模式
- 安全的身份验证实现
- 输入验证覆盖情况
- 密钥管理
- 安全标头配置
- 依赖项漏洞