security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<objective> Comprehensive security skill covering authentication patterns, secrets management, input validation, and common vulnerability prevention. Focuses on practical patterns for web applications with Supabase, Next.js, and Python backends.
Security is not optional - it's a fundamental requirement. This skill helps you build secure applications from the start, not bolt on security as an afterthought. </objective>
<quick_start> Security essentials for any new project:
  1. Secrets: Never commit to git, validate at startup
    typescript
    // .env (gitignored) + envSchema.parse(process.env)
  2. Auth: Short-lived JWTs + httpOnly cookies
    typescript
    jwt.sign(payload, secret, { expiresIn: '15m' })
  3. Input: Validate everything with schemas
    typescript
    const data = z.object({ email: z.string().email() }).parse(input)
  4. SQL: Always use parameterized queries (ORMs handle this)
  5. RLS: Enable on all Supabase tables with user-scoped policies </quick_start>
<success_criteria> Security implementation is successful when:
  • All secrets in environment variables, validated at startup
  • No secrets in version control (verified with gitleaks)
  • JWT tokens short-lived (≤15 min) with refresh token rotation
  • All user input validated with Zod or similar schema validation
  • RLS enabled on all database tables with appropriate policies
  • CSP headers configured (no unsafe-inline where possible)
  • Security checklist completed before deployment </success_criteria>
<security_mindset>
<objective> 全面的安全技能,涵盖身份认证模式、密钥管理、输入验证和常见漏洞防护。重点介绍适用于基于Supabase、Next.js和Python后端的Web应用的实用模式。
安全并非可选选项——而是一项基本要求。本技能帮助你从项目初期就构建安全的应用,而非事后再补加安全措施。 </objective>
<quick_start> 任何新项目的安全要点:
  1. 密钥:绝对不要提交到git,在启动时进行验证
    typescript
    // .env (gitignored) + envSchema.parse(process.env)
  2. 身份认证:短期JWT + httpOnly Cookie
    typescript
    jwt.sign(payload, secret, { expiresIn: '15m' })
  3. 输入:使用校验规则验证所有输入
    typescript
    const data = z.object({ email: z.string().email() }).parse(input)
  4. SQL:始终使用参数化查询(ORM会自动处理)
  5. RLS:在所有Supabase表上启用用户范围的策略 </quick_start>
<success_criteria> 安全实现成功的标志:
  • 所有密钥都存储在环境变量中,并在启动时验证
  • 版本控制中无密钥(使用gitleaks验证)
  • JWT令牌有效期短(≤15分钟)且支持刷新令牌轮换
  • 所有用户输入都使用Zod或类似的校验规则验证
  • 所有数据库表都启用RLS并配置适当的策略
  • 配置CSP头(尽可能避免unsafe-inline)
  • 部署前完成安全检查清单 </success_criteria>
<security_mindset>

The Security Mindset

安全思维模式

Core Principles

核心原则

  1. Defense in depth - Multiple layers of security, not one wall
  2. Least privilege - Grant minimum access required
  3. Never trust input - Validate everything from users and external systems
  4. Fail secure - Errors should deny access, not grant it
  5. Keep secrets secret - API keys never in code or logs
  1. 纵深防御 - 多层安全防护,而非单一壁垒
  2. 最小权限 - 仅授予完成任务所需的最小权限
  3. 绝不信任输入 - 验证所有来自用户和外部系统的输入
  4. 安全失败 - 错误应拒绝访问,而非授予访问权限
  5. 密钥保密 - API密钥绝不出现在代码或日志中

Security Questions to Ask

需关注的安全问题

Before shipping any feature:
  • What data does this expose?
  • Who can access this endpoint/page?
  • What happens if the user sends malicious input?
  • Are secrets properly protected?
  • Is sensitive data logged? </security_mindset>
<authentication>
在发布任何功能前:
  • 此功能会暴露哪些数据?
  • 谁可以访问此端点/页面?
  • 如果用户发送恶意输入会发生什么?
  • 密钥是否得到妥善保护?
  • 敏感数据是否被记录? </security_mindset>
<authentication>

Authentication Patterns

身份认证模式

JWT vs Session

JWT vs Session

AspectJWTSession
StorageClient (localStorage/cookie)Server (DB/Redis)
ScalabilityStateless, easy to scaleRequires shared session store
RevocationHard (need blacklist)Easy (delete from store)
SizeLarger (contains claims)Small (just session ID)
Best forAPIs, microservicesTraditional web apps
维度JWTSession
存储位置客户端(localStorage/Cookie)服务器(数据库/Redis)
可扩展性无状态,易于扩展需要共享会话存储
撤销难度困难(需要黑名单)简单(从存储中删除)
大小较大(包含声明信息)较小(仅会话ID)
最佳适用场景API、微服务传统Web应用

JWT Best Practices

JWT最佳实践

typescript
// DO: Short-lived access tokens + refresh tokens
const accessToken = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: '15m' } // Short-lived!
);

const refreshToken = jwt.sign(
  { userId: user.id },
  process.env.JWT_REFRESH_SECRET,
  { expiresIn: '7d' }
);

// DON'T: Long-lived tokens with sensitive data
// Bad example - never do this:
// { expiresIn: '365d' } // Too long!
// Including PII like SSN in token payload
typescript
// DO: Short-lived access tokens + refresh tokens
const accessToken = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: '15m' } // Short-lived!
);

const refreshToken = jwt.sign(
  { userId: user.id },
  process.env.JWT_REFRESH_SECRET,
  { expiresIn: '7d' }
);

// DON'T: Long-lived tokens with sensitive data
// Bad example - never do this:
// { expiresIn: '365d' } // Too long!
// Including PII like SSN in token payload

Token Storage

令牌存储

MethodXSS SafeCSRF SafeRecommendation
localStorageNoYesAvoid for auth
httpOnly cookieYesNo (needs CSRF token)Recommended
Memory (variable)YesYesBest for SPAs
方式防XSS防CSRF推荐等级
localStorage避免用于身份认证
httpOnly cookie否(需CSRF令牌)推荐
内存(变量)单页应用最佳选择

Refresh Token Flow

刷新令牌流程

┌─────────┐                    ┌─────────┐                    ┌─────────┐
│ Client  │                    │  Server │                    │   DB    │
└────┬────┘                    └────┬────┘                    └────┬────┘
     │                              │                              │
     │  Login (email, password)     │                              │
     │─────────────────────────────>│                              │
     │                              │  Verify credentials          │
     │                              │─────────────────────────────>│
     │                              │<─────────────────────────────│
     │  Access token (15m)          │                              │
     │  Refresh token (7d)          │  Store refresh token hash    │
     │<─────────────────────────────│─────────────────────────────>│
     │                              │                              │
     │  API call + access token     │                              │
     │─────────────────────────────>│                              │
     │  Response                    │                              │
     │<─────────────────────────────│                              │
     │                              │                              │
     │  [Access token expired]      │                              │
     │  Refresh token               │                              │
     │─────────────────────────────>│  Verify refresh token        │
     │                              │─────────────────────────────>│
     │  New access token            │                              │
     │<─────────────────────────────│                              │
┌─────────┐                    ┌─────────┐                    ┌─────────┐
│ Client  │                    │  Server │                    │   DB    │
└────┬────┘                    └────┬────┘                    └────┬────┘
     │                              │                              │
     │  Login (email, password)     │                              │
     │─────────────────────────────>│                              │
     │                              │  Verify credentials          │
     │                              │─────────────────────────────>│
     │                              │<─────────────────────────────│
     │  Access token (15m)          │                              │
     │  Refresh token (7d)          │  Store refresh token hash    │
     │<─────────────────────────────│─────────────────────────────>│
     │                              │                              │
     │  API call + access token     │                              │
     │─────────────────────────────>│                              │
     │  Response                    │                              │
     │<─────────────────────────────│                              │
     │                              │                              │
     │  [Access token expired]      │                              │
     │  Refresh token               │                              │
     │─────────────────────────────>│  Verify refresh token        │
     │                              │─────────────────────────────>│
     │  New access token            │                              │
     │<─────────────────────────────│                              │

Password Handling

密码处理

typescript
import bcrypt from 'bcrypt';

// DO: Hash with sufficient rounds
const SALT_ROUNDS = 12; // ~300ms on modern hardware
const hash = await bcrypt.hash(password, SALT_ROUNDS);

// DO: Constant-time comparison
const isValid = await bcrypt.compare(inputPassword, storedHash);

// DON'T: Use weak hashing algorithms like MD5 or SHA1 for passwords
typescript
import bcrypt from 'bcrypt';

// DO: Hash with sufficient rounds
const SALT_ROUNDS = 12; // ~300ms on modern hardware
const hash = await bcrypt.hash(password, SALT_ROUNDS);

// DO: Constant-time comparison
const isValid = await bcrypt.compare(inputPassword, storedHash);

// DON'T: Use weak hashing algorithms like MD5 or SHA1 for passwords

Password Requirements

密码要求

typescript
const passwordSchema = z.string()
  .min(8, 'Minimum 8 characters')
  .max(128, 'Maximum 128 characters')
  .regex(/[a-z]/, 'Must contain lowercase')
  .regex(/[A-Z]/, 'Must contain uppercase')
  .regex(/[0-9]/, 'Must contain number')
  .regex(/[^a-zA-Z0-9]/, 'Must contain special character');

// Check against common passwords (haveibeenpwned API)
async function isPasswordPwned(password: string): Promise<boolean> {
  const sha1 = crypto.createHash('sha1').update(password).digest('hex').toUpperCase();
  const prefix = sha1.slice(0, 5);
  const suffix = sha1.slice(5);

  const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
  const text = await response.text();

  return text.includes(suffix);
}
</authentication>
<secrets_management>
typescript
const passwordSchema = z.string()
  .min(8, 'Minimum 8 characters')
  .max(128, 'Maximum 128 characters')
  .regex(/[a-z]/, 'Must contain lowercase')
  .regex(/[A-Z]/, 'Must contain uppercase')
  .regex(/[0-9]/, 'Must contain number')
  .regex(/[^a-zA-Z0-9]/, 'Must contain special character');

// Check against common passwords (haveibeenpwned API)
async function isPasswordPwned(password: string): Promise<boolean> {
  const sha1 = crypto.createHash('sha1').update(password).digest('hex').toUpperCase();
  const prefix = sha1.slice(0, 5);
  const suffix = sha1.slice(5);

  const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
  const text = await response.text();

  return text.includes(suffix);
}
</authentication>
<secrets_management>

Secrets Management

密钥管理

The Golden Rule

黄金法则

NEVER commit secrets to version control
bash
undefined
绝对不要将密钥提交到版本控制
bash
undefined

.gitignore - Always include

.gitignore - Always include

.env .env.local .env.*.local *.pem *.key credentials.json secrets.yaml
undefined
.env .env.local .env.*.local *.pem *.key credentials.json secrets.yaml
undefined

Environment Variables

环境变量

bash
undefined
bash
undefined

.env (local development)

.env (local development)

DATABASE_URL=postgresql://localhost:5432/myapp JWT_SECRET=dev-secret-change-in-production STRIPE_SECRET_KEY=sk_test_...
DATABASE_URL=postgresql://localhost:5432/myapp JWT_SECRET=dev-secret-change-in-production STRIPE_SECRET_KEY=sk_test_...

.env.example (commit this!)

.env.example (commit this!)

DATABASE_URL=postgresql://localhost:5432/myapp JWT_SECRET=generate-secure-secret STRIPE_SECRET_KEY=sk_test_your_key_here
undefined
DATABASE_URL=postgresql://localhost:5432/myapp JWT_SECRET=generate-secure-secret STRIPE_SECRET_KEY=sk_test_your_key_here
undefined

Loading Secrets

加载密钥

typescript
// DO: Validate at startup
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
});

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

// DON'T: Use undefined secrets
// Always validate that required env vars exist
typescript
// DO: Validate at startup
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
});

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

// DON'T: Use undefined secrets
// Always validate that required env vars exist

Secret Rotation

密钥轮换

typescript
// Support multiple secrets during rotation
const JWT_SECRETS = [
  process.env.JWT_SECRET_NEW,    // Current
  process.env.JWT_SECRET_OLD,    // Previous (for validation)
].filter(Boolean);

function verifyToken(token: string): JWTPayload {
  for (const secret of JWT_SECRETS) {
    try {
      return jwt.verify(token, secret);
    } catch {
      continue;
    }
  }
  throw new Error('Invalid token');
}
typescript
// Support multiple secrets during rotation
const JWT_SECRETS = [
  process.env.JWT_SECRET_NEW,    // Current
  process.env.JWT_SECRET_OLD,    // Previous (for validation)
].filter(Boolean);

function verifyToken(token: string): JWTPayload {
  for (const secret of JWT_SECRETS) {
    try {
      return jwt.verify(token, secret);
    } catch {
      continue;
    }
  }
  throw new Error('Invalid token');
}

Never Log Secrets

绝不记录密钥

typescript
// DO: Mask sensitive data in logs
logger.info('User login', {
  userId: user.id,
  email: maskEmail(user.email), // t***@example.com
});

// DON'T: Log tokens or credentials
// Never log: authorization headers, request bodies with passwords, API keys
</secrets_management>
<input_validation>
typescript
// DO: Mask sensitive data in logs
logger.info('User login', {
  userId: user.id,
  email: maskEmail(user.email), // t***@example.com
});

// DON'T: Log tokens or credentials
// Never log: authorization headers, request bodies with passwords, API keys
</secrets_management>
<input_validation>

Input Validation

输入验证

Validate at System Boundaries

在系统边界处验证

┌─────────────────────────────────────────────────────────┐
│                    Your Application                      │
│                                                          │
│   ┌──────────┐     VALIDATE      ┌──────────────────┐  │
│   │  User    │ ───────────────>  │  Business Logic   │  │
│   │  Input   │                   │  (trusted data)   │  │
│   └──────────┘                   └──────────────────┘  │
│                                                          │
│   ┌──────────┐     VALIDATE      ┌──────────────────┐  │
│   │ External │ ───────────────>  │  Services         │  │
│   │   API    │                   │                   │  │
│   └──────────┘                   └──────────────────┘  │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│                    Your Application                      │
│                                                          │
│   ┌──────────┐     VALIDATE      ┌──────────────────┐  │
│   │  User    │ ───────────────>  │  Business Logic   │  │
│   │  Input   │                   │  (trusted data)   │  │
│   └──────────┘                   └──────────────────┘  │
│                                                          │
│   ┌──────────┐     VALIDATE      ┌──────────────────┐  │
│   │ External │ ───────────────>  │  Services         │  │
│   │   API    │                   │                   │  │
│   └──────────┘                   └──────────────────┘  │
└─────────────────────────────────────────────────────────┘

Schema Validation (Zod)

模式验证(Zod)

typescript
import { z } from 'zod';

// Define schemas
const createUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).max(128),
  name: z.string().min(1).max(100),
  age: z.number().int().min(13).max(120).optional(),
});

// Validate input
export async function createUser(input: unknown) {
  const data = createUserSchema.parse(input); // Throws if invalid
  // data is now typed and validated
  return db.users.create(data);
}

// API handler
export async function POST(req: Request) {
  try {
    const body = await req.json();
    const user = await createUser(body);
    return Response.json(user, { status: 201 });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return Response.json({ errors: error.errors }, { status: 400 });
    }
    throw error;
  }
}
typescript
import { z } from 'zod';

// Define schemas
const createUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).max(128),
  name: z.string().min(1).max(100),
  age: z.number().int().min(13).max(120).optional(),
});

// Validate input
export async function createUser(input: unknown) {
  const data = createUserSchema.parse(input); // Throws if invalid
  // data is now typed and validated
  return db.users.create(data);
}

// API handler
export async function POST(req: Request) {
  try {
    const body = await req.json();
    const user = await createUser(body);
    return Response.json(user, { status: 201 });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return Response.json({ errors: error.errors }, { status: 400 });
    }
    throw error;
  }
}

Sanitization

内容清理

typescript
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

const window = new JSDOM('').window;
const purify = DOMPurify(window);

// Sanitize HTML (for rich text fields)
const cleanHtml = purify.sanitize(userInput, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
  ALLOWED_ATTR: ['href']
});

// For plain text - escape or strip HTML
const plainText = purify.sanitize(userInput, { ALLOWED_TAGS: [] });
</input_validation>
<sql_injection>
typescript
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

const window = new JSDOM('').window;
const purify = DOMPurify(window);

// Sanitize HTML (for rich text fields)
const cleanHtml = purify.sanitize(userInput, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
  ALLOWED_ATTR: ['href']
});

// For plain text - escape or strip HTML
const plainText = purify.sanitize(userInput, { ALLOWED_TAGS: [] });
</input_validation>
<sql_injection>

SQL Injection Prevention

SQL注入防护

The Problem

问题

Attackers can manipulate SQL queries through unsanitized input. Example attack payload:
'; DROP TABLE users; --
攻击者可通过未清理的输入操纵SQL查询。 示例攻击载荷:
'; DROP TABLE users; --

The Solution: Parameterized Queries

解决方案:参数化查询

typescript
// SAFE: Parameterized query (Prisma)
const user = await prisma.user.findUnique({
  where: { email: email }
});

// SAFE: Parameterized query (raw SQL)
const user = await db.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
);

// SAFE: Supabase
const { data } = await supabase
  .from('users')
  .select()
  .eq('email', email);
typescript
// SAFE: Parameterized query (Prisma)
const user = await prisma.user.findUnique({
  where: { email: email }
});

// SAFE: Parameterized query (raw SQL)
const user = await db.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
);

// SAFE: Supabase
const { data } = await supabase
  .from('users')
  .select()
  .eq('email', email);

Supabase RLS (Row Level Security)

Supabase RLS(行级安全)

sql
-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Users can only see their own posts
CREATE POLICY "Users see own posts"
  ON posts FOR SELECT
  USING (auth.uid() = user_id);

-- Users can only create posts as themselves
CREATE POLICY "Users create own posts"
  ON posts FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- Users can only update their own posts
CREATE POLICY "Users update own posts"
  ON posts FOR UPDATE
  USING (auth.uid() = user_id);

-- Users can only delete their own posts
CREATE POLICY "Users delete own posts"
  ON posts FOR DELETE
  USING (auth.uid() = user_id);
</sql_injection>
<xss_prevention>
sql
-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Users can only see their own posts
CREATE POLICY "Users see own posts"
  ON posts FOR SELECT
  USING (auth.uid() = user_id);

-- Users can only create posts as themselves
CREATE POLICY "Users create own posts"
  ON posts FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- Users can only update their own posts
CREATE POLICY "Users update own posts"
  ON posts FOR UPDATE
  USING (auth.uid() = user_id);

-- Users can only delete their own posts
CREATE POLICY "Users delete own posts"
  ON posts FOR DELETE
  USING (auth.uid() = user_id);
</sql_injection>
<xss_prevention>

XSS Prevention

XSS防护

The Problem

问题

Attackers inject malicious scripts that execute in victim's browser, stealing cookies/data.
攻击者注入恶意脚本,在受害者浏览器中执行以窃取Cookie或数据。

The Solution: Auto-escaping + CSP

解决方案:自动转义 + CSP

typescript
// React auto-escapes by default - this is safe
return <div>Welcome, {userName}</div>;

// AVOID rendering raw HTML from user input
// If you absolutely must render user HTML, ALWAYS sanitize with DOMPurify first
import DOMPurify from 'dompurify';
const sanitizedContent = DOMPurify.sanitize(userContent);
typescript
// React auto-escapes by default - this is safe
return <div>Welcome, {userName}</div>;

// AVOID rendering raw HTML from user input
// If you absolutely must render user HTML, ALWAYS sanitize with DOMPurify first
import DOMPurify from 'dompurify';
const sanitizedContent = DOMPurify.sanitize(userContent);

Content Security Policy

内容安全策略

typescript
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: [
      "default-src 'self'",
      "script-src 'self'", // Avoid 'unsafe-inline' if possible
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self'",
      "connect-src 'self' https://api.supabase.co",
    ].join('; ')
  }
];
</xss_prevention>
<csrf_protection>
typescript
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: [
      "default-src 'self'",
      "script-src 'self'", // Avoid 'unsafe-inline' if possible
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self'",
      "connect-src 'self' https://api.supabase.co",
    ].join('; ')
  }
];
</xss_prevention>
<csrf_protection>

CSRF Protection

CSRF防护

The Problem

问题

Attackers trick authenticated users into submitting malicious requests to your site.
攻击者诱骗已认证用户向你的站点提交恶意请求。

The Solution: CSRF Tokens + SameSite Cookies

解决方案:CSRF令牌 + SameSite Cookie

typescript
// Server: Generate token
import { randomBytes } from 'crypto';

function generateCsrfToken(): string {
  return randomBytes(32).toString('hex');
}

// Store in session
session.csrfToken = generateCsrfToken();

// Client: Include in forms as hidden field
// Server: Validate token matches session

// Modern approach: SameSite cookies (most effective)
res.cookie('session', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict', // or 'lax'
});
</csrf_protection>
<references> For detailed patterns, load the appropriate reference:
TopicReference FileWhen to Load
Auth patterns
reference/auth-patterns.md
JWT, OAuth, sessions
Secrets
reference/secrets-management.md
API keys, env vars
Input validation
reference/input-validation.md
Sanitization, schemas
Supabase RLS
reference/rls-policies.md
Row level security
OWASP Top 10
reference/owasp-top-10.md
Vulnerability checklist
To load: Ask for the specific topic or check if context suggests it. </references>
<checklist>
typescript
// Server: Generate token
import { randomBytes } from 'crypto';

function generateCsrfToken(): string {
  return randomBytes(32).toString('hex');
}

// Store in session
session.csrfToken = generateCsrfToken();

// Client: Include in forms as hidden field
// Server: Validate token matches session

// Modern approach: SameSite cookies (most effective)
res.cookie('session', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict', // or 'lax'
});
</csrf_protection>
<references> 如需详细模式,请加载对应参考资料:
主题参考文件加载场景
身份认证模式
reference/auth-patterns.md
JWT、OAuth、会话
密钥管理
reference/secrets-management.md
API密钥、环境变量
输入验证
reference/input-validation.md
内容清理、校验规则
Supabase RLS
reference/rls-policies.md
行级安全
OWASP Top 10
reference/owasp-top-10.md
漏洞检查清单
加载方式: 请求特定主题或根据上下文判断是否需要加载。 </references>
<checklist>

Security Checklist

安全检查清单

Before deploying:
部署前需完成:

Authentication

身份认证

  • Passwords hashed with bcrypt (12+ rounds)
  • JWT tokens short-lived (15 min max)
  • Refresh tokens stored securely
  • Session cookies httpOnly + secure + sameSite
  • 密码使用bcrypt哈希(12轮及以上)
  • JWT令牌有效期短(最长15分钟)
  • 刷新令牌安全存储
  • 会话Cookie配置httpOnly + secure + sameSite

Secrets

密钥管理

  • No secrets in code or version control
  • Environment variables validated at startup
  • Secrets not logged
  • 代码或版本控制中无密钥
  • 环境变量在启动时验证
  • 密钥未被记录

Input

输入处理

  • All user input validated with schemas
  • SQL uses parameterized queries
  • HTML sanitized before rendering
  • File uploads validated (type, size, name)
  • 所有用户输入都使用校验规则验证
  • SQL使用参数化查询
  • HTML在渲染前进行清理
  • 文件上传经过验证(类型、大小、名称)

Headers

安全头

  • HTTPS enforced
  • CSP header configured
  • CORS restricted to allowed origins
  • Security headers set (HSTS, X-Frame-Options)
  • 强制使用HTTPS
  • 配置CSP头
  • CORS限制为允许的源
  • 设置安全头(HSTS、X-Frame-Options)

Authorization

授权控制

  • RLS enabled on all tables
  • API endpoints check permissions
  • Admin routes protected
  • Rate limiting on auth endpoints
</checklist>
  • 所有表启用RLS
  • API端点检查权限
  • 管理路由受保护
  • 身份认证端点配置速率限制
</checklist>