owasp-security by hoodini/ai-agents-skills
npx skills add https://github.com/hoodini/ai-agents-skills --skill owasp-security防范 Web 应用程序中的常见安全漏洞。
---|---|---
A01 | 失效的访问控制 | 正确的授权检查
A02 | 加密机制失效 | 强加密,安全存储
A03 | 注入 | 输入验证,参数化查询
A04 | 不安全的设计 | 威胁建模,安全模式
A05 | 安全配置错误 | 强化配置,不使用默认设置
A06 | 易受攻击和过时的组件 | 依赖项扫描,及时更新
A07 | 身份验证和授权失效 | 多因素认证,安全的会话管理
A08 | 软件和数据完整性故障 | 输入验证,签名更新
A09 | 安全日志和监控故障 | 全面的审计日志
A10 | 服务器端请求伪造 | URL 验证,白名单机制
// ❌ 错误:无授权检查
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});
// ✅ 正确:验证所有权
app.get('/api/users/:id', authenticate, async (req, res) => {
const userId = req.params.id;
// 用户只能访问自己的数据
if (req.user.id !== userId && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await db.users.findById(userId);
res.json(user);
});
// ✅ 正确:基于角色的访问控制
const requireRole = (...roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!roles.includes(req.user?.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
app.delete('/api/posts/:id', authenticate, requireRole('admin', 'moderator'), deletePost);
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// ❌ 错误:暴露了可预测的 ID
GET /api/invoices/1001
GET /api/invoices/1002 // 可以枚举他人的发票
// ✅ 正确:使用 UUID + 所有权检查
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoices.findOne({
id: req.params.id,
userId: req.user.id, // 强制所有权
});
if (!invoice) {
return res.status(404).json({ error: 'Not found' });
}
res.json(invoice);
});
import bcrypt from 'bcrypt';
import crypto from 'crypto';
// ✅ 使用 bcrypt 哈希密码
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// ✅ 安全的令牌生成
function generateSecureToken(length = 32): string {
return crypto.randomBytes(length).toString('hex');
}
// ✅ 加密敏感数据
const ALGORITHM = 'aes-256-gcm';
const KEY = crypto.scryptSync(process.env.ENCRYPTION_KEY!, 'salt', 32);
function encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
encrypted,
iv: iv.toString('hex'),
tag: cipher.getAuthTag().toString('hex'),
};
}
function decrypt(encrypted: string, iv: string, tag: string): string {
const decipher = crypto.createDecipheriv(ALGORITHM, KEY, Buffer.from(iv, 'hex'));
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
import helmet from 'helmet';
app.use(helmet());
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'strict-dynamic'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
},
}));
// ❌ 错误:字符串拼接
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✅ 正确:参数化查询
// 使用 Prisma
const user = await prisma.user.findUnique({ where: { email } });
// 使用原始 SQL(参数化)
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
// 使用 Knex
const user = await knex('users').where({ email }).first();
// ❌ 错误:在查询中直接使用用户输入
const user = await User.findOne({ username: req.body.username });
// 攻击示例:{ "username": { "$gt": "" } } 返回第一个用户
// ✅ 正确:验证输入类型
import { z } from 'zod';
const loginSchema = z.object({
username: z.string().min(3).max(50),
password: z.string().min(8),
});
app.post('/login', async (req, res) => {
const { username, password } = loginSchema.parse(req.body);
const user = await User.findOne({ username: String(username) });
// ...
});
import { execFile } from 'child_process';
// ❌ 错误:Shell 注入
exec(`convert ${userInput} output.png`); // userInput: "; rm -rf /"
// ✅ 正确:使用 execFile 并传递数组参数
execFile('convert', [userInput, 'output.png'], (error, stdout) => {
// 安全 - 参数不会被 shell 解释
});
// ✅ 正确:验证和清理
const allowedFormats = ['png', 'jpg', 'gif'];
if (!allowedFormats.includes(format)) {
throw new Error('Invalid format');
}
import rateLimit from 'express-rate-limit';
// 通用速率限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 每个窗口 100 个请求
standardHeaders: true,
legacyHeaders: false,
});
// 认证端点的严格限制
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 小时
max: 5, // 5 次失败尝试
skipSuccessfulRequests: true,
});
app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
password: z.string()
.min(8)
.regex(/[A-Z]/, '必须包含大写字母')
.regex(/[a-z]/, '必须包含小写字母')
.regex(/[0-9]/, '必须包含数字')
.regex(/[^A-Za-z0-9]/, '必须包含特殊字符'),
age: z.number().int().min(13).max(120),
role: z.enum(['user', 'admin']).default('user'),
});
app.post('/api/users', async (req, res) => {
try {
const data = userSchema.parse(req.body);
// 验证后的数据可以安全使用
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ errors: error.errors });
}
throw error;
}
});
// ✅ 切勿在生产环境中暴露堆栈跟踪
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack); // 记录日志用于调试
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? '内部服务器错误'
: err.message,
});
});
// ✅ 禁用敏感头部
app.disable('x-powered-by');
// ✅ 安全的 Cookie 配置
app.use(session({
secret: process.env.SESSION_SECRET!,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24 小时
},
resave: false,
saveUninitialized: false,
}));
# 检查漏洞
npm audit
npm audit fix
# 使用 Snyk 进行深度扫描
npx snyk test
npx snyk monitor
# 保持依赖项更新
npx npm-check-updates -u
// package.json - 使用精确版本或范围
{
"dependencies": {
"express": "^4.18.0", // 允许次要版本更新
"lodash": "4.17.21" // 精确版本
},
"overrides": {
"vulnerable-package": "^2.0.0" // 强制使用安全版本
}
}
import jwt from 'jsonwebtoken';
// ✅ JWT 短期有效期 + 刷新令牌
function generateTokens(userId: string) {
const accessToken = jwt.sign(
{ userId },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // 短期有效
);
const refreshToken = jwt.sign(
{ userId, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
// ✅ 安全的密码重置
async function initiatePasswordReset(email: string) {
const user = await db.users.findByEmail(email);
if (!user) return; // 不透露邮箱是否存在
const token = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
await db.passwordResets.create({
userId: user.id,
token: hashedToken,
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 小时
});
await sendEmail(email, `重置链接: /reset?token=${token}`);
}
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
// 设置 TOTP
async function setupMFA(userId: string) {
const secret = authenticator.generateSecret();
const otpauth = authenticator.keyuri(userId, 'MyApp', secret);
const qrCode = await QRCode.toDataURL(otpauth);
await db.users.update(userId, { mfaSecret: encrypt(secret) });
return { qrCode, secret };
}
// 验证 TOTP
function verifyMFA(token: string, secret: string): boolean {
return authenticator.verify({ token, secret });
}
// ✅ React 默认自动转义
const UserProfile = ({ user }) => (
<div>{user.name}</div> // 安全 - 自动转义
);
// ⚠️ 危险 - 尽可能避免使用
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
// ✅ 如果需要,清理 HTML
import DOMPurify from 'dompurify';
const sanitizedHtml = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// ✅ 内容安全策略
app.use(helmet.contentSecurityPolicy({
directives: {
scriptSrc: ["'self'"], // 禁止内联脚本
styleSrc: ["'self'", "'unsafe-inline'"],
},
}));
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// ✅ 记录安全事件
function logSecurityEvent(event: string, details: object) {
logger.warn({
type: 'security',
event,
...details,
timestamp: new Date().toISOString(),
});
}
// 用法示例
logSecurityEvent('failed_login', { email, ip: req.ip, userAgent: req.headers['user-agent'] });
logSecurityEvent('access_denied', { userId, resource, action });
logSecurityEvent('suspicious_activity', { userId, pattern: 'rapid_requests' });
import { URL } from 'url';
// ✅ 根据白名单验证 URL
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
function isAllowedUrl(urlString: string): boolean {
try {
const url = new URL(urlString);
// 阻止私有 IP
const privatePatterns = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[01])\./,
/^192\.168\./,
/^0\./,
/^169\.254\./, // 链路本地地址
];
if (privatePatterns.some(p => p.test(url.hostname))) {
return false;
}
// 检查白名单
return ALLOWED_HOSTS.includes(url.hostname);
} catch {
return false;
}
}
app.post('/api/fetch-url', async (req, res) => {
const { url } = req.body;
if (!isAllowedUrl(url)) {
return res.status(400).json({ error: 'URL not allowed' });
}
const response = await fetch(url);
// ...
});
## 部署前检查清单
### 身份验证
- [ ] 使用 bcrypt 哈希密码(成本 ≥ 12)
- [ ] JWT 令牌具有短期有效期
- [ ] 会话 Cookie 设置为 httpOnly、secure、sameSite
- [ ] 对认证端点实施速率限制
### 授权
- [ ] 所有端点都有身份验证检查
- [ ] 正确实施了基于角色的访问控制
- [ ] 没有不安全的直接对象引用漏洞
### 输入/输出
- [ ] 所有输入都使用 Zod/Joi 验证
- [ ] SQL 查询已参数化
- [ ] 防范跨站脚本攻击(内容安全策略,转义)
- [ ] 文件上传经过验证并隔离
### 基础设施
- [ ] 强制使用 HTTPS
- [ ] 配置了安全头部
- [ ] 依赖项已审计
- [ ] 密钥存储在环境变量中
### 监控
- [ ] 记录安全事件
- [ ] 启用了错误监控
- [ ] 配置了警报
每周安装量
730
代码仓库
GitHub 星标数
134
首次出现
2026年1月22日
安全审计
安装于
opencode649
codex628
gemini-cli625
github-copilot609
kimi-cli546
cursor543
Prevent common security vulnerabilities in web applications.
---|---|---
A01 | Broken Access Control | Proper authorization checks
A02 | Cryptographic Failures | Strong encryption, secure storage
A03 | Injection | Input validation, parameterized queries
A04 | Insecure Design | Threat modeling, secure patterns
A05 | Security Misconfiguration | Hardened configs, no defaults
A06 | Vulnerable Components | Dependency scanning, updates
A07 | Auth Failures | MFA, secure session management
A08 | Data Integrity Failures | Input validation, signed updates
A09 | Logging Failures | Comprehensive audit logs
A10 | SSRF | URL validation, allowlists
// ❌ BAD: No authorization check
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});
// ✅ GOOD: Verify ownership
app.get('/api/users/:id', authenticate, async (req, res) => {
const userId = req.params.id;
// Users can only access their own data
if (req.user.id !== userId && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await db.users.findById(userId);
res.json(user);
});
// ✅ GOOD: Role-based access control (RBAC)
const requireRole = (...roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!roles.includes(req.user?.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
app.delete('/api/posts/:id', authenticate, requireRole('admin', 'moderator'), deletePost);
// ❌ BAD: Predictable IDs exposed
GET /api/invoices/1001
GET /api/invoices/1002 // Can enumerate others' invoices
// ✅ GOOD: Use UUIDs + ownership check
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoices.findOne({
id: req.params.id,
userId: req.user.id, // Enforce ownership
});
if (!invoice) {
return res.status(404).json({ error: 'Not found' });
}
res.json(invoice);
});
import bcrypt from 'bcrypt';
import crypto from 'crypto';
// ✅ Hash passwords with bcrypt
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// ✅ Secure token generation
function generateSecureToken(length = 32): string {
return crypto.randomBytes(length).toString('hex');
}
// ✅ Encrypt sensitive data
const ALGORITHM = 'aes-256-gcm';
const KEY = crypto.scryptSync(process.env.ENCRYPTION_KEY!, 'salt', 32);
function encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
encrypted,
iv: iv.toString('hex'),
tag: cipher.getAuthTag().toString('hex'),
};
}
function decrypt(encrypted: string, iv: string, tag: string): string {
const decipher = crypto.createDecipheriv(ALGORITHM, KEY, Buffer.from(iv, 'hex'));
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
import helmet from 'helmet';
app.use(helmet());
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'strict-dynamic'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
},
}));
// ❌ BAD: String concatenation
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✅ GOOD: Parameterized queries
// With Prisma
const user = await prisma.user.findUnique({ where: { email } });
// With raw SQL (parameterized)
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
// With Knex
const user = await knex('users').where({ email }).first();
// ❌ BAD: Direct user input in query
const user = await User.findOne({ username: req.body.username });
// Attack: { "username": { "$gt": "" } } returns first user
// ✅ GOOD: Validate input type
import { z } from 'zod';
const loginSchema = z.object({
username: z.string().min(3).max(50),
password: z.string().min(8),
});
app.post('/login', async (req, res) => {
const { username, password } = loginSchema.parse(req.body);
const user = await User.findOne({ username: String(username) });
// ...
});
import { execFile } from 'child_process';
// ❌ BAD: Shell injection
exec(`convert ${userInput} output.png`); // userInput: "; rm -rf /"
// ✅ GOOD: Use execFile with array args
execFile('convert', [userInput, 'output.png'], (error, stdout) => {
// Safe - arguments are not shell-interpreted
});
// ✅ GOOD: Validate and sanitize
const allowedFormats = ['png', 'jpg', 'gif'];
if (!allowedFormats.includes(format)) {
throw new Error('Invalid format');
}
import rateLimit from 'express-rate-limit';
// General rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
});
// Strict limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 failed attempts
skipSuccessfulRequests: true,
});
app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
password: z.string()
.min(8)
.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'),
age: z.number().int().min(13).max(120),
role: z.enum(['user', 'admin']).default('user'),
});
app.post('/api/users', async (req, res) => {
try {
const data = userSchema.parse(req.body);
// Validated data is safe to use
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ errors: error.errors });
}
throw error;
}
});
// ✅ Never expose stack traces in production
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack); // Log for debugging
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message,
});
});
// ✅ Disable sensitive headers
app.disable('x-powered-by');
// ✅ Secure cookie configuration
app.use(session({
secret: process.env.SESSION_SECRET!,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
resave: false,
saveUninitialized: false,
}));
# Check for vulnerabilities
npm audit
npm audit fix
# Use Snyk for deeper scanning
npx snyk test
npx snyk monitor
# Keep dependencies updated
npx npm-check-updates -u
// package.json - Use exact versions or ranges
{
"dependencies": {
"express": "^4.18.0", // Minor updates OK
"lodash": "4.17.21" // Exact version
},
"overrides": {
"vulnerable-package": "^2.0.0" // Force safe version
}
}
import jwt from 'jsonwebtoken';
// ✅ JWT with short expiry + refresh tokens
function generateTokens(userId: string) {
const accessToken = jwt.sign(
{ userId },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // Short-lived
);
const refreshToken = jwt.sign(
{ userId, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
// ✅ Secure password reset
async function initiatePasswordReset(email: string) {
const user = await db.users.findByEmail(email);
if (!user) return; // Don't reveal if email exists
const token = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
await db.passwordResets.create({
userId: user.id,
token: hashedToken,
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
});
await sendEmail(email, `Reset link: /reset?token=${token}`);
}
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
// Setup TOTP
async function setupMFA(userId: string) {
const secret = authenticator.generateSecret();
const otpauth = authenticator.keyuri(userId, 'MyApp', secret);
const qrCode = await QRCode.toDataURL(otpauth);
await db.users.update(userId, { mfaSecret: encrypt(secret) });
return { qrCode, secret };
}
// Verify TOTP
function verifyMFA(token: string, secret: string): boolean {
return authenticator.verify({ token, secret });
}
// ✅ React auto-escapes by default
const UserProfile = ({ user }) => (
<div>{user.name}</div> // Safe - auto-escaped
);
// ⚠️ Dangerous - avoid if possible
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
// ✅ Sanitize HTML if needed
import DOMPurify from 'dompurify';
const sanitizedHtml = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// ✅ Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
scriptSrc: ["'self'"], // No inline scripts
styleSrc: ["'self'", "'unsafe-inline'"],
},
}));
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// ✅ Log security events
function logSecurityEvent(event: string, details: object) {
logger.warn({
type: 'security',
event,
...details,
timestamp: new Date().toISOString(),
});
}
// Usage
logSecurityEvent('failed_login', { email, ip: req.ip, userAgent: req.headers['user-agent'] });
logSecurityEvent('access_denied', { userId, resource, action });
logSecurityEvent('suspicious_activity', { userId, pattern: 'rapid_requests' });
import { URL } from 'url';
// ✅ Validate URLs against allowlist
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
function isAllowedUrl(urlString: string): boolean {
try {
const url = new URL(urlString);
// Block private IPs
const privatePatterns = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[01])\./,
/^192\.168\./,
/^0\./,
/^169\.254\./, // Link-local
];
if (privatePatterns.some(p => p.test(url.hostname))) {
return false;
}
// Check allowlist
return ALLOWED_HOSTS.includes(url.hostname);
} catch {
return false;
}
}
app.post('/api/fetch-url', async (req, res) => {
const { url } = req.body;
if (!isAllowedUrl(url)) {
return res.status(400).json({ error: 'URL not allowed' });
}
const response = await fetch(url);
// ...
});
## Pre-Deployment Checklist
### Authentication
- [ ] Passwords hashed with bcrypt (cost ≥ 12)
- [ ] JWT tokens have short expiry
- [ ] Session cookies are httpOnly, secure, sameSite
- [ ] Rate limiting on auth endpoints
### Authorization
- [ ] All endpoints have auth checks
- [ ] RBAC implemented correctly
- [ ] No IDOR vulnerabilities
### Input/Output
- [ ] All input validated with Zod/Joi
- [ ] SQL queries parameterized
- [ ] XSS prevented (CSP, escaping)
- [ ] File uploads validated and sandboxed
### Infrastructure
- [ ] HTTPS enforced
- [ ] Security headers configured
- [ ] Dependencies audited
- [ ] Secrets in environment variables
### Monitoring
- [ ] Security events logged
- [ ] Error monitoring enabled
- [ ] Alerts configured
Weekly Installs
730
Repository
GitHub Stars
134
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode649
codex628
gemini-cli625
github-copilot609
kimi-cli546
cursor543
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
103,800 周安装
YouTube视频分析师 - 逆向分析病毒内容公式,提取钩子、留存机制与情感触发点
647 周安装
SQLiteData 使用指南:SwiftData 轻量级替代方案,支持 CloudKit 同步
CTF密码学挑战速查指南 | 经典/现代密码攻击、RSA/ECC/流密码实战技巧
648 周安装
Bitrefill CLI:让AI智能体自主购买数字商品,支持加密货币支付
Bilibili 字幕提取工具 - 支持 AI 字幕检测与 ASR 转录,一键下载视频字幕
648 周安装
assistant-ui thread-list 线程列表:管理多聊天线程的 React AI SDK 组件
649 周安装