mcpField
S
security-audit

security-audit

github.com/mgd34msu安全659 次安装10 周前更新
代码仓库

关于 security-audit

security-audit 是面向代码库审查者的安全审计技能,按阶段检查常见漏洞与配置风险,涵盖认证、输入处理、数据保护和依赖项。

主要功能

  1. 01分阶段梳理代码库攻击面
  2. 02检查认证与访问控制
  3. 03排查注入和不安全输入处理
  4. 04检查敏感数据与密钥管理
  5. 05审查依赖漏洞和供应链风险

适用场景

  1. 01上线前进行安全审查
  2. 02响应安全事件或漏洞报告
  3. 03主要功能变更后检查安全控制
  4. 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]' });
  // ...
}

审计后续行动

完成审计后:

  1. 按严重性和可利用性确定发现的优先级
  2. 为每项发现创建工单(链接到审计报告)
  3. 立即修复严重/高危问题
  4. 运行验证脚本,确认修复措施有效
  5. 在修复后安排后续审计
  6. 根据经验教训更新安全文档
  7. 针对发现的常见漏洞对团队进行培训

验证

完成审计修复后运行验证脚本:

./scripts/validate-security-audit.sh /path/to/project

该脚本会检查:

  • 常见漏洞模式
  • 安全的身份验证实现
  • 输入验证覆盖情况
  • 密钥管理
  • 安全标头配置
  • 依赖项漏洞

延伸阅读

评论

支持 Markdown