security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security Skill

安全技能指南

Dependency Scanning

依赖项扫描

pnpm audit

pnpm 审计

bash
undefined
bash
undefined

Run audit

执行审计

pnpm audit
pnpm audit

Only high/critical

仅检查高/严重等级漏洞

pnpm audit --audit-level=high
pnpm audit --audit-level=high

Auto-fix

自动修复

pnpm audit --fix
pnpm audit --fix

JSON report

生成JSON报告

pnpm audit --json > audit.json
undefined
pnpm audit --json > audit.json
undefined

Fix Vulnerabilities

修复漏洞

Direct dependencies: Update version in
pnpm-workspace.yaml
catalog
Transitive dependencies:
bash
undefined
直接依赖项:
pnpm-workspace.yaml
的依赖清单中更新版本
间接依赖项:
bash
undefined

Find dependency chain

查找依赖链

pnpm why vulnerable-package
pnpm why vulnerable-package

Use overrides as last resort

仅在万不得已时使用覆盖配置

package.json

package.json

{ "pnpm": { "overrides": { "vulnerable-package": "^3.1.0" } } }
undefined
{ "pnpm": { "overrides": { "vulnerable-package": "^3.1.0" } } }
undefined

Snyk

Snyk

bash
snyk auth          # Authenticate
snyk test          # Test for vulnerabilities
snyk monitor       # Monitor for new vulnerabilities
snyk fix           # Auto-fix
bash
snyk auth          # 身份验证
snyk test          # 检测漏洞
snyk monitor       # 持续监控新漏洞
snyk fix           # 自动修复

OWASP Top 10 Checks

OWASP Top 10 检查项

1. Broken Access Control

1. 访问控制失效

typescript
// ❌ No authorization
export async function deletePost(postId: string) {
  await db.delete(posts).where(eq(posts.id, postId));
}

// ✅ With authorization
export async function deletePost(postId: string, userId: string) {
  const post = await db.query.posts.findFirst({ where: eq(posts.id, postId) });
  if (post.authorId !== userId) throw new Error("Unauthorized");
  await db.delete(posts).where(eq(posts.id, postId));
}
typescript
// ❌ 未添加授权校验
export async function deletePost(postId: string) {
  await db.delete(posts).where(eq(posts.id, postId));
}

// ✅ 添加授权校验
export async function deletePost(postId: string, userId: string) {
  const post = await db.query.posts.findFirst({ where: eq(posts.id, postId) });
  if (post.authorId !== userId) throw new Error("Unauthorized");
  await db.delete(posts).where(eq(posts.id, postId));
}

2. Injection Prevention

2. 注入攻击防护

typescript
// ❌ SQL Injection
const query = `SELECT * FROM users WHERE id = ${userId}`;

// ✅ Parameterized query (Drizzle ORM)
const user = await db.query.users.findFirst({ where: eq(users.id, userId) });
typescript
// ❌ 存在SQL注入风险
const query = `SELECT * FROM users WHERE id = ${userId}`;

// ✅ 使用参数化查询(Drizzle ORM)
const user = await db.query.users.findFirst({ where: eq(users.id, userId) });

3. XSS Prevention

3. XSS攻击防护

React escapes content by default. When rendering HTML:
  • Sanitize with
    sanitize-html
    library before rendering
  • Never render untrusted content directly
React默认会转义内容。渲染HTML时:
  • 渲染前使用
    sanitize-html
    库进行内容清理
  • 绝不要直接渲染不可信内容

4. Rate Limiting

4. 限流控制

typescript
import { Ratelimit } from "@upstash/ratelimit";
import { redis } from "@sgcarstrends/utils";

const ratelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(5, "15 m"),
});

export async function login(email: string, password: string, ip: string) {
  const { success } = await ratelimit.limit(ip);
  if (!success) throw new Error("Too many login attempts");
  return verifyCredentials(email, password);
}
typescript
import { Ratelimit } from "@upstash/ratelimit";
import { redis } from "@sgcarstrends/utils";

const ratelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(5, "15 m"),
});

export async function login(email: string, password: string, ip: string) {
  const { success } = await ratelimit.limit(ip);
  if (!success) throw new Error("Too many login attempts");
  return verifyCredentials(email, password);
}

5. Password Security

5. 密码安全

typescript
import bcrypt from "bcrypt";

// ✅ Hash passwords
const hashedPassword = await bcrypt.hash(password, 10);

// ✅ Strong password validation
const passwordSchema = z.string()
  .min(12)
  .regex(/[A-Z]/, "Must contain uppercase")
  .regex(/[a-z]/, "Must contain lowercase")
  .regex(/[0-9]/, "Must contain number")
  .regex(/[^A-Za-z0-9]/, "Must contain special character");
typescript
import bcrypt from "bcrypt";

// ✅ 对密码进行哈希处理
const hashedPassword = await bcrypt.hash(password, 10);

// ✅ 强密码校验
const passwordSchema = z.string()
  .min(12)
  .regex(/[A-Z]/, "Must contain uppercase")
  .regex(/[a-z]/, "Must contain lowercase")
  .regex(/[0-9]/, "Must contain number")
  .regex(/[^A-Za-z0-9]/, "Must contain special character");

6. SSRF Prevention

6. SSRF攻击防护

typescript
// ❌ SSRF vulnerability
export async function fetchUrl(url: string) {
  return await fetch(url);
}

// ✅ Whitelist approach
const ALLOWED_DOMAINS = ["api.example.com", "data.gov.sg"];

export async function fetchUrl(url: string) {
  const parsedUrl = new URL(url);
  if (!ALLOWED_DOMAINS.includes(parsedUrl.hostname)) {
    throw new Error("Domain not allowed");
  }
  return await fetch(url);
}
typescript
// ❌ 存在SSRF漏洞风险
export async function fetchUrl(url: string) {
  return await fetch(url);
}

// ✅ 采用白名单机制
const ALLOWED_DOMAINS = ["api.example.com", "data.gov.sg"];

export async function fetchUrl(url: string) {
  const parsedUrl = new URL(url);
  if (!ALLOWED_DOMAINS.includes(parsedUrl.hostname)) {
    throw new Error("Domain not allowed");
  }
  return await fetch(url);
}

Input Validation

输入校验

typescript
import { z } from "zod";

const userInputSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
});

export async function createUser(data: unknown) {
  const validated = userInputSchema.parse(data);
  // Now safe to use
}
typescript
import { z } from "zod";

const userInputSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
});

export async function createUser(data: unknown) {
  const validated = userInputSchema.parse(data);
  // 此时数据可安全使用
}

CORS Configuration

CORS配置

typescript
// ❌ Too permissive
app.use(cors({ origin: "*" }));

// ✅ Whitelist specific origins
app.use(cors({
  origin: [
    "https://sgcarstrends.com",
    "https://staging.sgcarstrends.com",
    process.env.NODE_ENV === "development" ? "http://localhost:3001" : "",
  ].filter(Boolean),
  credentials: true,
}));
typescript
// ❌ 配置过于宽松
app.use(cors({ origin: "*" }));

// ✅ 配置特定域名白名单
app.use(cors({
  origin: [
    "https://sgcarstrends.com",
    "https://staging.sgcarstrends.com",
    process.env.NODE_ENV === "development" ? "http://localhost:3001" : "",
  ].filter(Boolean),
  credentials: true,
}));

Security Headers

安全响应头

typescript
// next.config.js
const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
  { key: "X-Frame-Options", value: "SAMEORIGIN" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "X-XSS-Protection", value: "1; mode=block" },
  { key: "Referrer-Policy", value: "origin-when-cross-origin" },
];

module.exports = {
  async headers() {
    return [{ source: "/:path*", headers: securityHeaders }];
  },
};
typescript
// next.config.js
const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
  { key: "X-Frame-Options", value: "SAMEORIGIN" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "X-XSS-Protection", value: "1; mode=block" },
  { key: "Referrer-Policy", value: "origin-when-cross-origin" },
];

module.exports = {
  async headers() {
    return [{ source: "/:path*", headers: securityHeaders }];
  },
};

Environment Variables

环境变量

typescript
// ❌ Hardcoded secret
const apiKey = "sk_live_EXAMPLE_NOT_REAL";

// ✅ From environment with validation
import { z } from "zod";

const envSchema = z.object({
  API_KEY: z.string().min(1),
  DATABASE_URL: z.string().url(),
});

const env = envSchema.parse(process.env);
typescript
// ❌ 硬编码密钥
const apiKey = "sk_live_EXAMPLE_NOT_REAL";

// ✅ 从环境变量读取并校验
import { z } from "zod";

const envSchema = z.object({
  API_KEY: z.string().min(1),
  DATABASE_URL: z.string().url(),
});

const env = envSchema.parse(process.env);

CI Integration

CI集成

yaml
undefined
yaml
undefined

.github/workflows/security.yml

.github/workflows/security.yml

name: Security Audit
on: push: branches: [main] schedule: - cron: '0 0 * * 1' # Weekly
jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 - run: pnpm install - run: pnpm audit --audit-level=high
undefined
name: Security Audit
on: push: branches: [main] schedule: - cron: '0 0 * * 1' # 每周执行
jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 - run: pnpm install - run: pnpm audit --audit-level=high
undefined

Security Checklist

安全检查清单

  • All user input validated (Zod schemas)
  • SQL injection prevented (using ORM)
  • XSS prevented (React escaping, sanitization)
  • Authentication implemented correctly
  • Authorization checks in place
  • Passwords hashed (bcrypt/argon2)
  • Rate limiting configured
  • Security headers set
  • CORS configured properly
  • HTTPS enforced
  • Dependencies audited (pnpm audit)
  • Secrets in environment variables
  • Error messages don't leak info
  • 所有用户输入已通过Zod Schema校验
  • 已通过ORM防止SQL注入
  • 已通过React转义及内容清理防止XSS攻击
  • 已正确实现身份认证机制
  • 已添加访问授权校验
  • 密码已通过bcrypt/argon2进行哈希处理
  • 已配置限流控制
  • 已设置安全响应头
  • CORS已正确配置
  • 已强制使用HTTPS
  • 已通过pnpm audit审计依赖项
  • 密钥已存储在环境变量中
  • 错误信息未泄露敏感数据

References

参考资料