aiwaf 0.1.7.7__tar.gz → 0.1.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiwaf might be problematic. Click here for more details.

Files changed (27) hide show
  1. {aiwaf-0.1.7.7/aiwaf.egg-info → aiwaf-0.1.8.1}/PKG-INFO +54 -27
  2. aiwaf-0.1.7.7/PKG-INFO → aiwaf-0.1.8.1/README.md +48 -42
  3. aiwaf-0.1.8.1/aiwaf/management/commands/add_ipexemption.py +22 -0
  4. aiwaf-0.1.8.1/aiwaf/management/commands/aiwaf_reset.py +84 -0
  5. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/middleware.py +48 -16
  6. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/models.py +11 -1
  7. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/trainer.py +6 -0
  8. aiwaf-0.1.7.7/README.md → aiwaf-0.1.8.1/aiwaf.egg-info/PKG-INFO +69 -26
  9. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf.egg-info/SOURCES.txt +3 -0
  10. aiwaf-0.1.8.1/aiwaf.egg-info/requires.txt +5 -0
  11. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/pyproject.toml +8 -2
  12. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/setup.py +1 -1
  13. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/LICENSE +0 -0
  14. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/__init__.py +0 -0
  15. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/apps.py +0 -0
  16. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/blacklist_manager.py +0 -0
  17. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/management/__init__.py +0 -0
  18. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/management/commands/__init__.py +0 -0
  19. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/management/commands/detect_and_train.py +0 -0
  20. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/resources/model.pkl +0 -0
  21. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/storage.py +0 -0
  22. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/templatetags/__init__.py +0 -0
  23. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/templatetags/aiwaf_tags.py +0 -0
  24. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/utils.py +0 -0
  25. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf.egg-info/dependency_links.txt +0 -0
  26. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf.egg-info/top_level.txt +0 -0
  27. {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiwaf
3
- Version: 0.1.7.7
3
+ Version: 0.1.8.1
4
4
  Summary: AI-powered Web Application Firewall
5
5
  Home-page: https://github.com/aayushgauba/aiwaf
6
6
  Author: Aayush Gauba
@@ -9,6 +9,11 @@ License: MIT
9
9
  Requires-Python: >=3.8
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
+ Requires-Dist: Django>=3.2
13
+ Requires-Dist: numpy>=1.21
14
+ Requires-Dist: pandas>=1.3
15
+ Requires-Dist: scikit-learn>=1.0
16
+ Requires-Dist: joblib>=1.1
12
17
  Dynamic: author
13
18
  Dynamic: home-page
14
19
  Dynamic: license-file
@@ -72,19 +77,58 @@ aiwaf/
72
77
  - **File‑Extension Probing Detection**
73
78
  Tracks repeated 404s on common extensions (e.g. `.php`, `.asp`) and blocks IPs.
74
79
 
75
- - **Honeypot Field**
76
- Hidden field for bot detection IP blacklisted on fill.
80
+ - **Timing-Based Honeypot**
81
+ Tracks GET→POST timing patterns. Blocks IPs that:
82
+ - POST directly without a preceding GET request
83
+ - Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
77
84
 
78
85
  - **UUID Tampering Protection**
79
86
  Blocks guessed or invalid UUIDs that don’t resolve to real models.
80
87
 
81
- - **Exempt Path Awareness**
82
- Fully respects `AIWAF_EXEMPT_PATHS` across all modules — exempt paths are:
88
+
89
+ **Exempt Path & IP Awareness**
90
+
91
+ **Exempt Paths:**
92
+ Set `AIWAF_EXEMPT_PATHS` in your Django `settings.py` (not in your code). Fully respects this setting across all modules — exempt paths are:
83
93
  - Skipped from keyword learning
84
94
  - Immune to AI blocking
85
95
  - Ignored in log training
86
96
  - Cleaned from `DynamicKeyword` model automatically
87
97
 
98
+ **Exempt IPs:**
99
+ You can exempt specific IP addresses from all blocking and blacklisting logic. Exempted IPs will:
100
+ - Never be added to the blacklist (even if they trigger rules)
101
+ - Be automatically removed from the blacklist during retraining
102
+ - Bypass all block/deny logic in middleware
103
+
104
+ ### Managing Exempt IPs
105
+
106
+ Add an IP to the exemption list using the management command:
107
+
108
+ ```bash
109
+ python manage.py add_ipexemption <ip-address> --reason "optional reason"
110
+ ```
111
+
112
+ ### Resetting AI-WAF
113
+
114
+ Clear all blacklist and exemption entries:
115
+
116
+ ```bash
117
+ # Clear everything (with confirmation prompt)
118
+ python manage.py aiwaf_reset
119
+
120
+ # Clear everything without confirmation
121
+ python manage.py aiwaf_reset --confirm
122
+
123
+ # Clear only blacklist entries
124
+ python manage.py aiwaf_reset --blacklist-only
125
+
126
+ # Clear only exemption entries
127
+ python manage.py aiwaf_reset --exemptions-only
128
+ ```
129
+
130
+ This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
131
+
88
132
  - **Daily Retraining**
89
133
  Reads rotated logs, auto‑blocks 404 floods, retrains the IsolationForest, updates `model.pkl`, and evolves the keyword DB.
90
134
 
@@ -119,13 +163,13 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
119
163
 
120
164
  ```python
121
165
  AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
122
- AIWAF_HONEYPOT_FIELD = "hp_field"
166
+ AIWAF_MIN_FORM_TIME = 1.0 # minimum seconds between GET and POST
123
167
  AIWAF_RATE_WINDOW = 10 # seconds
124
168
  AIWAF_RATE_MAX = 20 # max requests per window
125
169
  AIWAF_RATE_FLOOD = 10 # flood threshold
126
170
  AIWAF_WINDOW_SECONDS = 60 # anomaly detection window
127
171
  AIWAF_FILE_EXTENSIONS = [".php", ".asp", ".jsp"]
128
- AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
172
+ AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
129
173
  "/favicon.ico",
130
174
  "/robots.txt",
131
175
  "/static/",
@@ -147,7 +191,7 @@ MIDDLEWARE = [
147
191
  "aiwaf.middleware.IPAndKeywordBlockMiddleware",
148
192
  "aiwaf.middleware.RateLimitMiddleware",
149
193
  "aiwaf.middleware.AIAnomalyMiddleware",
150
- "aiwaf.middleware.HoneypotMiddleware",
194
+ "aiwaf.middleware.HoneypotTimingMiddleware",
151
195
  "aiwaf.middleware.UUIDTamperMiddleware",
152
196
  # ... other middleware ...
153
197
  ]
@@ -155,24 +199,7 @@ MIDDLEWARE = [
155
199
 
156
200
  ---
157
201
 
158
- ## 🕵️ Honeypot Field (in your template)
159
-
160
- ```django
161
- {% load aiwaf_tags %}
162
-
163
- <form method="post">
164
- {% csrf_token %}
165
- {% honeypot_field %}
166
- <!-- your real fields -->
167
- </form>
168
- ```
169
-
170
- > Renders a hidden `<input name="hp_field" style="display:none">`.
171
- > Any non‑empty submission → IP blacklisted.
172
-
173
- ---
174
-
175
- ## 🔁 Running Detection & Training
202
+ ## Running Detection & Training
176
203
 
177
204
  ```bash
178
205
  python manage.py detect_and_train
@@ -195,7 +222,7 @@ python manage.py detect_and_train
195
222
  | IPAndKeywordBlockMiddleware | Blocks requests from known blacklisted IPs and Keywords |
196
223
  | RateLimitMiddleware | Enforces burst & flood thresholds |
197
224
  | AIAnomalyMiddleware | ML‑driven behavior analysis + block on anomaly |
198
- | HoneypotMiddleware | Detects bots filling hidden inputs in forms |
225
+ | HoneypotTimingMiddleware | Detects bots via GET→POST timing analysis |
199
226
  | UUIDTamperMiddleware | Blocks guessed/nonexistent UUIDs across all models in an app |
200
227
 
201
228
  ---
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: aiwaf
3
- Version: 0.1.7.7
4
- Summary: AI-powered Web Application Firewall
5
- Home-page: https://github.com/aayushgauba/aiwaf
6
- Author: Aayush Gauba
7
- Author-email: Aayush Gauba <gauba.aayush@gmail.com>
8
- License: MIT
9
- Requires-Python: >=3.8
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Dynamic: author
13
- Dynamic: home-page
14
- Dynamic: license-file
15
- Dynamic: requires-python
16
-
17
1
 
18
2
  # AI‑WAF
19
3
 
@@ -72,19 +56,58 @@ aiwaf/
72
56
  - **File‑Extension Probing Detection**
73
57
  Tracks repeated 404s on common extensions (e.g. `.php`, `.asp`) and blocks IPs.
74
58
 
75
- - **Honeypot Field**
76
- Hidden field for bot detection IP blacklisted on fill.
59
+ - **Timing-Based Honeypot**
60
+ Tracks GET→POST timing patterns. Blocks IPs that:
61
+ - POST directly without a preceding GET request
62
+ - Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
77
63
 
78
64
  - **UUID Tampering Protection**
79
65
  Blocks guessed or invalid UUIDs that don’t resolve to real models.
80
66
 
81
- - **Exempt Path Awareness**
82
- Fully respects `AIWAF_EXEMPT_PATHS` across all modules — exempt paths are:
67
+
68
+ **Exempt Path & IP Awareness**
69
+
70
+ **Exempt Paths:**
71
+ Set `AIWAF_EXEMPT_PATHS` in your Django `settings.py` (not in your code). Fully respects this setting across all modules — exempt paths are:
83
72
  - Skipped from keyword learning
84
73
  - Immune to AI blocking
85
74
  - Ignored in log training
86
75
  - Cleaned from `DynamicKeyword` model automatically
87
76
 
77
+ **Exempt IPs:**
78
+ You can exempt specific IP addresses from all blocking and blacklisting logic. Exempted IPs will:
79
+ - Never be added to the blacklist (even if they trigger rules)
80
+ - Be automatically removed from the blacklist during retraining
81
+ - Bypass all block/deny logic in middleware
82
+
83
+ ### Managing Exempt IPs
84
+
85
+ Add an IP to the exemption list using the management command:
86
+
87
+ ```bash
88
+ python manage.py add_ipexemption <ip-address> --reason "optional reason"
89
+ ```
90
+
91
+ ### Resetting AI-WAF
92
+
93
+ Clear all blacklist and exemption entries:
94
+
95
+ ```bash
96
+ # Clear everything (with confirmation prompt)
97
+ python manage.py aiwaf_reset
98
+
99
+ # Clear everything without confirmation
100
+ python manage.py aiwaf_reset --confirm
101
+
102
+ # Clear only blacklist entries
103
+ python manage.py aiwaf_reset --blacklist-only
104
+
105
+ # Clear only exemption entries
106
+ python manage.py aiwaf_reset --exemptions-only
107
+ ```
108
+
109
+ This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
110
+
88
111
  - **Daily Retraining**
89
112
  Reads rotated logs, auto‑blocks 404 floods, retrains the IsolationForest, updates `model.pkl`, and evolves the keyword DB.
90
113
 
@@ -119,13 +142,13 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
119
142
 
120
143
  ```python
121
144
  AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
122
- AIWAF_HONEYPOT_FIELD = "hp_field"
145
+ AIWAF_MIN_FORM_TIME = 1.0 # minimum seconds between GET and POST
123
146
  AIWAF_RATE_WINDOW = 10 # seconds
124
147
  AIWAF_RATE_MAX = 20 # max requests per window
125
148
  AIWAF_RATE_FLOOD = 10 # flood threshold
126
149
  AIWAF_WINDOW_SECONDS = 60 # anomaly detection window
127
150
  AIWAF_FILE_EXTENSIONS = [".php", ".asp", ".jsp"]
128
- AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
151
+ AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
129
152
  "/favicon.ico",
130
153
  "/robots.txt",
131
154
  "/static/",
@@ -147,7 +170,7 @@ MIDDLEWARE = [
147
170
  "aiwaf.middleware.IPAndKeywordBlockMiddleware",
148
171
  "aiwaf.middleware.RateLimitMiddleware",
149
172
  "aiwaf.middleware.AIAnomalyMiddleware",
150
- "aiwaf.middleware.HoneypotMiddleware",
173
+ "aiwaf.middleware.HoneypotTimingMiddleware",
151
174
  "aiwaf.middleware.UUIDTamperMiddleware",
152
175
  # ... other middleware ...
153
176
  ]
@@ -155,24 +178,7 @@ MIDDLEWARE = [
155
178
 
156
179
  ---
157
180
 
158
- ## 🕵️ Honeypot Field (in your template)
159
-
160
- ```django
161
- {% load aiwaf_tags %}
162
-
163
- <form method="post">
164
- {% csrf_token %}
165
- {% honeypot_field %}
166
- <!-- your real fields -->
167
- </form>
168
- ```
169
-
170
- > Renders a hidden `<input name="hp_field" style="display:none">`.
171
- > Any non‑empty submission → IP blacklisted.
172
-
173
- ---
174
-
175
- ## 🔁 Running Detection & Training
181
+ ## Running Detection & Training
176
182
 
177
183
  ```bash
178
184
  python manage.py detect_and_train
@@ -195,7 +201,7 @@ python manage.py detect_and_train
195
201
  | IPAndKeywordBlockMiddleware | Blocks requests from known blacklisted IPs and Keywords |
196
202
  | RateLimitMiddleware | Enforces burst & flood thresholds |
197
203
  | AIAnomalyMiddleware | ML‑driven behavior analysis + block on anomaly |
198
- | HoneypotMiddleware | Detects bots filling hidden inputs in forms |
204
+ | HoneypotTimingMiddleware | Detects bots via GET→POST timing analysis |
199
205
  | UUIDTamperMiddleware | Blocks guessed/nonexistent UUIDs across all models in an app |
200
206
 
201
207
  ---
@@ -0,0 +1,22 @@
1
+ from django.core.management.base import BaseCommand, CommandError
2
+ from aiwaf.models import IPExemption
3
+
4
+ class Command(BaseCommand):
5
+ help = 'Add an IP address to the IPExemption list (prevents blacklisting)'
6
+
7
+ def add_arguments(self, parser):
8
+ parser.add_argument('ip', type=str, help='IP address to exempt')
9
+ parser.add_argument('--reason', type=str, default='', help='Reason for exemption (optional)')
10
+
11
+ def handle(self, *args, **options):
12
+ ip = options['ip']
13
+ reason = options['reason']
14
+ obj, created = IPExemption.objects.get_or_create(ip_address=ip, defaults={'reason': reason})
15
+ if not created:
16
+ self.stdout.write(self.style.WARNING(f'IP {ip} is already exempted.'))
17
+ else:
18
+ self.stdout.write(self.style.SUCCESS(f'IP {ip} added to exemption list.'))
19
+ if reason:
20
+ obj.reason = reason
21
+ obj.save()
22
+ self.stdout.write(self.style.SUCCESS(f'Reason set to: {reason}'))
@@ -0,0 +1,84 @@
1
+ from django.core.management.base import BaseCommand
2
+ from aiwaf.models import BlacklistEntry, IPExemption
3
+
4
+ class Command(BaseCommand):
5
+ help = 'Reset AI-WAF by clearing all blacklist and exemption (whitelist) entries'
6
+
7
+ def add_arguments(self, parser):
8
+ parser.add_argument(
9
+ '--blacklist-only',
10
+ action='store_true',
11
+ help='Clear only blacklist entries, keep exemptions'
12
+ )
13
+ parser.add_argument(
14
+ '--exemptions-only',
15
+ action='store_true',
16
+ help='Clear only exemption entries, keep blacklist'
17
+ )
18
+ parser.add_argument(
19
+ '--confirm',
20
+ action='store_true',
21
+ help='Skip confirmation prompt'
22
+ )
23
+
24
+ def handle(self, *args, **options):
25
+ blacklist_only = options['blacklist_only']
26
+ exemptions_only = options['exemptions_only']
27
+ confirm = options['confirm']
28
+
29
+ # Count current entries
30
+ blacklist_count = BlacklistEntry.objects.count()
31
+ exemption_count = IPExemption.objects.count()
32
+
33
+ if blacklist_only and exemptions_only:
34
+ self.stdout.write(self.style.ERROR('Cannot use both --blacklist-only and --exemptions-only flags'))
35
+ return
36
+
37
+ # Determine what to clear
38
+ if blacklist_only:
39
+ action = f"Clear {blacklist_count} blacklist entries"
40
+ clear_blacklist = True
41
+ clear_exemptions = False
42
+ elif exemptions_only:
43
+ action = f"Clear {exemption_count} exemption entries"
44
+ clear_blacklist = False
45
+ clear_exemptions = True
46
+ else:
47
+ action = f"Clear {blacklist_count} blacklist entries and {exemption_count} exemption entries"
48
+ clear_blacklist = True
49
+ clear_exemptions = True
50
+
51
+ # Show what will be cleared
52
+ self.stdout.write(f"AI-WAF Reset: {action}")
53
+
54
+ if not confirm:
55
+ response = input("Are you sure you want to proceed? [y/N]: ")
56
+ if response.lower() not in ['y', 'yes']:
57
+ self.stdout.write(self.style.WARNING('Operation cancelled'))
58
+ return
59
+
60
+ # Perform the reset
61
+ deleted_counts = {'blacklist': 0, 'exemptions': 0}
62
+
63
+ if clear_blacklist:
64
+ deleted_counts['blacklist'], _ = BlacklistEntry.objects.all().delete()
65
+
66
+ if clear_exemptions:
67
+ deleted_counts['exemptions'], _ = IPExemption.objects.all().delete()
68
+
69
+ # Report results
70
+ if clear_blacklist and clear_exemptions:
71
+ self.stdout.write(
72
+ self.style.SUCCESS(
73
+ f"✅ Reset complete: Deleted {deleted_counts['blacklist']} blacklist entries "
74
+ f"and {deleted_counts['exemptions']} exemption entries"
75
+ )
76
+ )
77
+ elif clear_blacklist:
78
+ self.stdout.write(
79
+ self.style.SUCCESS(f"✅ Blacklist cleared: Deleted {deleted_counts['blacklist']} entries")
80
+ )
81
+ elif clear_exemptions:
82
+ self.stdout.write(
83
+ self.style.SUCCESS(f"✅ Exemptions cleared: Deleted {deleted_counts['exemptions']} entries")
84
+ )
@@ -16,7 +16,9 @@ from django.apps import apps
16
16
  from django.urls import get_resolver
17
17
  from .trainer import STATIC_KW, STATUS_IDX, is_exempt_path, path_exists_in_django
18
18
  from .blacklist_manager import BlacklistManager
19
- from .models import DynamicKeyword
19
+ from .models import DynamicKeyword, IPExemption
20
+ def is_ip_exempted(ip):
21
+ return IPExemption.objects.filter(ip_address=ip).exists()
20
22
 
21
23
  def is_exempt_path(path):
22
24
  path = path.lower()
@@ -77,6 +79,8 @@ class IPAndKeywordBlockMiddleware:
77
79
  return self.get_response(request)
78
80
  ip = get_ip(request)
79
81
  path = raw_path.lstrip("/")
82
+ if is_ip_exempted(ip):
83
+ return self.get_response(request)
80
84
  if BlacklistManager.is_blocked(ip):
81
85
  return JsonResponse({"error": "blocked"}, status=403)
82
86
  segments = [seg for seg in re.split(r"\W+", path) if len(seg) > 3]
@@ -95,8 +99,9 @@ class IPAndKeywordBlockMiddleware:
95
99
  }
96
100
  for seg in segments:
97
101
  if seg in suspicious_kw:
98
- BlacklistManager.block(ip, f"Keyword block: {seg}")
99
- return JsonResponse({"error": "blocked"}, status=403)
102
+ if not is_ip_exempted(ip):
103
+ BlacklistManager.block(ip, f"Keyword block: {seg}")
104
+ return JsonResponse({"error": "blocked"}, status=403)
100
105
  return self.get_response(request)
101
106
 
102
107
 
@@ -120,8 +125,9 @@ class RateLimitMiddleware:
120
125
  timestamps.append(now)
121
126
  cache.set(key, timestamps, timeout=self.WINDOW)
122
127
  if len(timestamps) > self.FLOOD:
123
- BlacklistManager.block(ip, "Flood pattern")
124
- return JsonResponse({"error": "blocked"}, status=403)
128
+ if not is_ip_exempted(ip):
129
+ BlacklistManager.block(ip, "Flood pattern")
130
+ return JsonResponse({"error": "blocked"}, status=403)
125
131
  if len(timestamps) > self.MAX:
126
132
  return JsonResponse({"error": "too_many_requests"}, status=429)
127
133
  return self.get_response(request)
@@ -141,6 +147,8 @@ class AIAnomalyMiddleware(MiddlewareMixin):
141
147
  return None
142
148
  request._start_time = time.time()
143
149
  ip = get_ip(request)
150
+ if is_ip_exempted(ip):
151
+ return None
144
152
  if BlacklistManager.is_blocked(ip):
145
153
  return JsonResponse({"error": "blocked"}, status=403)
146
154
  return None
@@ -166,8 +174,9 @@ class AIAnomalyMiddleware(MiddlewareMixin):
166
174
  feats = [path_len, kw_hits, resp_time, status_idx, burst_count, total_404]
167
175
  X = np.array(feats, dtype=float).reshape(1, -1)
168
176
  if self.model.predict(X)[0] == -1:
169
- BlacklistManager.block(ip, "AI anomaly")
170
- return JsonResponse({"error": "blocked"}, status=403)
177
+ if not is_ip_exempted(ip):
178
+ BlacklistManager.block(ip, "AI anomaly")
179
+ return JsonResponse({"error": "blocked"}, status=403)
171
180
 
172
181
  data.append((now, request.path, response.status_code, resp_time))
173
182
  data = [d for d in data if now - d[0] < self.WINDOW]
@@ -180,15 +189,37 @@ class AIAnomalyMiddleware(MiddlewareMixin):
180
189
  return response
181
190
 
182
191
 
183
- class HoneypotMiddleware(MiddlewareMixin):
184
- def process_view(self, request, view_func, view_args, view_kwargs):
192
+ class HoneypotTimingMiddleware(MiddlewareMixin):
193
+ MIN_FORM_TIME = getattr(settings, "AIWAF_MIN_FORM_TIME", 1.0) # seconds
194
+
195
+ def process_request(self, request):
185
196
  if is_exempt_path(request.path):
186
197
  return None
187
- trap = request.POST.get(getattr(settings, "AIWAF_HONEYPOT_FIELD", "hp_field"), "")
188
- if trap:
189
- ip = get_ip(request)
190
- BlacklistManager.block(ip, "HONEYPOT triggered")
191
- return JsonResponse({"error": "bot_detected"}, status=403)
198
+
199
+ ip = get_ip(request)
200
+ if is_ip_exempted(ip):
201
+ return None
202
+
203
+ if request.method == "GET":
204
+ # Store timestamp for this IP's GET request
205
+ cache.set(f"honeypot_get:{ip}", time.time(), timeout=300) # 5 min timeout
206
+
207
+ elif request.method == "POST":
208
+ # Check if there was a preceding GET request
209
+ get_time = cache.get(f"honeypot_get:{ip}")
210
+
211
+ if get_time is None:
212
+ # No GET request - likely bot posting directly
213
+ BlacklistManager.block(ip, "Direct POST without GET")
214
+ return JsonResponse({"error": "blocked"}, status=403)
215
+
216
+ # Check timing
217
+ time_diff = time.time() - get_time
218
+ if time_diff < self.MIN_FORM_TIME:
219
+ # Posted too quickly - likely bot
220
+ BlacklistManager.block(ip, f"Form submitted too quickly ({time_diff:.2f}s)")
221
+ return JsonResponse({"error": "blocked"}, status=403)
222
+
192
223
  return None
193
224
 
194
225
 
@@ -211,5 +242,6 @@ class UUIDTamperMiddleware(MiddlewareMixin):
211
242
  except (ValueError, TypeError):
212
243
  continue
213
244
 
214
- BlacklistManager.block(ip, "UUID tampering")
215
- return JsonResponse({"error": "blocked"}, status=403)
245
+ if not is_ip_exempted(ip):
246
+ BlacklistManager.block(ip, "UUID tampering")
247
+ return JsonResponse({"error": "blocked"}, status=403)
@@ -33,4 +33,14 @@ class DynamicKeyword(models.Model):
33
33
  last_updated = models.DateTimeField(auto_now=True)
34
34
 
35
35
  class Meta:
36
- ordering = ['-count']
36
+ ordering = ['-count']
37
+
38
+
39
+ # Model to store IP addresses that are exempt from blacklisting
40
+ class IPExemption(models.Model):
41
+ ip_address = models.GenericIPAddressField(unique=True, db_index=True)
42
+ reason = models.CharField(max_length=100, blank=True, default="")
43
+ created_at = models.DateTimeField(auto_now_add=True)
44
+
45
+ def __str__(self):
46
+ return f"{self.ip_address} (Exempted: {self.reason})"
@@ -26,8 +26,10 @@ _LOG_RX = re.compile(
26
26
  r'(\d{3}).*?"(.*?)" "(.*?)".*?response-time=(\d+\.\d+)'
27
27
  )
28
28
 
29
+
29
30
  BlacklistEntry = apps.get_model("aiwaf", "BlacklistEntry")
30
31
  DynamicKeyword = apps.get_model("aiwaf", "DynamicKeyword")
32
+ IPExemption = apps.get_model("aiwaf", "IPExemption")
31
33
 
32
34
 
33
35
  def is_exempt_path(path: str) -> bool:
@@ -103,6 +105,10 @@ def _parse(line: str) -> dict | None:
103
105
 
104
106
  def train() -> None:
105
107
  remove_exempt_keywords()
108
+ # Remove any IPs in IPExemption from the blacklist
109
+ exempt_ips = set(IPExemption.objects.values_list("ip_address", flat=True))
110
+ if exempt_ips:
111
+ BlacklistEntry.objects.filter(ip_address__in=exempt_ips).delete()
106
112
  raw_lines = _read_all_logs()
107
113
  if not raw_lines:
108
114
  print("No log lines found – check AIWAF_ACCESS_LOG setting.")
@@ -1,3 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: aiwaf
3
+ Version: 0.1.8.1
4
+ Summary: AI-powered Web Application Firewall
5
+ Home-page: https://github.com/aayushgauba/aiwaf
6
+ Author: Aayush Gauba
7
+ Author-email: Aayush Gauba <gauba.aayush@gmail.com>
8
+ License: MIT
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: Django>=3.2
13
+ Requires-Dist: numpy>=1.21
14
+ Requires-Dist: pandas>=1.3
15
+ Requires-Dist: scikit-learn>=1.0
16
+ Requires-Dist: joblib>=1.1
17
+ Dynamic: author
18
+ Dynamic: home-page
19
+ Dynamic: license-file
20
+ Dynamic: requires-python
21
+
1
22
 
2
23
  # AI‑WAF
3
24
 
@@ -56,19 +77,58 @@ aiwaf/
56
77
  - **File‑Extension Probing Detection**
57
78
  Tracks repeated 404s on common extensions (e.g. `.php`, `.asp`) and blocks IPs.
58
79
 
59
- - **Honeypot Field**
60
- Hidden field for bot detection IP blacklisted on fill.
80
+ - **Timing-Based Honeypot**
81
+ Tracks GET→POST timing patterns. Blocks IPs that:
82
+ - POST directly without a preceding GET request
83
+ - Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
61
84
 
62
85
  - **UUID Tampering Protection**
63
86
  Blocks guessed or invalid UUIDs that don’t resolve to real models.
64
87
 
65
- - **Exempt Path Awareness**
66
- Fully respects `AIWAF_EXEMPT_PATHS` across all modules — exempt paths are:
88
+
89
+ **Exempt Path & IP Awareness**
90
+
91
+ **Exempt Paths:**
92
+ Set `AIWAF_EXEMPT_PATHS` in your Django `settings.py` (not in your code). Fully respects this setting across all modules — exempt paths are:
67
93
  - Skipped from keyword learning
68
94
  - Immune to AI blocking
69
95
  - Ignored in log training
70
96
  - Cleaned from `DynamicKeyword` model automatically
71
97
 
98
+ **Exempt IPs:**
99
+ You can exempt specific IP addresses from all blocking and blacklisting logic. Exempted IPs will:
100
+ - Never be added to the blacklist (even if they trigger rules)
101
+ - Be automatically removed from the blacklist during retraining
102
+ - Bypass all block/deny logic in middleware
103
+
104
+ ### Managing Exempt IPs
105
+
106
+ Add an IP to the exemption list using the management command:
107
+
108
+ ```bash
109
+ python manage.py add_ipexemption <ip-address> --reason "optional reason"
110
+ ```
111
+
112
+ ### Resetting AI-WAF
113
+
114
+ Clear all blacklist and exemption entries:
115
+
116
+ ```bash
117
+ # Clear everything (with confirmation prompt)
118
+ python manage.py aiwaf_reset
119
+
120
+ # Clear everything without confirmation
121
+ python manage.py aiwaf_reset --confirm
122
+
123
+ # Clear only blacklist entries
124
+ python manage.py aiwaf_reset --blacklist-only
125
+
126
+ # Clear only exemption entries
127
+ python manage.py aiwaf_reset --exemptions-only
128
+ ```
129
+
130
+ This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
131
+
72
132
  - **Daily Retraining**
73
133
  Reads rotated logs, auto‑blocks 404 floods, retrains the IsolationForest, updates `model.pkl`, and evolves the keyword DB.
74
134
 
@@ -103,13 +163,13 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
103
163
 
104
164
  ```python
105
165
  AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
106
- AIWAF_HONEYPOT_FIELD = "hp_field"
166
+ AIWAF_MIN_FORM_TIME = 1.0 # minimum seconds between GET and POST
107
167
  AIWAF_RATE_WINDOW = 10 # seconds
108
168
  AIWAF_RATE_MAX = 20 # max requests per window
109
169
  AIWAF_RATE_FLOOD = 10 # flood threshold
110
170
  AIWAF_WINDOW_SECONDS = 60 # anomaly detection window
111
171
  AIWAF_FILE_EXTENSIONS = [".php", ".asp", ".jsp"]
112
- AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
172
+ AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
113
173
  "/favicon.ico",
114
174
  "/robots.txt",
115
175
  "/static/",
@@ -131,7 +191,7 @@ MIDDLEWARE = [
131
191
  "aiwaf.middleware.IPAndKeywordBlockMiddleware",
132
192
  "aiwaf.middleware.RateLimitMiddleware",
133
193
  "aiwaf.middleware.AIAnomalyMiddleware",
134
- "aiwaf.middleware.HoneypotMiddleware",
194
+ "aiwaf.middleware.HoneypotTimingMiddleware",
135
195
  "aiwaf.middleware.UUIDTamperMiddleware",
136
196
  # ... other middleware ...
137
197
  ]
@@ -139,24 +199,7 @@ MIDDLEWARE = [
139
199
 
140
200
  ---
141
201
 
142
- ## 🕵️ Honeypot Field (in your template)
143
-
144
- ```django
145
- {% load aiwaf_tags %}
146
-
147
- <form method="post">
148
- {% csrf_token %}
149
- {% honeypot_field %}
150
- <!-- your real fields -->
151
- </form>
152
- ```
153
-
154
- > Renders a hidden `<input name="hp_field" style="display:none">`.
155
- > Any non‑empty submission → IP blacklisted.
156
-
157
- ---
158
-
159
- ## 🔁 Running Detection & Training
202
+ ## Running Detection & Training
160
203
 
161
204
  ```bash
162
205
  python manage.py detect_and_train
@@ -179,7 +222,7 @@ python manage.py detect_and_train
179
222
  | IPAndKeywordBlockMiddleware | Blocks requests from known blacklisted IPs and Keywords |
180
223
  | RateLimitMiddleware | Enforces burst & flood thresholds |
181
224
  | AIAnomalyMiddleware | ML‑driven behavior analysis + block on anomaly |
182
- | HoneypotMiddleware | Detects bots filling hidden inputs in forms |
225
+ | HoneypotTimingMiddleware | Detects bots via GET→POST timing analysis |
183
226
  | UUIDTamperMiddleware | Blocks guessed/nonexistent UUIDs across all models in an app |
184
227
 
185
228
  ---
@@ -13,9 +13,12 @@ aiwaf/utils.py
13
13
  aiwaf.egg-info/PKG-INFO
14
14
  aiwaf.egg-info/SOURCES.txt
15
15
  aiwaf.egg-info/dependency_links.txt
16
+ aiwaf.egg-info/requires.txt
16
17
  aiwaf.egg-info/top_level.txt
17
18
  aiwaf/management/__init__.py
18
19
  aiwaf/management/commands/__init__.py
20
+ aiwaf/management/commands/add_ipexemption.py
21
+ aiwaf/management/commands/aiwaf_reset.py
19
22
  aiwaf/management/commands/detect_and_train.py
20
23
  aiwaf/resources/model.pkl
21
24
  aiwaf/templatetags/__init__.py
@@ -0,0 +1,5 @@
1
+ Django>=3.2
2
+ numpy>=1.21
3
+ pandas>=1.3
4
+ scikit-learn>=1.0
5
+ joblib>=1.1
@@ -1,9 +1,15 @@
1
1
  [project]
2
2
  name = "aiwaf"
3
- version = "0.1.7.7"
3
+ version = "0.1.8.1"
4
4
  description = "AI-powered Web Application Firewall"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.8"
7
7
  license = {text = "MIT"}
8
8
  authors = [{ name = "Aayush Gauba", email = "gauba.aayush@gmail.com" }]
9
- dependencies = [ ]
9
+ dependencies = [
10
+ "Django>=3.2",
11
+ "numpy>=1.21",
12
+ "pandas>=1.3",
13
+ "scikit-learn>=1.0",
14
+ "joblib>=1.1"
15
+ ]
@@ -9,7 +9,7 @@ long_description = (HERE / "README.md").read_text(encoding="utf-8")
9
9
 
10
10
  setup(
11
11
  name="aiwaf",
12
- version="0.1.7.7",
12
+ version="0.1.8.1",
13
13
  description="AI‑driven, self‑learning Web Application Firewall for Django",
14
14
  long_description=long_description,
15
15
  long_description_content_type="text/markdown",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes