Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问clear审计通过

api-security-reviewAPI 安全审查

Agent Skill

用于辅助 API 设计、接口文档、请求响应结构和服务集成说明。它适合让 Agent 梳理 endpoint、生成 OpenAPI 草稿、检查字段命名、整理错误码或辅助前后端联调。使用时需要确认真实业务语义、鉴权方式、分页和错误处理规则;涉及生成接口文档时,应避免凭空补字段,最好从现有代码、schema 或接口样例中提取事实。

总安装

5,916

周安装

249

GitHub Stars

39

下载量

2,072
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:api-security-review(API 安全审查)
来源仓库:https://github.com/bobmatnyc/claude-mpm-skills
仓库路径:skills/api-security-review
安装命令:
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill api-security-review
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill api-security-review

简介

提供 API 端点开发的综合安全检查清单与最佳实践。

  • 适用于部署前的身份验证、授权、输入输出安全审查。
  • 包含 Next.js/Express 示例代码与常见漏洞防范指南。
  • 安装方式:npx skills add 仓库路径 --skill 名称。
  • 需结合实际代码上下文判断安全措施适用性。api-security-review 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

API Security Review Skill

Summary

Comprehensive security checklist for API endpoint development. Ensures proper authentication, authorization, input validation, output safety, and security logging are implemented before deployment.

When to Use

  • Before merging any PR with API changes
  • When creating new API endpoints
  • When modifying authentication/authorization logic
  • During security audits
  • Code review of API routes

Quick Checklist

Pre-Deployment Security Audit

  • Authentication: Route requires valid user identity
  • Authorization: Ownership/permission checks implemented
  • Input Validation: All inputs validated with schema (Zod/Joi/etc.)
  • Output Safety: No sensitive data exposed in responses
  • Logging: Security events logged appropriately
  • Rate Limiting: Protection against abuse configured
  • Error Handling: No system information leaked in errors

Authentication

Requirements

Every API endpoint must verify the user's identity before processing requests.

Next.js (App Router) with Clerk

import { auth } from '@clerk/nextjs';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  // 1. Authenticate request
  const { userId } = await auth();

  if (!userId) {
    return NextResponse.json(
      { error: "Unauthorized" },
      { status: 401 }
    );
  }

  // Continue with authenticated request...
}

Express.js with JWT

import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

function authenticateToken(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.sendStatus(401);
  }

  jwt.verify(token, process.env.JWT_SECRET!, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

app.get('/api/protected', authenticateToken, (req, res) => {
  // Request is authenticated
});

FastAPI with OAuth2

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)):
    user = await verify_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return user

@app.get("/api/protected")
async def protected_route(current_user: User = Depends(get_current_user)):
    return {"user": current_user.email}

Django REST Framework

from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def protected_view(request):
    # request.user is authenticated
    return Response({'user': request.user.email})

Authorization

Resource Ownership Verification

Authentication proves WHO the user is. Authorization proves the user has permission to access the resource.

Next.js Example

import { auth } from '@clerk/nextjs';
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { eq } from 'drizzle-orm';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  // 1. Authenticate
  const { userId } = await auth();
  if (!userId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  // 2. Fetch resource
  const resource = await db.query.resources.findFirst({
    where: eq(resources.id, params.id)
  });

  if (!resource) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  // 3. Authorize - Check ownership
  if (resource.ownerId !== userId) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  }

  // 4. Return authorized data
  return NextResponse.json(resource);
}

Role-Based Access Control (RBAC)

enum Role {
  USER = 'user',
  ADMIN = 'admin',
  MODERATOR = 'moderator'
}

function requireRole(allowedRoles: Role[]) {
  return async (request: Request) => {
    const { userId } = await auth();
    if (!userId) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const user = await db.query.users.findFirst({
      where: eq(users.clerkId, userId)
    });

    if (!user || !allowedRoles.includes(user.role)) {
      return NextResponse.json({ error: "Forbidden" }, { status: 403 });
    }

    return null; // Authorized
  };
}

export async function DELETE(request: Request) {
  const authError = await requireRole([Role.ADMIN, Role.MODERATOR])(request);
  if (authError) return authError;

  // User is authorized as admin or moderator
}

Multi-Tenant Data Isolation

// CRITICAL: Prevent cross-tenant data leaks

// ❌ WRONG - No tenant check
const orders = await db.query.orders.findMany({
  where: eq(orders.userId, userId)
});

// ✅ CORRECT - Tenant isolation
const user = await db.query.users.findFirst({
  where: eq(users.clerkId, userId)
});

const orders = await db.query.orders.findMany({
  where: and(
    eq(orders.userId, userId),
    eq(orders.tenantId, user.tenantId) // CRITICAL: tenant boundary
  )
});

Input Validation

Zod Schema Validation (TypeScript)

import { z } from 'zod';
import { NextResponse } from 'next/server';

const updateUserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email().optional(),
  age: z.number().int().min(0).max(150).optional(),
  role: z.enum(['user', 'admin', 'moderator']).optional(),
});

export async function PATCH(request: Request) {
  const { userId } = await auth();
  if (!userId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  // Parse and validate input
  const body = await request.json();
  const result = updateUserSchema.safeParse(body);

  if (!result.success) {
    return NextResponse.json({
      error: "Validation failed",
      details: result.error.issues
    }, { status: 400 });
  }

  // Safe to use validated data
  const validatedData = result.data;
  // ... update logic
}

Pydantic Validation (Python)

from pydantic import BaseModel, EmailStr, Field, validator
from fastapi import HTTPException

class UpdateUser(BaseModel):
    id: str = Field(..., regex=r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')
    email: EmailStr | None = None
    age: int | None = Field(None, ge=0, le=150)
    role: str | None = Field(None, regex=r'^(user|admin|moderator)$')

    @validator('email')
    def email_must_not_be_disposable(cls, v):
        if v and any(domain in v for domain in ['tempmail.com', '10minutemail.com']):
            raise ValueError('Disposable email addresses not allowed')
        return v

@app.patch("/api/users")
async def update_user(user_data: UpdateUser, current_user: User = Depends(get_current_user)):
    # user_data is validated
    return {"status": "updated"}

SQL Injection Prevention

// ❌ NEVER: Raw SQL with string interpolation
const userId = request.params.id;
const query = `SELECT * FROM users WHERE id = '${userId}'`; // VULNERABLE!
db.execute(query);

// ✅ ALWAYS: Use ORM or parameterized queries
import { eq } from 'drizzle-orm';
const user = await db.query.users.findFirst({
  where: eq(users.id, userId)
});

// ✅ OR: Parameterized raw query
const [user] = await db.execute(
  'SELECT * FROM users WHERE id = ?',
  [userId]
);

File Upload Validation

const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];

export async function POST(request: Request) {
  const formData = await request.formData();
  const file = formData.get('file') as File;

  if (!file) {
    return NextResponse.json({ error: "No file provided" }, { status: 400 });
  }

  // Validate file size
  if (file.size > MAX_FILE_SIZE) {
    return NextResponse.json({
      error: "File too large. Max 5MB"
    }, { status: 400 });
  }

  // Validate file type
  if (!ALLOWED_TYPES.includes(file.type)) {
    return NextResponse.json({
      error: "Invalid file type. Only JPEG, PNG, GIF allowed"
    }, { status: 400 });
  }

  // Process file...
}

Output Safety

Remove Sensitive Data from Responses

// ❌ WRONG - Exposing sensitive fields
const user = await db.query.users.findFirst({
  where: eq(users.id, userId)
});
return NextResponse.json(user); // Includes password hash, internal IDs, etc.

// ✅ CORRECT - Explicitly select safe fields
const user = await db.query.users.findFirst({
  where: eq(users.id, userId),
  columns: {
    id: true,
    email: true,
    name: true,
    createdAt: true,
    // Exclude: passwordHash, internalNotes, apiKey, etc.
  }
});
return NextResponse.json(user);

// ✅ BETTER - Use DTOs
interface PublicUserDTO {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
}

function toPublicUser(user: User): PublicUserDTO {
  return {
    id: user.id,
    email: user.email,
    name: user.name,
    createdAt: user.createdAt
  };
}

return NextResponse.json(toPublicUser(user));

Mask PII in Logs

function sanitizeForLogging(data: any) {
  const sanitized = { ...data };

  // Mask email
  if (sanitized.email) {
    const [local, domain] = sanitized.email.split('@');
    sanitized.email = `${local.slice(0, 2)}***@${domain}`;
  }

  // Mask SSN
  if (sanitized.ssn) {
    sanitized.ssn = `***-**-${sanitized.ssn.slice(-4)}`;
  }

  // Remove sensitive fields
  delete sanitized.passwordHash;
  delete sanitized.apiKey;

  return sanitized;
}

console.log('User updated:', sanitizeForLogging(user));

Safe Error Messages

// ❌ WRONG - Leaking system information
try {
  await db.execute(query);
} catch (error) {
  return NextResponse.json({
    error: error.message, // Might expose SQL, file paths, etc.
    stack: error.stack     // NEVER expose in production
  }, { status: 500 });
}

// ✅ CORRECT - Generic error with logging
try {
  await db.execute(query);
} catch (error) {
  console.error('Database error:', error); // Log full error internally
  return NextResponse.json({
    error: "An error occurred processing your request"
  }, { status: 500 });
}

Logging

Security Event Logging

enum SecurityEvent {
  AUTH_FAILURE = 'auth_failure',
  UNAUTHORIZED_ACCESS = 'unauthorized_access',
  PERMISSION_DENIED = 'permission_denied',
  RATE_LIMIT_EXCEEDED = 'rate_limit_exceeded',
  INVALID_INPUT = 'invalid_input'
}

function logSecurityEvent(event: SecurityEvent, details: any) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    event,
    userId: details.userId || 'anonymous',
    ip: details.ip,
    endpoint: details.endpoint,
    details: sanitizeForLogging(details)
  }));
}

// Usage
export async function GET(request: Request) {
  const { userId } = await auth();

  if (!userId) {
    logSecurityEvent(SecurityEvent.AUTH_FAILURE, {
      ip: request.headers.get('x-forwarded-for'),
      endpoint: request.url
    });
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
}

Request Tracing

import { v4 as uuidv4 } from 'uuid';

export async function middleware(request: Request) {
  const requestId = uuidv4();

  console.log(JSON.stringify({
    requestId,
    method: request.method,
    url: request.url,
    timestamp: new Date().toISOString()
  }));

  // Pass request ID through headers
  const response = await fetch(request.url, {
    headers: {
      ...request.headers,
      'X-Request-ID': requestId
    }
  });

  return response;
}

Example Secure Endpoint

Complete Next.js API Route

import { auth } from '@clerk/nextjs';
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { eq, and } from 'drizzle-orm';
import { z } from 'zod';
import { ratelimit } from '@/lib/ratelimit';

// 1. Input validation schema
const updateResourceSchema = z.object({
  name: z.string().min(1).max(100),
  description: z.string().max(500).optional(),
  isPublic: z.boolean().optional()
});

// 2. DTO for safe output
interface ResourceDTO {
  id: string;
  name: string;
  description: string;
  isPublic: boolean;
  createdAt: Date;
}

function toResourceDTO(resource: any): ResourceDTO {
  return {
    id: resource.id,
    name: resource.name,
    description: resource.description,
    isPublic: resource.isPublic,
    createdAt: resource.createdAt
    // Exclude: ownerId, internalNotes, etc.
  };
}

export async function PATCH(
  request: Request,
  { params }: { params: { id: string } }
) {
  try {
    // 3. Rate limiting
    const { success } = await ratelimit.limit(request.headers.get('x-forwarded-for') || 'anonymous');
    if (!success) {
      return NextResponse.json({ error: "Too many requests" }, { status: 429 });
    }

    // 4. Authentication
    const { userId } = await auth();
    if (!userId) {
      console.log('Auth failure:', { endpoint: request.url });
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    // 5. Input validation
    const body = await request.json();
    const result = updateResourceSchema.safeParse(body);

    if (!result.success) {
      return NextResponse.json({
        error: "Validation failed",
        details: result.error.issues
      }, { status: 400 });
    }

    // 6. Fetch and verify existence
    const resource = await db.query.resources.findFirst({
      where: eq(resources.id, params.id)
    });

    if (!resource) {
      return NextResponse.json({ error: "Not found" }, { status: 404 });
    }

    // 7. Authorization check
    if (resource.ownerId !== userId) {
      console.log('Permission denied:', { userId, resourceId: params.id });
      return NextResponse.json({ error: "Forbidden" }, { status: 403 });
    }

    // 8. Update resource
    const [updatedResource] = await db.update(resources)
      .set({
        ...result.data,
        updatedAt: new Date()
      })
      .where(eq(resources.id, params.id))
      .returning();

    // 9. Return safe response
    return NextResponse.json(toResourceDTO(updatedResource));

  } catch (error) {
    // 10. Safe error handling
    console.error('Error updating resource:', error);
    return NextResponse.json({
      error: "An error occurred"
    }, { status: 500 });
  }
}

Framework Patterns

Express.js Middleware Pattern

import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';

// Validation middleware factory
function validateSchema(schema: z.ZodSchema) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(400).json({
        error: 'Validation failed',
        details: result.error.issues
      });
    }
    req.body = result.data;
    next();
  };
}

// Authorization middleware
async function requireOwnership(req: Request, res: Response, next: NextFunction) {
  const resource = await db.resources.findById(req.params.id);

  if (!resource) {
    return res.status(404).json({ error: 'Not found' });
  }

  if (resource.ownerId !== req.user.id) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  req.resource = resource;
  next();
}

// Usage
const updateSchema = z.object({ name: z.string() });
app.patch('/api/resources/:id',
  authenticate,
  validateSchema(updateSchema),
  requireOwnership,
  async (req, res) => {
    // All checks passed
    const updated = await updateResource(req.resource, req.body);
    res.json(toDTO(updated));
  }
);

FastAPI Dependency Injection

from fastapi import Depends, HTTPException
from typing import Annotated

async def verify_ownership(
    resource_id: str,
    current_user: User = Depends(get_current_user)
):
    resource = await db.resources.get(resource_id)
    if not resource:
        raise HTTPException(status_code=404, detail="Not found")
    if resource.owner_id != current_user.id:
        raise HTTPException(status_code=403, detail="Forbidden")
    return resource

@app.patch("/api/resources/{resource_id}")
async def update_resource(
    data: UpdateResourceSchema,
    resource: Resource = Depends(verify_ownership)
):
    # Resource ownership verified
    updated = await resource.update(data.dict())
    return ResourceDTO.from_orm(updated)

Common Vulnerabilities

OWASP Top 10 API Security

1. Broken Object Level Authorization (BOLA)

// ❌ VULNERABLE
export async function GET(request: Request) {
  const { userId } = await auth();
  const resourceId = new URL(request.url).searchParams.get('id');

  // Missing ownership check!
  const resource = await db.query.resources.findFirst({
    where: eq(resources.id, resourceId)
  });

  return NextResponse.json(resource);
}

// ✅ FIXED
export async function GET(request: Request) {
  const { userId } = await auth();
  const resourceId = new URL(request.url).searchParams.get('id');

  const resource = await db.query.resources.findFirst({
    where: and(
      eq(resources.id, resourceId),
      eq(resources.ownerId, userId) // Ownership check
    )
  });

  if (!resource) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  return NextResponse.json(resource);
}

2. Mass Assignment

// ❌ VULNERABLE - User can set any field
export async function PATCH(request: Request) {
  const body = await request.json();

  // User could send: { role: 'admin', isVerified: true }
  await db.update(users)
    .set(body) // DANGEROUS!
    .where(eq(users.id, userId));
}

// ✅ FIXED - Explicit allowed fields
const allowedFields = z.object({
  name: z.string().optional(),
  bio: z.string().optional()
  // role, isVerified NOT allowed
});

export async function PATCH(request: Request) {
  const body = await request.json();
  const validated = allowedFields.parse(body);

  await db.update(users)
    .set(validated)
    .where(eq(users.id, userId));
}

3. Excessive Data Exposure

// ❌ VULNERABLE
return NextResponse.json(user); // All fields exposed

// ✅ FIXED
return NextResponse.json({
  id: user.id,
  name: user.name,
  email: user.email
  // passwordHash, resetToken, etc. excluded
});

4. Rate Limiting

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10 s'),
});

export async function POST(request: Request) {
  const identifier = request.headers.get('x-forwarded-for') || 'anonymous';
  const { success } = await ratelimit.limit(identifier);

  if (!success) {
    return NextResponse.json({ error: "Too many requests" }, { status: 429 });
  }

  // Process request...
}

Summary

Security Checklist Template

## Security Review for [Endpoint Name]

### Authentication
- [ ] User identity verified before processing
- [ ] Invalid tokens rejected with 401
- [ ] Token expiration checked

### Authorization
- [ ] Resource ownership verified
- [ ] Role/permission checks implemented
- [ ] Cross-tenant data isolation enforced

### Input Validation
- [ ] All inputs validated with schema (Zod/Pydantic)
- [ ] File uploads size/type limited
- [ ] SQL injection prevented (using ORM)
- [ ] XSS prevention in place

### Output Safety
- [ ] Sensitive fields excluded from responses
- [ ] PII masked in logs
- [ ] Error messages don't leak system info

### Rate Limiting
- [ ] Rate limits configured per endpoint
- [ ] DDoS protection in place

### Logging
- [ ] Failed auth attempts logged
- [ ] Permission denials logged
- [ ] Request IDs for traceability

Use this skill during code reviews and before deploying API changes to ensure comprehensive security coverage.

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude Code

31.33%
按下载量换算649

Gemini CLI

24.88%
按下载量换算516

OpenCode

16.71%
按下载量换算346

Antigravity

13.3%
按下载量换算276

windsurf

7.79%
按下载量换算161

github-copilot

3.24%
按下载量换算67

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills