Skip to main content

Django middleware for bot detection and composite risk scoring

Project description

django-risk-guardian

License: MIT Python 3.11+ Django 4.2+ CI

Middleware Django reutilizável para detecção de bots e usuários maliciosos via score de risco composto.

Reusable Django middleware for bot detection and malicious user scoring through composite risk assessment.


Português | English


Português

O que é

Um middleware que analisa cada requisição HTTP e atribui um score de risco (0–100) baseado em múltiplos sinais: taxa de requisições, user-agent, sessão, padrões de navegação e timing. Sinais fracos isolados não bloqueiam, mas combinados identificam bots sem falsos positivos.

Por que não apenas rate limiting? Sinais isolados geram falsos positivos. Um IP com rate médio + UA desatualizado + sem sessão em path autenticado é mais suspeito do que qualquer um desses sozinho. O score composto captura isso.

Instalação

# pyproject.toml
[tool.poetry.dependencies]
django-risk-guardian = { git = "https://github.com/mupisystems/django-risk-guardian" }
# settings.py (3 linhas)
INSTALLED_APPS += ["risk_guardian"]

MIDDLEWARE = [
    "risk_guardian.middleware.RiskGuardianMiddleware",
    # ... demais middlewares
]

Requisitos

  • Python 3.11+
  • Django 4.2+
  • Redis (via django-redis)

Configuração

Todos os parâmetros têm defaults funcionais. Sobrescreva apenas o necessário:

RISK_GUARDIAN = {
    # Comportamento geral
    "ENABLED": True,
    "CACHE_BACKEND": "default",           # backend do Django cache (deve ser Redis)
    "CACHE_PREFIX": "rg",
    "LOG_ALL_SCORES": False,

    # Thresholds (0–100)
    "SCORE_THRESHOLD_BLOCK": 80,          # bloqueia a requisição
    "SCORE_THRESHOLD_CHALLENGE": 50,      # sinaliza para a view (ex: exigir 2FA)

    # Bloqueio
    "BLOCK_RESPONSE_CODE": 429,
    "BLOCK_TTL_SECONDS": 3600,

    # Histórico deslizante
    "HISTORY_WINDOW_SECONDS": 300,        # janela de 5 min
    "HISTORY_MAX_REQUESTS": 100,

    # Paths ignorados
    "IGNORE_PATHS": ["/health/", "/metrics/", "/__debug__/", "/favicon.ico"],

    # Analyzers ativos (ordem importa)
    "ANALYZERS": [
        "risk_guardian.analyzers.RateAnalyzer",
        "risk_guardian.analyzers.UserAgentAnalyzer",
        "risk_guardian.analyzers.SessionAnalyzer",
        "risk_guardian.analyzers.PatternAnalyzer",
        "risk_guardian.analyzers.TimingAnalyzer",
    ],
}

Uso nas views

Após o middleware processar, toda view tem acesso a request.risk:

def minha_view(request):
    if request.risk.challenged:
        return redirect("verificacao_2fa")

    print(request.risk.score)      # int (0–100)
    print(request.risk.reasons)    # ["high_rate", "outdated_browser"]
    print(request.risk.blocked)    # bool

Decorators

from risk_guardian.decorators import require_risk_below, require_no_challenge

@require_risk_below(50)
def endpoint_sensivel(request):
    ...

@require_no_challenge
def area_restrita(request):
    ...

Signals

from django.dispatch import receiver
from risk_guardian.signals import ip_blocked

@receiver(ip_blocked)
def notificar_bloqueio(sender, ip, score, reasons, **kwargs):
    SlackNotifier.send(f"IP bloqueado: {ip} (score={score})")

Signals disponíveis: ip_blocked, risk_assessed, challenge_required.

Analyzers

Analyzer Detecta Score máximo Reasons emitidos
RateAnalyzer Volume anormal de requisições por IP +50 critical_rate, high_rate, medium_rate
UserAgentAnalyzer UAs de bots, browsers desatualizados, UA vazio +40 bot_ua:curl, missing_ua, outdated_browser
SessionAnalyzer Sessão ausente, rotação de UA, sessões excessivas por IP +35 no_session_on_auth_path, session_ua_rotation, excessive_sessions_per_ip
PatternAnalyzer Paths de scan (.env, wp-admin), taxa de erro alta, diversidade de paths +60 scan_attempt:/.env, high_error_rate, excessive_path_diversity
TimingAnalyzer Intervalos artificialmente regulares entre requisições +30 robotic_timing

Logs estruturados

O middleware emite JSON estruturado via logger risk_guardian:

{
  "event": "ip_blocked",
  "ip": "1.2.3.4",
  "score": 85,
  "reasons": ["high_rate", "missing_ua"],
  "request_id": "abc-123"
}

Eventos emitidos: risk_assessed, ip_blocked, challenge_required, analyzer_error.

Testes

pip install pytest pytest-django fakeredis
pytest tests/ -v

English

What is it

A middleware that analyzes each HTTP request and assigns a risk score (0–100) based on multiple signals: request rate, user-agent, session, navigation patterns, and timing. Weak signals alone don't block, but combined they identify bots without false positives.

Why not just rate limiting? Isolated signals produce false positives. An IP with medium rate + outdated UA + no session on an authenticated path is far more suspicious than any single signal alone. Composite scoring captures that.

Installation

# pyproject.toml
[tool.poetry.dependencies]
django-risk-guardian = { git = "https://github.com/mupisystems/django-risk-guardian" }
# settings.py (3 lines)
INSTALLED_APPS += ["risk_guardian"]

MIDDLEWARE = [
    "risk_guardian.middleware.RiskGuardianMiddleware",
    # ... other middlewares
]

Requirements

  • Python 3.11+
  • Django 4.2+
  • Redis (via django-redis)

Configuration

All parameters have functional defaults. Override only what you need:

RISK_GUARDIAN = {
    # General behavior
    "ENABLED": True,
    "CACHE_BACKEND": "default",           # Django cache backend (should be Redis)
    "CACHE_PREFIX": "rg",
    "LOG_ALL_SCORES": False,

    # Thresholds (0–100)
    "SCORE_THRESHOLD_BLOCK": 80,          # blocks the request
    "SCORE_THRESHOLD_CHALLENGE": 50,      # flags for the view (e.g., require 2FA)

    # Blocking
    "BLOCK_RESPONSE_CODE": 429,
    "BLOCK_TTL_SECONDS": 3600,

    # Sliding history
    "HISTORY_WINDOW_SECONDS": 300,        # 5-minute window
    "HISTORY_MAX_REQUESTS": 100,

    # Ignored paths
    "IGNORE_PATHS": ["/health/", "/metrics/", "/__debug__/", "/favicon.ico"],

    # Active analyzers (order matters)
    "ANALYZERS": [
        "risk_guardian.analyzers.RateAnalyzer",
        "risk_guardian.analyzers.UserAgentAnalyzer",
        "risk_guardian.analyzers.SessionAnalyzer",
        "risk_guardian.analyzers.PatternAnalyzer",
        "risk_guardian.analyzers.TimingAnalyzer",
    ],
}

Usage in views

After the middleware processes a request, every view has access to request.risk:

def my_view(request):
    if request.risk.challenged:
        return redirect("2fa_verification")

    print(request.risk.score)      # int (0–100)
    print(request.risk.reasons)    # ["high_rate", "outdated_browser"]
    print(request.risk.blocked)    # bool

Decorators

from risk_guardian.decorators import require_risk_below, require_no_challenge

@require_risk_below(50)
def sensitive_endpoint(request):
    ...

@require_no_challenge
def restricted_area(request):
    ...

Signals

from django.dispatch import receiver
from risk_guardian.signals import ip_blocked

@receiver(ip_blocked)
def notify_block(sender, ip, score, reasons, **kwargs):
    SlackNotifier.send(f"IP blocked: {ip} (score={score})")

Available signals: ip_blocked, risk_assessed, challenge_required.

Analyzers

Analyzer Detects Max score Emitted reasons
RateAnalyzer Abnormal request volume per IP +50 critical_rate, high_rate, medium_rate
UserAgentAnalyzer Bot UAs, outdated browsers, missing UA +40 bot_ua:curl, missing_ua, outdated_browser
SessionAnalyzer Missing session, UA rotation, excessive sessions per IP +35 no_session_on_auth_path, session_ua_rotation, excessive_sessions_per_ip
PatternAnalyzer Scan paths (.env, wp-admin), high error rate, path diversity +60 scan_attempt:/.env, high_error_rate, excessive_path_diversity
TimingAnalyzer Artificially regular intervals between requests +30 robotic_timing

Structured logs

The middleware emits structured JSON via the risk_guardian logger:

{
  "event": "ip_blocked",
  "ip": "1.2.3.4",
  "score": 85,
  "reasons": ["high_rate", "missing_ua"],
  "request_id": "abc-123"
}

Emitted events: risk_assessed, ip_blocked, challenge_required, analyzer_error.

Tests

pip install pytest pytest-django fakeredis
pytest tests/ -v

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_risk_guardian-0.1.0.tar.gz (18.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_risk_guardian-0.1.0-py3-none-any.whl (18.1 kB view details)

Uploaded Python 3

File details

Details for the file django_risk_guardian-0.1.0.tar.gz.

File metadata

  • Download URL: django_risk_guardian-0.1.0.tar.gz
  • Upload date:
  • Size: 18.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for django_risk_guardian-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ceaa10c6a8d6a8285d344b0d8d34b4da9317e170f46e4c79f519698d32025ba8
MD5 36594a14971acf97c5d633c21d26ca7b
BLAKE2b-256 49ac3aed2c264dafc52ec50eb7521db1374d1418be242651d68a724786e420e1

See more details on using hashes here.

File details

Details for the file django_risk_guardian-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_risk_guardian-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 89aeff08e8913998253932bd2871d0cab9baaf00c04af46f06adfe9fc17704d8
MD5 cda8527ab24c97eb2b8320999cba5020
BLAKE2b-256 72a364ac5dcde3fb928ad3b17243458f7f2e6642386f14b626234f3761425095

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page