Skip to main content

NAI Security

PyPI version Django Packages

Django security package for IP blocking, country blocking, email blocking, rate limiting, and login tracking.

Docs: https://nematiai.github.io/nai-security/
Wiki: https://github.com/nematiai/nai-security/wiki
Wiki source in repo: wiki/
NEMATI AI: https://nemati.ai

Features

  • IP Blocking - Block specific IPs manually or automatically
  • Country Blocking - Block/allow countries using GeoIP
  • Email Blocking - Helpers + admin lists for signup/login in your app (not request middleware)
  • Domain Blocking - Helpers + admin lists for disposable/spam domains
  • User Agent Blocking - Block bots, scrapers, attack tools
  • Header Stripping - Remove Server, X-Powered-By and similar so scanners cannot fingerprint the stack
  • Deploy Checks - manage.py check --deploy flags URL-surface exposure Django's own checks miss (dangerous routes, default admin prefix, Django-served static)
  • Path Blocking - Reject scanner probes for dotfiles (/.git, /.env, /.ssh), leak paths (/server-status, /phpinfo) and leftover files by extension (.bak, .sql, .log, .php), keeping /.well-known/ reachable
  • Rate Limiting - Logs django-ratelimit hits; RateLimitRule is storage only (not an engine)
  • Login History - Track user logins with anomaly detection
  • Auto-Blocking - Automatically block IPs/countries based on attack patterns
  • Security Logs - Comprehensive logging of all security events
  • Axes Integration - Dynamic login attempt limits, cooloff time, and per-attempt expiry via admin panel (requires django-axes >= 8.3)
  • Whitelisted Users - Exempt specific users from security checks

Installation

pip install nai-security

With Django integration extras (axes, ratelimit, import-export, unfold):

pip install nai-security[all]

Each extra also needs its app in INSTALLED_APPS to become active ("axes", "unfold", "import_export"). Installing an extra without listing its app is safe — the feature is just off.

Background tasks extra (not included in [all]):

pip install nai-security[celery]

Or install from GitHub:

pip install git+https://github.com/nematiai/nai-security.git

Quick Start

1. Add to INSTALLED_APPS

INSTALLED_APPS = [
    ...
    "nai_security",
]

2. Add Middleware

Important: nai_security.middleware.SecurityMiddleware must be placed after django.contrib.auth.middleware.AuthenticationMiddleware. The package validates this at startup and raises ImproperlyConfigured if misordered.

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",  # must be before
    "django.contrib.messages.middleware.MessageMiddleware",
    "nai_security.middleware.SecurityMiddleware",               # after auth
    "nai_security.middleware.RateLimitLoggingMiddleware",       # optional
]

3. Configure Settings

GEOIP_PATH = "/path/to/GeoLite2-Country.mmdb"

# Optional: override default exempt paths (default: /health/, /ready/, /favicon.ico)
NAI_SECURITY_EXEMPT_PATHS = ["/health/", "/health", "/ready/", "/ready", "/favicon.ico"]

# Required if you sit behind a reverse proxy. Default False: ignore X-Forwarded-For / X-Real-IP
# so clients cannot spoof their IP.
NAI_SECURITY_TRUST_PROXY_HEADERS = True

4. Run Migrations

python manage.py migrate

5. Download GeoIP Database

python manage.py download_geoip

Dependencies

Required:

  • Django >= 5.2
  • geoip2 >= 5.0, < 6
  • redis >= 5.0, < 9
  • requests >= 2.32.4

Optional:

  • django-axes >= 8.3.1, < 9 — login attempt tracking and lockout; without it axes features are silently disabled
  • django-ratelimit >= 4.1 — rate limiting per endpoint
  • django-import-export >= 4.0, < 5 — admin import/export for blocked emails/domains; without it those buttons are hidden
  • django-unfold >= 0.90 — admin UI theme; without it falls back to standard Django admin (0.100+ needs Python >= 3.12)
  • celery >= 5.3, < 6 — background tasks (auto-block processing, sync, reports); without it tasks are no-ops (pip install nai-security[celery])

Axes Integration

Enable brute-force protection with dynamic settings controlled from the admin panel:

# settings.py
INSTALLED_APPS = [
    ...
    "axes",
    "nai_security",
]

AXES_HANDLER = 'nai_security.handlers.axes_integration.DynamicAxesHandler'

AUTHENTICATION_BACKENDS = [
    'axes.backends.AxesStandaloneBackend',
    'django.contrib.auth.backends.ModelBackend',
]

This gives you admin-configurable control over:

Setting Description
Max login attempts Failed attempts before lockout (default: 5)
Cooloff time Minutes before locked accounts auto-unlock (0 = permanent)
Attempt expiry Each failed attempt expires independently — requires cooloff > 0

All changes take effect immediately — no server restart required.

Validation: Enabling attempt expiry with cooloff set to 0 will raise a validation error in the admin panel.

Whitelist bypass

DynamicAxesHandler short-circuits all axes checks (is_allowed, is_locked, user_login_failed) when the request matches any active whitelist:

  • The client IP (REMOTE_ADDR, or X-Forwarded-For / X-Real-IP when NAI_SECURITY_TRUST_PROXY_HEADERS=True) appears in WhitelistedIP — bypass works even when the request has no credentials (e.g. GET /login/).
  • The login username/email resolves to a user with an active WhitelistedUser row — regardless of exemption_type. The exemption_type field still controls middleware-level bypasses (IP, country, rate-limit); for axes lockout the rule is binary.
  • Username lookup tolerates USERNAME_FIELD='username' deployments where the login form posts an email — falls back to email__iexact automatically.

When a whitelisted request fails authentication, no AccessAttempt row is recorded — the table stays clean for whitelisted admins.

Management Commands

# Download GeoIP database
python manage.py download_geoip

# Sync disposable email domains and bad bot lists
python manage.py sync_security_lists
python manage.py sync_security_lists --domains-only
python manage.py sync_security_lists --bots-only

Celery Tasks

from celery.schedules import crontab

CELERY_BEAT_SCHEDULE = {
    'security-auto-blocks': {
        'task': 'security.process_auto_blocks',
        'schedule': crontab(minute='*/5'),
    },
    'security-cleanup-expired': {
        'task': 'security.cleanup_expired_blocks',
        'schedule': crontab(minute=0, hour='*'),
    },
    'security-sync-lists': {
        'task': 'security.sync_security_lists',
        'schedule': crontab(minute=0, hour=0, day_of_week=0),
    },
    'security-daily-report': {
        'task': 'security.generate_security_report',
        'schedule': crontab(minute=0, hour=6),
    },
}

Models

Model Description
BlockedIP Blocked IP addresses
BlockedCountry Blocked countries
AllowedCountry Allowed countries (whitelist mode)
BlockedEmail Blocked email addresses (helpers for signup/login in your app — not request middleware)
BlockedDomain Blocked email domains (helpers for signup/login in your app — not request middleware)
BlockedUserAgent Blocked user agents
WhitelistedIP IPs that bypass all checks
WhitelistedUser Users exempted from security checks (see exemption types below)
RateLimitRule Storage for custom rate rules — this package does not enforce them (use django-ratelimit)
LoginHistory User login tracking
SecurityLog Security event logs
SecuritySettings Global settings (singleton)

User Exemptions (WhitelistedUser)

Exempt specific users from security checks via the admin panel or ORM:

Exemption Type Bypasses
all Entire security middleware — IP, country, user-agent
ip_block IP blocking only
geo_block Country/geo blocking only
rate_limit Rate limit logging only

Exemptions support optional expiration (expires_at) and can be toggled via is_active.

Axes lockout note: any active WhitelistedUser row exempts the user from django-axes lockout, regardless of exemption_type. The exemption_type field controls only the SecurityMiddleware checks (IP/country/rate-limit). See Axes Integration → Whitelist bypass.

Upgrading to 1.17.0

Path blocking now also matches file extensions — backup/editor leftovers, database dumps, key material, and scripts Django never executes. Root-level archives too. Behaviour change: if a real route ends in one of those, add it to NAI_SECURITY_EXEMPT_PATHS.

Upgrading to 1.16.0

Adds ResponseHeaderMiddleware, which strips stack-identifying response headers, and check nai_security.W004, which warns when it is not installed. Opt-in — add it to MIDDLEWARE (first, so it sees the final response). No migration, no API change.

Upgrading to 1.15.0

Adds three deploy-tagged system checks (nai_security.W001–W003) that inspect the URLconf for exposure Django's settings-only checks miss. Run manage.py check --deploy. No migration, no API change, no request-time behaviour change.

Upgrading to 1.14.1

Bug-fix only. Two startup crashes on installs without the full extra set: a duplicate-base TypeError when django-import-export is absent, and a RuntimeError when django-axes is installed but not in INSTALLED_APPS. No API change, no migration.

Upgrading to 1.14.0

New Path Blocking check (two migrations). Run python manage.py migrate — 0006 adds SecuritySettings.path_blocking_enabled, default on. Dotfile paths (/.git, /.env, /.ssh, …) and /server-status, /server-info, /phpinfo, /wp-config.php, /web.config, /id_rsa now return 403 and log a PATH_BLOCK event; 0007 adds PATH_BLOCK to the SecurityLog.action choices. /.well-known/ stays reachable (ACME renewal is unaffected). Disable it in admin → Security Settings.

Upgrading to 1.13.0

Install-time / support matrix (no app API change):

  • Required Django floor is now 5.2 (4.2 and 5.0 are past end of support).
  • Required requests floor is now 2.32.4.
  • New optional extra: pip install nai-security[celery] (celery>=5.3,<6). [all] is unchanged (axes, ratelimit, import-export, unfold).
  • Dev extra adds pytest-cov, hypothesis, time-machine, responses, fakeredis, model-bakery, mypy, and pip-audit.

Upgrading to 1.12.2

Metadata only: NEMATI AI links (https://nemati.ai) on PyPI, docs, and wiki. No app API change.

Upgrading to 1.12.1

Docs links: PyPI Documentation now opens https://nematiai.github.io/nai-security/ (no app API change).

Upgrading to 1.12.0

Security / behavior fix (no migrations):

  • If you run behind a reverse proxy, set NAI_SECURITY_TRUST_PROXY_HEADERS = True. Forwarded headers are ignored by default so clients cannot spoof IP to bypass blocks.
  • Axes cooloff and attempt-expiry now apply on the next request in every worker (not only the process that saved admin).
  • Whitelisting a user with any exemption_type now clears an active axes lockout.
  • Default bad-bot sync no longer blocks python-requests / curl / wget / Go-http-client.
  • GEOIP_PATH accepts a directory or the .mmdb file. Classifiers add Django 6.1 and Python 3.14.

Upgrading to 1.11.0

Dependency pin updates (install-time):

  • Declared requests>=2.28 as a required dependency (used by sync services).
  • Raised/capped floors: geoip2>=5.0,<6, redis>=5.0,<9.
  • Optional extras: django-import-export>=4.0,<5, django-unfold>=0.90, django-ratelimit>=4.1.
  • django-axes remains >=8.3.1,<9.0.
  • No application API changes in this release.

Upgrading to 1.10.1

Bug fixes (no breaking changes):

  • Whitelisted users were still being locked out by django-axes. Three independent paths caused this:
    1. WhitelistedIP was not consulted by the axes handler — whitelisted IPs could still be locked, especially with AXES_LOCKOUT_PARAMETERS=['ip_address'].
    2. The handler's whitelist check was hard-coded to exemption_type='all' — users with 'ip_block', 'rate_limit', or 'geo_block' were still locked.
    3. User lookup used USERNAME_FIELD only and silently failed when the login form posts an email but USERNAME_FIELD='username'.
  • All three are fixed in nai_security.handlers.axes_integration.DynamicAxesHandler via a unified _is_request_whitelisted() helper. Failed login attempts from whitelisted requests are no longer recorded in AccessAttempt.
  • Verified end-to-end with 100x failed-login smoke test (scripts/smoke_100x_lockout.py) and 9 regression tests in tests/test_axes_integration.py.

Upgrading to 1.9.1

Breaking changes:

  • SecurityMiddleware now requires AuthenticationMiddleware to be placed before it in MIDDLEWARE. If misordered, the app raises ImproperlyConfigured at startup. Previously the middleware silently failed to resolve users.
  • NAI_SECURITY_USER_RESOLVER setting has been removed. User resolution now uses request.user directly (guaranteed by middleware ordering).
  • parse_user_agent now correctly detects Android, iOS, and Opera (previously misidentified as Linux, macOS, and Chrome respectively).

Testing

pip install -e ".[dev,all]"
python -m pytest
python -m pytest --cov=nai_security --cov-report=term-missing
pip-audit

License

MIT License

Author

Ali Nemati - NEMATI AI

Metadata

Release files for nai-security 1.17.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nai-security 1.17.0
File Size Uploaded
nai_security-1.17.0.tar.gz 65.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nai-security 1.17.0
File Interpreter ABI Platform
nai_security-1.17.0-py3-none-any.whl Python 3 none any Details

Total release size: 121.0 kB

Release files / nai_security-1.17.0.tar.gz

Download URL nai_security-1.17.0.tar.gz
Size 65.8 kB
Tags Source
SHA-256 checksum
How to use checksums
6dbf0c25a7d0b21edc0977a7701f471d5b348c6fed2bcf7452eefa002f1172e2
BLAKE2b-256 checksum
How to use checksums
dd9ca05f41cb5da315aced6627943059d9943234fd364a7aa5d33408d17b7651
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / nai_security-1.17.0-py3-none-any.whl

Download URL nai_security-1.17.0-py3-none-any.whl
Size 55.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fca931da0dbeadf853477c503b8479e6d10d5962d1bd5af9142cf7a55bbe5a2c
BLAKE2b-256 checksum
How to use checksums
eab6269ccf72dc1c4883d8066bb4fd7b0130eaf3468e53a96fb460ee5e5f73ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release history Release notifications | RSS feed

This release

1.17.0 This release

2 release files

1.14.1

2 release files

1.14.0

2 release files

1.13.0

2 release files

1.12.2

2 release files

1.12.1

2 release files

1.12.0

2 release files

1.9.2

2 release files

1.9.1

2 release files

1.9.0

2 release files

1.8.1

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.2

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page