aiwaf 0.1.8.4__tar.gz → 0.1.8.5__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.
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/PKG-INFO +1 -1
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/middleware.py +22 -10
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/trainer.py +40 -5
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf.egg-info/PKG-INFO +1 -1
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/pyproject.toml +1 -1
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/setup.py +1 -1
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/LICENSE +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/README.md +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/__init__.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/apps.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/blacklist_manager.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/management/__init__.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/management/commands/__init__.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/management/commands/add_ipexemption.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/management/commands/aiwaf_reset.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/management/commands/detect_and_train.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/models.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/resources/model.pkl +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/storage.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/templatetags/__init__.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/templatetags/aiwaf_tags.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf/utils.py +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf.egg-info/SOURCES.txt +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf.egg-info/dependency_links.txt +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf.egg-info/requires.txt +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/aiwaf.egg-info/top_level.txt +0 -0
- {aiwaf-0.1.8.4 → aiwaf-0.1.8.5}/setup.cfg +0 -0
|
@@ -219,7 +219,8 @@ class HoneypotTimingMiddleware(MiddlewareMixin):
|
|
|
219
219
|
return None
|
|
220
220
|
|
|
221
221
|
if request.method == "GET":
|
|
222
|
-
# Store timestamp for this IP's GET request
|
|
222
|
+
# Store timestamp for this IP's GET request
|
|
223
|
+
# Use a general key for the IP, not path-specific
|
|
223
224
|
cache.set(f"honeypot_get:{ip}", time.time(), timeout=300) # 5 min timeout
|
|
224
225
|
|
|
225
226
|
elif request.method == "POST":
|
|
@@ -228,15 +229,26 @@ class HoneypotTimingMiddleware(MiddlewareMixin):
|
|
|
228
229
|
|
|
229
230
|
if get_time is None:
|
|
230
231
|
# No GET request - likely bot posting directly
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
232
|
+
# But be more lenient for login paths since users might bookmark them
|
|
233
|
+
if not any(request.path.lower().startswith(login_path) for login_path in [
|
|
234
|
+
"/admin/login/", "/login/", "/accounts/login/", "/auth/login/", "/signin/"
|
|
235
|
+
]):
|
|
236
|
+
BlacklistManager.block(ip, "Direct POST without GET")
|
|
237
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
238
|
+
else:
|
|
239
|
+
# Check timing - be more lenient for login paths
|
|
240
|
+
time_diff = time.time() - get_time
|
|
241
|
+
min_time = self.MIN_FORM_TIME
|
|
242
|
+
|
|
243
|
+
# Use shorter time threshold for login paths (users can login quickly)
|
|
244
|
+
if any(request.path.lower().startswith(login_path) for login_path in [
|
|
245
|
+
"/admin/login/", "/login/", "/accounts/login/", "/auth/login/", "/signin/"
|
|
246
|
+
]):
|
|
247
|
+
min_time = 0.1 # Very short threshold for login forms
|
|
248
|
+
|
|
249
|
+
if time_diff < min_time:
|
|
250
|
+
BlacklistManager.block(ip, f"Form submitted too quickly ({time_diff:.2f}s)")
|
|
251
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
240
252
|
|
|
241
253
|
return None
|
|
242
254
|
|
|
@@ -203,13 +203,48 @@ def train() -> None:
|
|
|
203
203
|
os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
|
|
204
204
|
joblib.dump(model, MODEL_PATH)
|
|
205
205
|
print(f"Model trained on {len(X)} samples → {MODEL_PATH}")
|
|
206
|
+
|
|
207
|
+
# Check for anomalies and intelligently decide which IPs to block
|
|
206
208
|
preds = model.predict(X)
|
|
207
209
|
anomalous_ips = set(df.loc[preds == -1, "ip"])
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
|
|
211
|
+
if anomalous_ips:
|
|
212
|
+
print(f"⚠️ Detected {len(anomalous_ips)} potentially anomalous IPs during training")
|
|
213
|
+
|
|
214
|
+
blocked_count = 0
|
|
215
|
+
for ip in anomalous_ips:
|
|
216
|
+
# Skip if IP is exempted
|
|
217
|
+
if IPExemption.objects.filter(ip_address=ip).exists():
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
# Get this IP's behavior from the data
|
|
221
|
+
ip_data = df[df["ip"] == ip]
|
|
222
|
+
|
|
223
|
+
# Criteria to determine if this is likely a legitimate user vs threat:
|
|
224
|
+
avg_kw_hits = ip_data["kw_hits"].mean()
|
|
225
|
+
max_404s = ip_data["total_404"].max()
|
|
226
|
+
avg_burst = ip_data["burst_count"].mean()
|
|
227
|
+
total_requests = len(ip_data)
|
|
228
|
+
|
|
229
|
+
# Don't block if it looks like legitimate behavior:
|
|
230
|
+
if (
|
|
231
|
+
avg_kw_hits < 2 and # Not hitting many malicious keywords
|
|
232
|
+
max_404s < 10 and # Not excessive 404s
|
|
233
|
+
avg_burst < 15 and # Not excessive burst activity
|
|
234
|
+
total_requests < 100 # Not excessive total requests
|
|
235
|
+
):
|
|
236
|
+
print(f" - {ip}: Anomalous but looks legitimate (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f}) - NOT blocking")
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
# Block if it shows clear signs of malicious behavior
|
|
240
|
+
BlacklistEntry.objects.get_or_create(
|
|
241
|
+
ip_address=ip,
|
|
242
|
+
defaults={"reason": f"AI anomaly + suspicious patterns (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f})"}
|
|
243
|
+
)
|
|
244
|
+
blocked_count += 1
|
|
245
|
+
print(f" - {ip}: Blocked for suspicious behavior (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f})")
|
|
246
|
+
|
|
247
|
+
print(f" → Blocked {blocked_count}/{len(anomalous_ips)} anomalous IPs (others looked legitimate)")
|
|
213
248
|
|
|
214
249
|
tokens = Counter()
|
|
215
250
|
for r in parsed:
|
|
@@ -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.8.
|
|
12
|
+
version="0.1.8.5",
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|