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:
-
Secrets: Never commit to git, validate at startuptypescript
// .env (gitignored) + envSchema.parse(process.env) -
Auth: Short-lived JWTs + httpOnly cookiestypescript
jwt.sign(payload, secret, { expiresIn: '15m' }) -
Input: Validate everything with schemastypescript
const data = z.object({ email: z.string().email() }).parse(input) -
SQL: Always use parameterized queries (ORMs handle this)
-
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>
任何新项目的安全要点:
-
密钥:绝对不要提交到git,在启动时进行验证typescript
// .env (gitignored) + envSchema.parse(process.env) -
身份认证:短期JWT + httpOnly Cookietypescript
jwt.sign(payload, secret, { expiresIn: '15m' }) -
输入:使用校验规则验证所有输入typescript
const data = z.object({ email: z.string().email() }).parse(input) -
SQL:始终使用参数化查询(ORM会自动处理)
-
RLS:在所有Supabase表上启用用户范围的策略 </quick_start>
<success_criteria>
安全实现成功的标志:
- 所有密钥都存储在环境变量中,并在启动时验证
- 版本控制中无密钥(使用gitleaks验证)
- JWT令牌有效期短(≤15分钟)且支持刷新令牌轮换
- 所有用户输入都使用Zod或类似的校验规则验证
- 所有数据库表都启用RLS并配置适当的策略
- 配置CSP头(尽可能避免unsafe-inline)
- 部署前完成安全检查清单 </success_criteria>
<security_mindset>
The Security Mindset
安全思维模式
Core Principles
核心原则
- Defense in depth - Multiple layers of security, not one wall
- Least privilege - Grant minimum access required
- Never trust input - Validate everything from users and external systems
- Fail secure - Errors should deny access, not grant it
- Keep secrets secret - API keys never in code or logs
- 纵深防御 - 多层安全防护,而非单一壁垒
- 最小权限 - 仅授予完成任务所需的最小权限
- 绝不信任输入 - 验证所有来自用户和外部系统的输入
- 安全失败 - 错误应拒绝访问,而非授予访问权限
- 密钥保密 - 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>
在发布任何功能前:
- 此功能会暴露哪些数据?
- 谁可以访问此端点/页面?
- 如果用户发送恶意输入会发生什么?
- 密钥是否得到妥善保护?
- 敏感数据是否被记录? </security_mindset>
Authentication Patterns
身份认证模式
JWT vs Session
JWT vs Session
| Aspect | JWT | Session |
|---|---|---|
| Storage | Client (localStorage/cookie) | Server (DB/Redis) |
| Scalability | Stateless, easy to scale | Requires shared session store |
| Revocation | Hard (need blacklist) | Easy (delete from store) |
| Size | Larger (contains claims) | Small (just session ID) |
| Best for | APIs, microservices | Traditional web apps |
| 维度 | JWT | Session |
|---|---|---|
| 存储位置 | 客户端(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 payloadtypescript
// 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 payloadToken Storage
令牌存储
| Method | XSS Safe | CSRF Safe | Recommendation |
|---|---|---|---|
| localStorage | No | Yes | Avoid for auth |
| httpOnly cookie | Yes | No (needs CSRF token) | Recommended |
| Memory (variable) | Yes | Yes | Best 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 passwordstypescript
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 passwordsPassword 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);
}<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);
}<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
undefinedEnvironment Variables
环境变量
bash
undefinedbash
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
undefinedDATABASE_URL=postgresql://localhost:5432/myapp
JWT_SECRET=generate-secure-secret
STRIPE_SECRET_KEY=sk_test_your_key_here
undefinedLoading 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 existtypescript
// 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 existSecret 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:
| Topic | Reference File | When to Load |
|---|---|---|
| Auth patterns | | JWT, OAuth, sessions |
| Secrets | | API keys, env vars |
| Input validation | | Sanitization, schemas |
| Supabase RLS | | Row level security |
| OWASP Top 10 | | 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>
如需详细模式,请加载对应参考资料:
| 主题 | 参考文件 | 加载场景 |
|---|---|---|
| 身份认证模式 | | JWT、OAuth、会话 |
| 密钥管理 | | API密钥、环境变量 |
| 输入验证 | | 内容清理、校验规则 |
| Supabase RLS | | 行级安全 |
| OWASP Top 10 | | 漏洞检查清单 |
加载方式: 请求特定主题或根据上下文判断是否需要加载。
</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
- 所有表启用RLS
- API端点检查权限
- 管理路由受保护
- 身份认证端点配置速率限制