Limitação de taxa

A limitação de taxa é um mecanismo de defesa fundamental para proteger seu sistema contra abuso de API, ataques de DoS e scraping.

Comparação de algoritmos

AlgoritmoCaracterísticasPrósContras
Fixed WindowContagens em um intervalo de tempo fixoSimples de implementar, eficiente em termos de memóriaAs explosões ocorrem nos limites das janelas
Sliding Window LogCarimbos de data e hora da solicitação de registrosControle precisoAlto consumo de memória
Sliding Window CounterMédia ponderada das janelas anterior e atualBom equilíbrioUm pouco complexo
Token BucketConsome tokens para acessoPermite explosões enquanto impõe limitesRequer ajuste de parâmetros
Leaky BucketProcessa solicitações em uma taxa constanteTaxa de saída estávelFraco em lidar com explosões

Implementação de limitação de taxa no Express.js

JavaScript (Express)Limitação de taxa em várias camadas
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');

// Limite global: aplicado a todas as APIs
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutos
  max: 100,
  standardHeaders: true,    // Retorna cabeçalhos RateLimit-*
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later.' },
});

// Para pontos de extremidade de autenticação: limites mais rígidos
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,  // Tentativas de login limitadas a 5 a cada 15 minutos
  skipSuccessfulRequests: true, // As solicitações bem-sucedidas não são contadas
});

// Para ambientes distribuídos: backend do Redis
const distributedLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args) => redisClient.sendCommand(args) }),
  windowMs: 60 * 1000,
  max: 30,
});

app.use(globalLimiter);
app.use('/api/auth', authLimiter);

Cabeçalhos de resposta

HTTP Response HeadersRFC 6585 / draft-ietf-httpapi-ratelimit-headers
RateLimit-Limit: 100
RateLimit-Remaining: 42
RateLimit-Reset: 1672531200
Retry-After: 30
Chaves de limitação de taxa

Ao aplicar limites de taxa usando chaves compostas, como chave de API, ID de usuário ou ponto de extremidade, além do endereço IP, é possível minimizar o impacto sobre os usuários legítimos.

🔍 Validação de entrada

Todas as entradas de API podem conter dados maliciosos. Sempre valide no lado do servidor.

Princípios de validação

Abordagem Allowlist

Define uma lista de valores permitidos. Mais seguro do que uma denylist (lista de bloqueio). Pode lidar com novos padrões de ataque.

Tipo, comprimento e alcance

Verifique rigorosamente os tipos de dados, a contagem máxima de caracteres e os limites numéricos superior/inferior. Uma defesa fundamental contra estouros de buffer.

Sanitizar vs. Rejeitar

Rejeitar uma entrada inválida é mais seguro do que "consertá-la". A higienização pode produzir transformações inesperadas.

Validação com esquema JSON

JavaScript (Express + Ajv)Validação de esquema
const Ajv = require('ajv');
const addFormats = require('ajv-formats');

const ajv = new Ajv({ allErrors: true, removeAdditional: true });
addFormats(ajv);

// Esquema para a API de criação de usuários
const createUserSchema = {
  type: 'object',
  required: ['name', 'email'],
  additionalProperties: false,
  properties: {
    name: {
      type: 'string',
      minLength: 1,
      maxLength: 100,
      pattern: '^[a-zA-Z0-9\\s\\-]+$', // Restringir a caracteres permitidos
    },
    email: {
      type: 'string',
      format: 'email',
      maxLength: 254,
    },
    age: {
      type: 'integer',
      minimum: 0,
      maximum: 150,
    },
  },
};

// Middleware de validação
function validateBody(schema) {
  const validate = ajv.compile(schema);
  return (req, res, next) => {
    if (!validate(req.body)) {
      return res.status(400).json({
        error: 'Validation failed',
        details: validate.errors,
      });
    }
    next();
  };
}

app.post('/api/users', validateBody(createUserSchema), createUser);

Ataques comuns e contramedidas de validação

AtaqueContramedidaExemplo
Injeção de SQLConsultas parametrizadas, use um ORMdb.query('SELECT * FROM users WHERE id = ?', [id])
Injeção de NoSQLVerificação de tipo, higienização de operadores $Garantir que a entrada seja uma cadeia de caracteres (rejeitar objetos)
XSS (via resposta da API)Especificar Content-Type, escapar da saídaContent-Type: application/json
Transversão de caminhoRemover separadores de caminho da entradaNormalize com path.basename()
XXE (Entidade Externa XML)Desativar a resolução de entidades externasDesativar nas configurações do analisador XML

🌐 CORS (compartilhamento de recursos entre origens)

O CORS é um mecanismo que controla a política de mesma origem do navegador. A configuração incorreta pode levar a sérios riscos de segurança.

Configuração perigosa

Access-Control-Allow-Origin: * e Access-Control-Allow-Credentials: true não podem ser usados juntos. Restringir o uso de curingas somente a APIs públicas.

Configuração segura de CORS

JavaScript (Express)Configuração de CORS
const cors = require('cors');

// Especificar explicitamente as origens permitidas
const allowedOrigins = [
  'https://app.example.com',
  'https://admin.example.com',
];

app.use(cors({
  origin(origin, callback) {
    // Permitir a comunicação de servidor para servidor (sem origem)
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
  credentials: true,
  maxAge: 86400,  // Cache de pré-voo: 24 horas
}));

Lista de verificação de CORS

Referência de cabeçalhos CORS

CabeçalhoFinalidadeValor recomendado
Access-Control-Allow-OriginOrigens permitidasEspecificação explícita do domínio
Access-Control-Allow-MethodsMétodos HTTP permitidosMínimo exigido apenas
Access-Control-Allow-HeadersCabeçalhos de solicitação permitidosMínimo exigido apenas
Access-Control-Allow-CredentialsPermitir a transmissão de cookiestrue (somente quando a autenticação é necessária)
Access-Control-Max-AgeDuração do cache do preflight em segundos86400 (24 horas)
Access-Control-Expose-HeadersCabeçalhos de resposta legíveis por JavaScriptSomente os cabeçalhos necessários, como RateLimit-*

🤖 AI / LLM API Rate Limiting

LLM APIs introduce new dimensions to rate limiting: token consumption, cost per request, and compute-intensive inference.

Traditional API vs. LLM API Rate Limiting

DimensionTraditional APILLM API
Cost per requestLow, predictableVariable, can be 100x+ (based on tokens)
Rate limit unitRequests per time windowTokens per minute (TPM) + Requests per minute (RPM)
Abuse patternScraping, brute force, DDoSPrompt injection, resource exhaustion, denial-of-wallet
Algorithm fitFixed/Sliding windowToken bucket (weighted by token count)

Token-Aware Rate Limiting (Python)

PythonToken-Based Rate Limiter
import tiktoken
import time
from collections import defaultdict

class TokenRateLimiter:
    """Rate limiter that counts tokens, not just requests."""

    def __init__(self, tokens_per_minute=100_000, requests_per_minute=60):
        self.tpm_limit = tokens_per_minute
        self.rpm_limit = requests_per_minute
        self.usage = defaultdict(lambda: {"tokens": [], "requests": []})
        self.encoder = tiktoken.encoding_for_model("gpt-4")

    def count_tokens(self, text: str) -> int:
        return len(self.encoder.encode(text))

    def check_limit(self, user_id: str, prompt: str) -> dict:
        now = time.time()
        window = now - 60  # 1-minute sliding window
        user = self.usage[user_id]

        # Clean up expired entries
        user["tokens"] = [(t, c) for t, c in user["tokens"] if t > window]
        user["requests"] = [t for t in user["requests"] if t > window]

        # Check RPM
        if len(user["requests"]) >= self.rpm_limit:
            return {"allowed": False, "reason": "RPM limit exceeded"}

        # Check TPM
        token_count = self.count_tokens(prompt)
        used_tokens = sum(c for _, c in user["tokens"])
        if used_tokens + token_count > self.tpm_limit:
            return {"allowed": False, "reason": "TPM limit exceeded"}

        # Record usage
        user["tokens"].append((now, token_count))
        user["requests"].append(now)
        return {"allowed": True, "tokens_used": token_count}
OWASP References

Related: LLM10: Model Denial of Service, ASI04: Cascading Hallucination Attacks

🛡 Prompt Injection & AI Input Validation

Prompt injection is the #1 risk for LLM applications. Apply defense-in-depth with input validation, structural separation, and output verification.

Prompt Sanitization

Strip or escape special tokens, instruction-like patterns, and control characters from user input before including in prompts.

Structural Input Separation

Use delimiters, XML tags, or separate message roles to clearly isolate system instructions from user-provided content.

Output Verification

Validate LLM responses against expected schemas. Check for data leakage, instruction following, and malicious content before rendering.

Prompt Injection Attack Vectors & Mitigations

AtaqueContramedidaExemplo
Direct Prompt InjectionInput sanitization + instruction/data separation"Ignore previous instructions and..."
Indirect Prompt InjectionSanitize RAG results + canary tokensMalicious instructions hidden in retrieved documents
Context StuffingToken limits + input truncationOverloading context window to push out system instructions
Parameter TamperingSchema validation + typed parametersManipulating temperature, max_tokens, or model parameters

Structured Input Validation (Python / Pydantic)

PythonPydantic Validation for LLM Requests
from pydantic import BaseModel, Field, field_validator
import re

class LLMRequest(BaseModel):
    """Validated LLM request with prompt injection defenses."""
    user_message: str = Field(..., max_length=4000)
    max_tokens: int = Field(default=1000, ge=1, le=4096)
    temperature: float = Field(default=0.7, ge=0.0, le=2.0)

    @field_validator("user_message")
    @classmethod
    def sanitize_prompt(cls, v: str) -> str:
        # Block known injection patterns
        patterns = [
            r"(?i)ignore\s+(previous|above|all)\s+(instructions?|prompts?)",
            r"(?i)you\s+are\s+now\s+",
            r"(?i)system\s*:\s*",
            r"(?i)\[INST\]|\[\/INST\]|<\|im_start\|>",
        ]
        for pattern in patterns:
            if re.search(pattern, v):
                raise ValueError("Input contains disallowed patterns")
        return v

# Usage with FastAPI
@app.post("/api/chat")
async def chat(request: LLMRequest):
    # Separate system instructions from user input
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"<user_input>{request.user_message}</user_input>"},
    ]
    return await call_llm(messages, request.max_tokens, request.temperature)
OWASP References

Related: LLM01: Prompt Injection, LLM02: Insecure Output Handling, ASI02: Prompt Injection via Tool Results

🔒 Outros cabeçalhos de segurança recomendados

HTTP Response HeadersConfigurações recomendadas
# Desativar a detecção automática de tipo de conteúdo
X-Content-Type-Options: nosniff

# Impedir a incorporação de iframe
X-Frame-Options: DENY

# Aplicar HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains

# CSP: como as APIs retornam apenas JSON, bloqueie toda a execução de scripts
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'

# Restringir informações do referenciador
Referrer-Policy: no-referrer

# Restringir os recursos do navegador
Permissions-Policy: geolocation=(), camera=(), microphone=()