NAI Security
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
- Rate Limiting - Logs django-ratelimit hits;
RateLimitRuleis 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 all optional dependencies:
pip install nai-security[all]
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.SecurityMiddlewaremust be placed afterdjango.contrib.auth.middleware.AuthenticationMiddleware. The package validates this at startup and raisesImproperlyConfiguredif 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 >= 4.2
- geoip2 >= 5.0, < 6
- redis >= 5.0, < 9
- requests >= 2.28
Optional:
django-axes >= 8.3.1, < 9— login attempt tracking and lockout; without it axes features are silently disableddjango-ratelimit >= 4.1— rate limiting per endpointdjango-import-export >= 4.0, < 5— admin import/export for blocked emails/domains; without it those buttons are hiddendjango-unfold >= 0.90— admin UI theme; without it falls back to standard Django admin (0.100+ needs Python >= 3.12)celery— background tasks (auto-block processing, sync, reports); without it tasks are no-ops
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, orX-Forwarded-For/X-Real-IPwhenNAI_SECURITY_TRUST_PROXY_HEADERS=True) appears inWhitelistedIP— bypass works even when the request has no credentials (e.g.GET /login/). - The login username/email resolves to a user with an active
WhitelistedUserrow — regardless ofexemption_type. Theexemption_typefield 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 toemail__iexactautomatically.
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
WhitelistedUserrow exempts the user from django-axes lockout, regardless ofexemption_type. Theexemption_typefield controls only theSecurityMiddlewarechecks (IP/country/rate-limit). See Axes Integration → Whitelist bypass.
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_typenow clears an active axes lockout. - Default bad-bot sync no longer blocks
python-requests/curl/wget/Go-http-client. GEOIP_PATHaccepts a directory or the.mmdbfile. Classifiers add Django 6.1 and Python 3.14.
Upgrading to 1.11.0
Dependency pin updates (install-time):
- Declared
requests>=2.28as 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-axesremains>=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:
WhitelistedIPwas not consulted by the axes handler — whitelisted IPs could still be locked, especially withAXES_LOCKOUT_PARAMETERS=['ip_address'].- The handler's whitelist check was hard-coded to
exemption_type='all'— users with'ip_block','rate_limit', or'geo_block'were still locked. - User lookup used
USERNAME_FIELDonly and silently failed when the login form posts an email butUSERNAME_FIELD='username'.
- All three are fixed in
nai_security.handlers.axes_integration.DynamicAxesHandlervia a unified_is_request_whitelisted()helper. Failed login attempts from whitelisted requests are no longer recorded inAccessAttempt. - Verified end-to-end with 100x failed-login smoke test (
scripts/smoke_100x_lockout.py) and 9 regression tests intests/test_axes_integration.py.
Upgrading to 1.9.1
Breaking changes:
SecurityMiddlewarenow requiresAuthenticationMiddlewareto be placed before it inMIDDLEWARE. If misordered, the app raisesImproperlyConfiguredat startup. Previously the middleware silently failed to resolve users.NAI_SECURITY_USER_RESOLVERsetting has been removed. User resolution now usesrequest.userdirectly (guaranteed by middleware ordering).parse_user_agentnow correctly detects Android, iOS, and Opera (previously misidentified as Linux, macOS, and Chrome respectively).
Testing
python -m pytest
License
MIT License
Author
Ali Nemati - NEMATI AI
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nai_security-1.12.2.tar.gz.
File metadata
- Download URL: nai_security-1.12.2.tar.gz
- Upload date:
- Size: 53.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d3a5419928bc43fcac7719629236bd79bb0632e091b9f46c3f853b3acd76b87
|
|
| MD5 |
e44720a938b0575126220cc224777b44
|
|
| BLAKE2b-256 |
dd51b97e3ca25f85298d2d225c13c53d62d16347043149c424f6c0a40cad3b17
|
File details
Details for the file nai_security-1.12.2-py3-none-any.whl.
File metadata
- Download URL: nai_security-1.12.2-py3-none-any.whl
- Upload date:
- Size: 49.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84b3e7511df736d899a64fcca28b46b6eee242a9c481efca8d0b631b0cb6277f
|
|
| MD5 |
b7d4f12b0d1935a8abe55cb8d74596fe
|
|
| BLAKE2b-256 |
fc6248fa8083a7516d7845b00a01da32bdc573901517e05bcb0d60a32082a79c
|