mcpField
I
implementing-api-schema-validation-security

implementing-api-schema-validation-security

github.com/mukul975安全35.2k255 次安装8 周前更新
代码仓库

关于 implementing-api-schema-validation-security

面向 API 安全实施的指南,介绍如何依据 OpenAPI 或 JSON Schema 验证请求和响应。内容涵盖运行时执行、开发阶段检查,以及批量赋值和数据泄露等模式风险。

主要功能

  1. 01验证 API 请求与响应结构
  2. 02通过模式规则约束字段
  3. 03配置网关级强制校验
  4. 04将模式检查加入 CI/CD
  5. 05识别常见模式安全缺陷

适用场景

  1. 01加固 API 网关部署
  2. 02在开发中落实 API 数据契约
  3. 03在拉取请求中加入模式合规检查

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill implementing-api-schema-validation-security

安装后在 Claude Code 中输入/implementing-api-schema-validation-security确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE
references/
api-reference.md
scripts/
agent.py

详细介绍

实施 API Schema 验证安全

概述

API Schema 验证确保所有通过 API 交换的数据都符合 OpenAPI Specification(OAS)或 JSON Schema 文档中定义的预定义结构。这可以防止注入攻击(SQLi、XSS、XXE),通过拒绝未知属性来阻止批量赋值,通过验证响应 Schema 来防止数据泄漏,并确保所有 API 交互的类型安全。Schema 验证在 API 网关层面(运行时强制执行)和开发期间(左移安全)同时运作。

何时使用

  • 在环境中部署或配置实施 API Schema 验证安全能力时
  • 在建立符合合规要求的安全控制措施时
  • 在为该领域构建或改进安全架构时
  • 在开展需要此实施的安全评估时

先决条件

  • 所有 API 端点使用 OpenAPI Specification v3.0 或 v3.1
  • 支持 Schema 验证的 API 网关(Cloudflare API Shield、Kong、AWS API Gateway)
  • 了解 JSON Schema draft-07 或更高版本
  • 具备 OpenAPI 验证库的开发环境
  • 用于自动化 Schema 合规测试的 CI/CD 流水线

核心实施

带安全约束的 OpenAPI Schema
openapi: 3.1.0
info:
  title: Secure E-Commerce API
  version: 2.0.0
servers:
  - url: https://api.example.com/v2
    description: Production (HTTPS enforced)
security:
  - OAuth2:
      - read:products
      - write:orders

paths:
  /products:
    post:
      operationId: createProduct
      security:
        - OAuth2: [write:products]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductCreate'
      responses:
        '201':
          description: Product created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /products/{productId}:
    get:
      operationId: getProduct
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'

components:
  schemas:
    ProductCreate:
      type: object
      required: [name, price, category]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 200
          pattern: '^[a-zA-Z0-9\s\-\.]+$'  # No special chars for injection prevention
        description:
          type: string
          maxLength: 2000
          # Sanitize HTML entities
        price:
          type: number
          format: float
          minimum: 0.01
          maximum: 999999.99
          exclusiveMinimum: 0
        category:
          type: string
          enum: [electronics, clothing, food, furniture, other]
        tags:
          type: array
          items:
            type: string
            maxLength: 50
            pattern: '^[a-zA-Z0-9\-]+$'
          maxItems: 10
          uniqueItems: true
      additionalProperties: false  # CRITICAL: Prevents mass assignment

    Product:
      type: object
      required: [id, name, price]
      properties:
        id:
          type: string
          format: uuid
          readOnly: true
        name:
          type: string
        price:
          type: number
        category:
          type: string
        tags:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
          readOnly: true
      additionalProperties: false  # Prevents data leakage of internal fields

    ValidationErrorResponse:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          enum: [VALIDATION_ERROR]
        message:
          type: string
          maxLength: 500
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              error:
                type: string
            additionalProperties: false
          maxItems: 50
      additionalProperties: false

  responses:
    ValidationError:
      description: Request validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationErrorResponse'
    Unauthorized:
      description: Authentication required

  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/authorize
          tokenUrl: https://auth.example.com/token
          scopes:
            read:products: Read product data
            write:products: Create and update products
            write:orders: Create orders
服务端 Schema 验证(Python/FastAPI)
"""API Schema Validation Middleware for FastAPI

Enforces strict schema validation on all request and response payloads
to prevent injection, mass assignment, and data leakage attacks.
"""

from fastapi import FastAPI, Request, Response, HTTPException
from fastapi.middleware import Middleware
from pydantic import BaseModel, Field, field_validator, ConfigDict
from typing import List, Optional
import re
import json
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()


# Strict Pydantic models with security constraints
class ProductCreate(BaseModel):
    model_config = ConfigDict(extra='forbid')  # Reject unknown fields (mass assignment)

    name: str = Field(min_length=1, max_length=200, pattern=r'^[a-zA-Z0-9\s\-\.]+$')
    description: Optional[str] = Field(default=None, max_length=2000)
    price: float = Field(gt=0, le=999999.99)
    category: str = Field(pattern=r'^(electronics|clothing|food|furniture|other)$')
    tags: Optional[List[str]] = Field(default=None, max_length=10)

    @field_validator('name')
    @classmethod
    def sanitize_name(cls, v):
        # Prevent XSS via HTML entities
        dangerous_patterns = ['<script', 'javascript:', 'onerror=', 'onload=']
        lower_v = v.lower()
        for pattern in dangerous_patterns:
            if pattern in lower_v:
                raise ValueError(f'Invalid characters in name')
        return v

    @field_validator('description')
    @classmethod
    def sanitize_description(cls, v):
        if v is None:
            return v
        # Strip potential SQL injection patterns
        sql_patterns = [
            r"('|--|;|/\*|\*/|xp_|exec\s|union\s+select|drop\s+table)",
        ]
        for pattern in sql_patterns:
            if re.search(pattern, v, re.IGNORECASE):
                raise ValueError('Invalid content in description')
        return v

    @field_validator('tags')
    @classmethod
    def validate_tags(cls, v):
        if v is None:
            return v
        if len(v) > 10:
            raise ValueError('Maximum 10 tags allowed')
        for tag in v:
            if not re.match(r'^[a-zA-Z0-9\-]+$', tag) or len(tag) > 50:
                raise ValueError(f'Invalid tag format: {tag}')
        return v


class ProductResponse(BaseModel):
    """Response model that explicitly defines allowed output fields.
    Prevents leakage of internal fields like internal_notes, cost_price, etc."""
    model_config = ConfigDict(extra='forbid')

    id: str
    name: str
    price: float
    category: str
    tags: List[str] = []
    created_at: str


class ResponseValidationMiddleware(BaseHTTPMiddleware):
    """Middleware to validate response payloads against schema.
    Prevents accidental data leakage by checking response content."""

    SCHEMA_MAP = {
        '/api/v2/products': {
            'POST': {'response_model': ProductResponse},
            'GET': {'response_model': ProductResponse},
        }
    }

    async def dispatch(self, request: Request, call_next):
        response = await call_next(request)

        # Only validate JSON responses
        content_type = response.headers.get('content-type', '')
        if 'application/json' not in content_type:
            return response

        # Check if endpoint has a registered response schema
        path = request.url.path
        method = request.method

        route_config = self.SCHEMA_MAP.get(path, {}).get(method)
        if not route_config:
            return response

        # Read and validate response body
        body = b""
        async for chunk in response.body_iterator:
            body += chunk

        try:
            data = json.loads(body)
            model = route_config['response_model']
            if isinstance(data, list):
                for item in data:
                    model.model_validate(item)
            else:
                model.model_validate(data)
        except Exception as e:
            # Log the validation failure for security monitoring
            print(f"SECURITY: Response schema violation on {method} {path}: {e}")
            # Return a safe error instead of potentially leaked data
            return Response(
                content=json.dumps({"error": "Internal server error"}),
                status_code=500,
                media_type="application/json"
            )

        return Response(
            content=body,
            status_code=response.status_code,
            headers=dict(response.headers),
            media_type=response.media_type
        )


app.add_middleware(ResponseValidationMiddleware)


@app.post("/api/v2/products", response_model=ProductResponse, status_code=201)
async def create_product(product: ProductCreate):
    # ProductCreate model with extra='forbid' automatically rejects
    # any unknown fields, preventing mass assignment attacks
    # (e.g., attacker trying to set is_admin=true or price=0)
    pass
Cloudflare API Shield Schema 验证
# Upload OpenAPI schema to Cloudflare API Shield
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/api_gateway/user_schemas" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: multipart/form-data" \
  -F "[email protected]" \
  -F "kind=openapi_v3"

# Enable schema validation with blocking mode
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/{zone_id}/api_gateway/settings/schema_validation" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "validation_default_mitigation_action": "block",
    "validation_override_mitigation_action": null
  }'
CI/CD Schema 合规测试
# GitHub Actions workflow for schema validation in CI
name: API Schema Security Check
on:
  pull_request:
    paths: ['api/**', 'openapi/**']

jobs:
  schema-security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Validate OpenAPI Schema
        run: |
          npm install -g @stoplight/spectral-cli
          spectral lint openapi.yaml --ruleset .spectral-security.yaml

      - name: Check for Security Anti-Patterns
        run: |
          python3 scripts/schema_security_check.py openapi.yaml

      - name: Run Contract Tests
        run: |
          npm install -g dredd
          dredd openapi.yaml http://localhost:3000 --hookfiles=./test/hooks.js

需要检测的安全反模式

反模式风险修复
additionalProperties: true 或缺失批量赋值设置 additionalProperties: false
字符串缺少 maxLength缓冲区溢出、DoS添加适当的 maxLength 约束
字符串字段缺少 pattern注入攻击添加正则表达式模式以限制输入
固定值字段缺少 enum意外的输入处理对已知值的字段使用 enum
无 TLS 的 format: password凭据泄露强制仅使用 HTTPS 的服务器 URL
缺少错误响应 Schema信息泄漏定义所有 4xx/5xx 响应 Schema
请求体中的 readOnly 字段数据篡改在服务端强制执行 readOnly

参考资料

评论

支持 Markdown