aiwaf 0.1.9.2.7__py3-none-any.whl → 0.1.9.2.9__py3-none-any.whl
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/__init__.py +1 -1
- aiwaf/management/commands/detect_and_train.py +13 -1
- aiwaf/management/commands/regenerate_model.py +23 -6
- aiwaf/middleware.py +8 -2
- aiwaf/storage.py +83 -9
- aiwaf/trainer.py +118 -86
- {aiwaf-0.1.9.2.7.dist-info → aiwaf-0.1.9.2.9.dist-info}/METADATA +16 -4
- {aiwaf-0.1.9.2.7.dist-info → aiwaf-0.1.9.2.9.dist-info}/RECORD +11 -11
- {aiwaf-0.1.9.2.7.dist-info → aiwaf-0.1.9.2.9.dist-info}/WHEEL +0 -0
- {aiwaf-0.1.9.2.7.dist-info → aiwaf-0.1.9.2.9.dist-info}/licenses/LICENSE +0 -0
- {aiwaf-0.1.9.2.7.dist-info → aiwaf-0.1.9.2.9.dist-info}/top_level.txt +0 -0
aiwaf/__init__.py
CHANGED
|
@@ -4,7 +4,19 @@ from aiwaf.trainer import train
|
|
|
4
4
|
class Command(BaseCommand):
|
|
5
5
|
help = "Run AI‑WAF detect & retrain"
|
|
6
6
|
|
|
7
|
+
def add_arguments(self, parser):
|
|
8
|
+
parser.add_argument(
|
|
9
|
+
'--disable-ai',
|
|
10
|
+
action='store_true',
|
|
11
|
+
help='Disable AI model training, only perform keyword learning'
|
|
12
|
+
)
|
|
13
|
+
|
|
7
14
|
def handle(self, *args, **options):
|
|
8
|
-
|
|
15
|
+
disable_ai = options.get('disable_ai', False)
|
|
16
|
+
|
|
17
|
+
if disable_ai:
|
|
18
|
+
self.stdout.write(self.style.WARNING("AI model training disabled - keyword learning only"))
|
|
19
|
+
|
|
20
|
+
train(disable_ai=disable_ai)
|
|
9
21
|
self.stdout.write(self.style.SUCCESS("Done."))
|
|
10
22
|
|
|
@@ -11,6 +11,11 @@ class Command(BaseCommand):
|
|
|
11
11
|
action='store_true',
|
|
12
12
|
help='Force regeneration even if model exists',
|
|
13
13
|
)
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
'--disable-ai',
|
|
16
|
+
action='store_true',
|
|
17
|
+
help='Disable AI model training, only perform keyword learning'
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
def handle(self, *args, **options):
|
|
16
21
|
self.stdout.write(self.style.HTTP_INFO("🔄 AI-WAF Model Regeneration"))
|
|
@@ -58,16 +63,28 @@ class Command(BaseCommand):
|
|
|
58
63
|
self.stdout.write("Regenerating model to fix version compatibility...")
|
|
59
64
|
|
|
60
65
|
# Regenerate model
|
|
61
|
-
|
|
66
|
+
disable_ai = options.get('disable_ai', False)
|
|
67
|
+
|
|
68
|
+
if disable_ai:
|
|
69
|
+
self.stdout.write("� AI model training disabled - keyword learning only")
|
|
70
|
+
self.stdout.write("🚀 Starting keyword training...")
|
|
71
|
+
else:
|
|
72
|
+
self.stdout.write("�🚀 Starting model training...")
|
|
62
73
|
|
|
63
74
|
try:
|
|
64
75
|
from aiwaf.trainer import train
|
|
65
|
-
train()
|
|
66
|
-
self.stdout.write("")
|
|
67
|
-
self.stdout.write(self.style.SUCCESS("✅ Model regenerated successfully!"))
|
|
76
|
+
train(disable_ai=disable_ai)
|
|
68
77
|
self.stdout.write("")
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
|
|
79
|
+
if disable_ai:
|
|
80
|
+
self.stdout.write(self.style.SUCCESS("✅ Keyword training completed successfully!"))
|
|
81
|
+
self.stdout.write("")
|
|
82
|
+
self.stdout.write("Keyword-based protection is now active.")
|
|
83
|
+
else:
|
|
84
|
+
self.stdout.write(self.style.SUCCESS("✅ Model regenerated successfully!"))
|
|
85
|
+
self.stdout.write("")
|
|
86
|
+
self.stdout.write("The model is now compatible with your current scikit-learn version.")
|
|
87
|
+
self.stdout.write("Version warnings should no longer appear.")
|
|
71
88
|
|
|
72
89
|
except Exception as e:
|
|
73
90
|
self.stdout.write("")
|
aiwaf/middleware.py
CHANGED
|
@@ -32,6 +32,12 @@ def load_model_safely():
|
|
|
32
32
|
import warnings
|
|
33
33
|
import sklearn
|
|
34
34
|
|
|
35
|
+
# Check if AI is disabled globally
|
|
36
|
+
ai_disabled = getattr(settings, "AIWAF_DISABLE_AI", False)
|
|
37
|
+
if ai_disabled:
|
|
38
|
+
print("AI functionality disabled via AIWAF_DISABLE_AI setting")
|
|
39
|
+
return None
|
|
40
|
+
|
|
35
41
|
try:
|
|
36
42
|
# Suppress sklearn version warnings temporarily
|
|
37
43
|
with warnings.catch_warnings():
|
|
@@ -46,13 +52,13 @@ def load_model_safely():
|
|
|
46
52
|
current_version = sklearn.__version__
|
|
47
53
|
|
|
48
54
|
if stored_version != current_version:
|
|
49
|
-
print(f"
|
|
55
|
+
print(f"Model was trained with sklearn v{stored_version}, current v{current_version}")
|
|
50
56
|
print(" Run 'python manage.py detect_and_train' to update model if needed.")
|
|
51
57
|
|
|
52
58
|
return model
|
|
53
59
|
else:
|
|
54
60
|
# Old format - direct model object
|
|
55
|
-
print("
|
|
61
|
+
print("Using legacy model format. Consider retraining for better compatibility.")
|
|
56
62
|
return model_data
|
|
57
63
|
|
|
58
64
|
except Exception as e:
|
aiwaf/storage.py
CHANGED
|
@@ -2,22 +2,43 @@ import numpy as np
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
from django.conf import settings
|
|
4
4
|
from django.utils import timezone
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
from collections import defaultdict
|
|
5
8
|
|
|
6
9
|
# Defer model imports to avoid AppRegistryNotReady during Django app loading
|
|
7
10
|
FeatureSample = BlacklistEntry = IPExemption = DynamicKeyword = None
|
|
8
11
|
|
|
12
|
+
# Fallback storage for when Django models are unavailable
|
|
13
|
+
_fallback_keywords = defaultdict(int)
|
|
14
|
+
_fallback_storage_path = os.path.join(os.path.dirname(__file__), 'fallback_keywords.json')
|
|
15
|
+
|
|
9
16
|
def _import_models():
|
|
10
17
|
"""Import Django models only when needed and apps are ready."""
|
|
11
18
|
global FeatureSample, BlacklistEntry, IPExemption, DynamicKeyword
|
|
12
19
|
|
|
13
20
|
if FeatureSample is not None:
|
|
14
21
|
return # Already imported
|
|
15
|
-
|
|
16
22
|
try:
|
|
17
23
|
from django.apps import apps
|
|
18
|
-
if apps.ready
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
if apps.ready:
|
|
25
|
+
# Try multiple ways to import models
|
|
26
|
+
try:
|
|
27
|
+
# First try: direct import (most reliable)
|
|
28
|
+
from .models import FeatureSample, BlacklistEntry, IPExemption, DynamicKeyword
|
|
29
|
+
except ImportError:
|
|
30
|
+
# Second try: check if aiwaf app is installed under different name
|
|
31
|
+
for app_config in apps.get_app_configs():
|
|
32
|
+
if 'aiwaf' in app_config.name.lower() or 'aiwaf' in app_config.label.lower():
|
|
33
|
+
try:
|
|
34
|
+
from .models import FeatureSample, BlacklistEntry, IPExemption, DynamicKeyword
|
|
35
|
+
break
|
|
36
|
+
except ImportError:
|
|
37
|
+
continue
|
|
38
|
+
except (ImportError, RuntimeError, Exception) as e:
|
|
39
|
+
# Log the error for debugging but don't fail silently
|
|
40
|
+
import sys
|
|
41
|
+
print(f"Warning: Could not import AIWAF models: {e}", file=sys.stderr)
|
|
21
42
|
# Keep models as None if can't import
|
|
22
43
|
pass
|
|
23
44
|
|
|
@@ -62,7 +83,6 @@ class ModelFeatureStore:
|
|
|
62
83
|
if df.empty:
|
|
63
84
|
return df
|
|
64
85
|
|
|
65
|
-
# Ensure proper column order and types
|
|
66
86
|
feature_cols = ['path_len', 'kw_hits', 'resp_time', 'status_idx', 'burst_count', 'total_404']
|
|
67
87
|
for col in feature_cols:
|
|
68
88
|
if col in df.columns:
|
|
@@ -242,11 +262,40 @@ class ModelExemptionStore:
|
|
|
242
262
|
return 0
|
|
243
263
|
|
|
244
264
|
class ModelKeywordStore:
|
|
265
|
+
@staticmethod
|
|
266
|
+
def _load_fallback_keywords():
|
|
267
|
+
"""Load keywords from fallback JSON file"""
|
|
268
|
+
global _fallback_keywords
|
|
269
|
+
try:
|
|
270
|
+
if os.path.exists(_fallback_storage_path):
|
|
271
|
+
with open(_fallback_storage_path, 'r') as f:
|
|
272
|
+
data = json.load(f)
|
|
273
|
+
_fallback_keywords = defaultdict(int, data)
|
|
274
|
+
except Exception as e:
|
|
275
|
+
import sys
|
|
276
|
+
print(f"Warning: Could not load fallback keywords: {e}", file=sys.stderr)
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def _save_fallback_keywords():
|
|
280
|
+
"""Save keywords to fallback JSON file"""
|
|
281
|
+
try:
|
|
282
|
+
with open(_fallback_storage_path, 'w') as f:
|
|
283
|
+
json.dump(dict(_fallback_keywords), f, indent=2)
|
|
284
|
+
except Exception as e:
|
|
285
|
+
import sys
|
|
286
|
+
print(f"Warning: Could not save fallback keywords: {e}", file=sys.stderr)
|
|
287
|
+
|
|
245
288
|
@staticmethod
|
|
246
289
|
def add_keyword(keyword, count=1):
|
|
247
290
|
"""Add a keyword to the dynamic keyword list"""
|
|
248
291
|
_import_models()
|
|
249
292
|
if DynamicKeyword is None:
|
|
293
|
+
# Use fallback storage when Django models not available
|
|
294
|
+
ModelKeywordStore._load_fallback_keywords()
|
|
295
|
+
_fallback_keywords[keyword] += count
|
|
296
|
+
ModelKeywordStore._save_fallback_keywords()
|
|
297
|
+
import sys
|
|
298
|
+
print(f"Info: Using fallback storage for keyword '{keyword}' - Django models not available.", file=sys.stderr)
|
|
250
299
|
return
|
|
251
300
|
try:
|
|
252
301
|
obj, created = DynamicKeyword.objects.get_or_create(keyword=keyword)
|
|
@@ -257,7 +306,12 @@ class ModelKeywordStore:
|
|
|
257
306
|
obj.count = count
|
|
258
307
|
obj.save()
|
|
259
308
|
except Exception as e:
|
|
260
|
-
|
|
309
|
+
# Fallback to file storage on database error
|
|
310
|
+
ModelKeywordStore._load_fallback_keywords()
|
|
311
|
+
_fallback_keywords[keyword] += count
|
|
312
|
+
ModelKeywordStore._save_fallback_keywords()
|
|
313
|
+
import sys
|
|
314
|
+
print(f"Database error adding keyword {keyword}, using fallback storage: {e}", file=sys.stderr)
|
|
261
315
|
|
|
262
316
|
@staticmethod
|
|
263
317
|
def remove_keyword(keyword):
|
|
@@ -275,14 +329,22 @@ class ModelKeywordStore:
|
|
|
275
329
|
"""Get top N keywords by count"""
|
|
276
330
|
_import_models()
|
|
277
331
|
if DynamicKeyword is None:
|
|
278
|
-
|
|
332
|
+
# Use fallback storage
|
|
333
|
+
ModelKeywordStore._load_fallback_keywords()
|
|
334
|
+
sorted_keywords = sorted(_fallback_keywords.items(), key=lambda x: x[1], reverse=True)
|
|
335
|
+
return [keyword for keyword, count in sorted_keywords[:n]]
|
|
279
336
|
try:
|
|
280
337
|
return list(
|
|
281
338
|
DynamicKeyword.objects.order_by('-count')[:n]
|
|
282
339
|
.values_list('keyword', flat=True)
|
|
283
340
|
)
|
|
284
|
-
except Exception:
|
|
285
|
-
|
|
341
|
+
except Exception as e:
|
|
342
|
+
# Fallback to file storage on database error
|
|
343
|
+
ModelKeywordStore._load_fallback_keywords()
|
|
344
|
+
sorted_keywords = sorted(_fallback_keywords.items(), key=lambda x: x[1], reverse=True)
|
|
345
|
+
import sys
|
|
346
|
+
print(f"Database error getting top keywords, using fallback storage: {e}", file=sys.stderr)
|
|
347
|
+
return [keyword for keyword, count in sorted_keywords[:n]]
|
|
286
348
|
|
|
287
349
|
@staticmethod
|
|
288
350
|
def get_all_keywords():
|
|
@@ -308,6 +370,18 @@ class ModelKeywordStore:
|
|
|
308
370
|
except Exception as e:
|
|
309
371
|
print(f"Error resetting keywords: {e}")
|
|
310
372
|
|
|
373
|
+
def add_keyword_for_route(self, route, keyword, count=1):
|
|
374
|
+
"""Add a keyword for a specific route (fallback method)"""
|
|
375
|
+
# For now, just use the general add_keyword method
|
|
376
|
+
# In a full implementation, this would handle route-specific storage
|
|
377
|
+
return ModelKeywordStore.add_keyword(keyword, count)
|
|
378
|
+
|
|
379
|
+
def get_keywords_for_route(self, route):
|
|
380
|
+
"""Get keywords for a specific route (fallback method)"""
|
|
381
|
+
# For now, return all keywords
|
|
382
|
+
# In a full implementation, this would return route-specific keywords
|
|
383
|
+
return ModelKeywordStore.get_all_keywords()
|
|
384
|
+
|
|
311
385
|
# Factory functions that only return Django model stores
|
|
312
386
|
def get_feature_store():
|
|
313
387
|
"""Get the feature store (Django models only)"""
|
aiwaf/trainer.py
CHANGED
|
@@ -3,13 +3,10 @@ import glob
|
|
|
3
3
|
import gzip
|
|
4
4
|
import re
|
|
5
5
|
import joblib
|
|
6
|
-
|
|
7
6
|
from datetime import datetime
|
|
8
7
|
from collections import defaultdict, Counter
|
|
9
|
-
|
|
10
8
|
import pandas as pd
|
|
11
9
|
from sklearn.ensemble import IsolationForest
|
|
12
|
-
|
|
13
10
|
from django.conf import settings
|
|
14
11
|
from django.apps import apps
|
|
15
12
|
from django.db.models import F
|
|
@@ -409,9 +406,16 @@ def _is_malicious_context_trainer(path: str, keyword: str, status: str = "404")
|
|
|
409
406
|
return any(malicious_indicators)
|
|
410
407
|
|
|
411
408
|
|
|
412
|
-
def train() -> None:
|
|
413
|
-
"""Enhanced training with improved keyword filtering and exemption handling
|
|
414
|
-
|
|
409
|
+
def train(disable_ai=False) -> None:
|
|
410
|
+
"""Enhanced training with improved keyword filtering and exemption handling
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
disable_ai (bool): If True, skip AI model training and only do keyword learning
|
|
414
|
+
"""
|
|
415
|
+
print("Starting AIWAF enhanced training...")
|
|
416
|
+
|
|
417
|
+
if disable_ai:
|
|
418
|
+
print("AI model training disabled - keyword learning only")
|
|
415
419
|
|
|
416
420
|
# Remove exempt keywords first
|
|
417
421
|
remove_exempt_keywords()
|
|
@@ -421,7 +425,7 @@ def train() -> None:
|
|
|
421
425
|
|
|
422
426
|
exempted_ips = [entry['ip_address'] for entry in exemption_store.get_all()]
|
|
423
427
|
if exempted_ips:
|
|
424
|
-
print(f"
|
|
428
|
+
print(f"Found {len(exempted_ips)} exempted IPs - clearing from blacklist")
|
|
425
429
|
for ip in exempted_ips:
|
|
426
430
|
BlacklistManager.unblock(ip)
|
|
427
431
|
|
|
@@ -484,85 +488,100 @@ def train() -> None:
|
|
|
484
488
|
})
|
|
485
489
|
|
|
486
490
|
if not feature_dicts:
|
|
487
|
-
print("
|
|
491
|
+
print(" Nothing to train on – no valid log entries.")
|
|
488
492
|
return
|
|
489
493
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
contamination=getattr(settings, "AIWAF_AI_CONTAMINATION", 0.05),
|
|
495
|
-
random_state=42
|
|
496
|
-
)
|
|
497
|
-
|
|
498
|
-
# Suppress sklearn warnings during training
|
|
499
|
-
import warnings
|
|
500
|
-
with warnings.catch_warnings():
|
|
501
|
-
warnings.filterwarnings("ignore", category=UserWarning, module="sklearn")
|
|
502
|
-
model.fit(X)
|
|
503
|
-
|
|
504
|
-
os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
|
|
505
|
-
|
|
506
|
-
# Save model with version metadata
|
|
507
|
-
import sklearn
|
|
508
|
-
from django.utils import timezone as django_timezone
|
|
509
|
-
model_data = {
|
|
510
|
-
'model': model,
|
|
511
|
-
'sklearn_version': sklearn.__version__,
|
|
512
|
-
'created_at': str(django_timezone.now()),
|
|
513
|
-
'feature_count': len(feature_cols),
|
|
514
|
-
'samples_count': len(X)
|
|
515
|
-
}
|
|
516
|
-
joblib.dump(model_data, MODEL_PATH)
|
|
517
|
-
print(f"✅ Model trained on {len(X)} samples → {MODEL_PATH}")
|
|
518
|
-
print(f"📦 Created with scikit-learn v{sklearn.__version__}")
|
|
519
|
-
|
|
520
|
-
# Check for anomalies and intelligently decide which IPs to block
|
|
521
|
-
preds = model.predict(X)
|
|
522
|
-
anomalous_ips = set(df.loc[preds == -1, "ip"])
|
|
523
|
-
|
|
524
|
-
if anomalous_ips:
|
|
525
|
-
print(f"⚠️ Detected {len(anomalous_ips)} potentially anomalous IPs during training")
|
|
526
|
-
|
|
527
|
-
exemption_store = get_exemption_store()
|
|
528
|
-
blacklist_store = get_blacklist_store()
|
|
529
|
-
blocked_count = 0
|
|
494
|
+
# AI Model Training (optional)
|
|
495
|
+
blocked_count = 0
|
|
496
|
+
if not disable_ai:
|
|
497
|
+
print(" Training AI anomaly detection model...")
|
|
530
498
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if
|
|
534
|
-
|
|
499
|
+
try:
|
|
500
|
+
df = pd.DataFrame(feature_dicts)
|
|
501
|
+
feature_cols = [c for c in df.columns if c != "ip"]
|
|
502
|
+
X = df[feature_cols].astype(float).values
|
|
503
|
+
model = IsolationForest(
|
|
504
|
+
contamination=getattr(settings, "AIWAF_AI_CONTAMINATION", 0.05),
|
|
505
|
+
random_state=42
|
|
506
|
+
)
|
|
535
507
|
|
|
536
|
-
#
|
|
537
|
-
|
|
508
|
+
# Suppress sklearn warnings during training
|
|
509
|
+
import warnings
|
|
510
|
+
with warnings.catch_warnings():
|
|
511
|
+
warnings.filterwarnings("ignore", category=UserWarning, module="sklearn")
|
|
512
|
+
model.fit(X)
|
|
513
|
+
|
|
514
|
+
os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
|
|
538
515
|
|
|
539
|
-
#
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
516
|
+
# Save model with version metadata
|
|
517
|
+
import sklearn
|
|
518
|
+
from django.utils import timezone as django_timezone
|
|
519
|
+
model_data = {
|
|
520
|
+
'model': model,
|
|
521
|
+
'sklearn_version': sklearn.__version__,
|
|
522
|
+
'created_at': str(django_timezone.now()),
|
|
523
|
+
'feature_count': len(feature_cols),
|
|
524
|
+
'samples_count': len(X)
|
|
525
|
+
}
|
|
526
|
+
joblib.dump(model_data, MODEL_PATH)
|
|
527
|
+
print(f"Model trained on {len(X)} samples → {MODEL_PATH}")
|
|
528
|
+
print(f"Created with scikit-learn v{sklearn.__version__}")
|
|
544
529
|
|
|
545
|
-
#
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
max_404s < 10 and # Not excessive 404s
|
|
549
|
-
avg_burst < 15 and # Not excessive burst activity
|
|
550
|
-
total_requests < 100 # Not excessive total requests
|
|
551
|
-
):
|
|
552
|
-
print(f" - {ip}: Anomalous but looks legitimate (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f}) - NOT blocking")
|
|
553
|
-
continue
|
|
530
|
+
# Check for anomalies and intelligently decide which IPs to block
|
|
531
|
+
preds = model.predict(X)
|
|
532
|
+
anomalous_ips = set(df.loc[preds == -1, "ip"])
|
|
554
533
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
534
|
+
if anomalous_ips:
|
|
535
|
+
print(f"Detected {len(anomalous_ips)} potentially anomalous IPs during training")
|
|
536
|
+
|
|
537
|
+
exemption_store = get_exemption_store()
|
|
538
|
+
blacklist_store = get_blacklist_store()
|
|
539
|
+
|
|
540
|
+
for ip in anomalous_ips:
|
|
541
|
+
# Skip if IP is exempted
|
|
542
|
+
if exemption_store.is_exempted(ip):
|
|
543
|
+
continue
|
|
544
|
+
|
|
545
|
+
# Get this IP's behavior from the data
|
|
546
|
+
ip_data = df[df["ip"] == ip]
|
|
547
|
+
|
|
548
|
+
# Criteria to determine if this is likely a legitimate user vs threat:
|
|
549
|
+
avg_kw_hits = ip_data["kw_hits"].mean()
|
|
550
|
+
max_404s = ip_data["total_404"].max()
|
|
551
|
+
avg_burst = ip_data["burst_count"].mean()
|
|
552
|
+
total_requests = len(ip_data)
|
|
553
|
+
|
|
554
|
+
# Don't block if it looks like legitimate behavior:
|
|
555
|
+
if (
|
|
556
|
+
avg_kw_hits < 2 and # Not hitting many malicious keywords
|
|
557
|
+
max_404s < 10 and # Not excessive 404s
|
|
558
|
+
avg_burst < 15 and # Not excessive burst activity
|
|
559
|
+
total_requests < 100 # Not excessive total requests
|
|
560
|
+
):
|
|
561
|
+
print(f" - {ip}: Anomalous but looks legitimate (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f}) - NOT blocking")
|
|
562
|
+
continue
|
|
563
|
+
|
|
564
|
+
# Block if it shows clear signs of malicious behavior
|
|
565
|
+
BlacklistManager.block(ip, f"AI anomaly + suspicious patterns (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f})")
|
|
566
|
+
blocked_count += 1
|
|
567
|
+
print(f" - {ip}: Blocked for suspicious behavior (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f})")
|
|
568
|
+
|
|
569
|
+
print(f" → Blocked {blocked_count}/{len(anomalous_ips)} anomalous IPs (others looked legitimate)")
|
|
559
570
|
|
|
560
|
-
|
|
571
|
+
except ImportError as e:
|
|
572
|
+
print(f"AI model training failed - missing dependencies: {e}")
|
|
573
|
+
print(" Continuing with keyword learning only...")
|
|
574
|
+
except Exception as e:
|
|
575
|
+
print(f"AI model training failed: {e}")
|
|
576
|
+
print(" Continuing with keyword learning only...")
|
|
577
|
+
else:
|
|
578
|
+
print("AI model training skipped (disabled)")
|
|
579
|
+
df = pd.DataFrame(feature_dicts) # Still need df for some operations
|
|
561
580
|
|
|
562
581
|
tokens = Counter()
|
|
563
582
|
legitimate_keywords = get_legitimate_keywords()
|
|
564
583
|
|
|
565
|
-
print(f"
|
|
584
|
+
print(f"Learning keywords from {len(parsed)} parsed requests...")
|
|
566
585
|
|
|
567
586
|
for r in parsed:
|
|
568
587
|
# Only learn from suspicious requests (errors on non-existent paths)
|
|
@@ -603,22 +622,35 @@ def train() -> None:
|
|
|
603
622
|
learned_from_paths.extend(example_paths[:2]) # Track first 2 example paths
|
|
604
623
|
|
|
605
624
|
if filtered_tokens:
|
|
606
|
-
print(f"
|
|
607
|
-
print(f"
|
|
625
|
+
print(f"Added {len(filtered_tokens)} suspicious keywords: {[kw for kw, _ in filtered_tokens]}")
|
|
626
|
+
print(f"Example malicious paths learned from: {learned_from_paths[:5]}") # Show first 5
|
|
608
627
|
else:
|
|
609
|
-
print("
|
|
628
|
+
print("No new suspicious keywords learned (good sign!)")
|
|
610
629
|
|
|
611
|
-
print(f"
|
|
612
|
-
print(f"
|
|
630
|
+
print(f"Smart keyword learning complete. Excluded {len(legitimate_keywords)} legitimate keywords.")
|
|
631
|
+
print(f"Used malicious context analysis to filter out false positives.")
|
|
613
632
|
|
|
614
633
|
# Training summary
|
|
615
634
|
print("\n" + "="*60)
|
|
616
|
-
|
|
635
|
+
if disable_ai:
|
|
636
|
+
print("AIWAF KEYWORD-ONLY TRAINING COMPLETE")
|
|
637
|
+
else:
|
|
638
|
+
print("AIWAF ENHANCED TRAINING COMPLETE")
|
|
617
639
|
print("="*60)
|
|
618
|
-
print(f"
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
640
|
+
print(f"Training Data: {len(parsed)} log entries processed")
|
|
641
|
+
|
|
642
|
+
if not disable_ai:
|
|
643
|
+
print(f"AI Model: Trained with {len(feature_cols) if 'feature_cols' in locals() else 'N/A'} features")
|
|
644
|
+
print(f"Blocked IPs: {blocked_count} suspicious IPs blocked")
|
|
645
|
+
else:
|
|
646
|
+
print(f"AI Model: Disabled (keyword learning only)")
|
|
647
|
+
print(f"Blocked IPs: 0 (AI blocking disabled)")
|
|
648
|
+
|
|
649
|
+
print(f"Keywords: {len(filtered_tokens)} new suspicious keywords learned")
|
|
650
|
+
print(f"Exemptions: {len(exempted_ips)} IPs protected from blocking")
|
|
651
|
+
|
|
652
|
+
if disable_ai:
|
|
653
|
+
print(f"Keyword-based protection now active with context-aware filtering!")
|
|
654
|
+
else:
|
|
655
|
+
print(f"Enhanced protection now active with context-aware filtering!")
|
|
624
656
|
print("="*60)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.9.2.
|
|
3
|
+
Version: 0.1.9.2.9
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -806,13 +806,25 @@ python manage.py aiwaf_reset --blacklist --confirm
|
|
|
806
806
|
|
|
807
807
|
---
|
|
808
808
|
|
|
809
|
-
##
|
|
809
|
+
## Sponsors
|
|
810
|
+
|
|
811
|
+
This project is proudly supported by:
|
|
812
|
+
|
|
813
|
+
<a href="https://www.digitalocean.com/">
|
|
814
|
+
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
|
|
815
|
+
</a>
|
|
816
|
+
|
|
817
|
+
[DigitalOcean](https://www.digitalocean.com/) provides the cloud infrastructure that powers AIWAF development.
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## License
|
|
810
822
|
|
|
811
823
|
This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.
|
|
812
824
|
|
|
813
825
|
---
|
|
814
826
|
|
|
815
|
-
##
|
|
827
|
+
## Credits
|
|
816
828
|
|
|
817
829
|
**AI‑WAF** by [Aayush Gauba](https://github.com/aayushgauba)
|
|
818
|
-
>
|
|
830
|
+
> "Let your firewall learn and evolve — keep your site a fortress." your Django `INSTALLED_APPS` to avoid setup errors.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
aiwaf/__init__.py,sha256=
|
|
1
|
+
aiwaf/__init__.py,sha256=WYEirEwV6DFp3jOUp4bh0HowpXax-5yA30mS_cQb1Tk,220
|
|
2
2
|
aiwaf/apps.py,sha256=nCez-Ptlv2kaEk5HenA8b1pATz1VfhrHP1344gwcY1A,142
|
|
3
3
|
aiwaf/blacklist_manager.py,sha256=LYCeKFB-7e_C6Bg2WeFJWFIIQlrfRMPuGp30ivrnhQY,1196
|
|
4
4
|
aiwaf/decorators.py,sha256=IUKOdM_gdroffImRZep1g1wT6gNqD10zGwcp28hsJCs,825
|
|
5
|
-
aiwaf/middleware.py,sha256=
|
|
5
|
+
aiwaf/middleware.py,sha256=Bo8xcrRugp9audWNKp2nkrfYC34E4523tgWarEdlb2A,32256
|
|
6
6
|
aiwaf/middleware_logger.py,sha256=LWZVDAnjh6CGESirA8eMbhGgJKB7lVDGRQqVroH95Lo,4742
|
|
7
7
|
aiwaf/models.py,sha256=vQxgY19BDVMjoO903UNrTZC1pNoLltMU6wbyWPoAEns,2719
|
|
8
|
-
aiwaf/storage.py,sha256=
|
|
9
|
-
aiwaf/trainer.py,sha256=
|
|
8
|
+
aiwaf/storage.py,sha256=pUXE3bm7aRrABh_B6jTOBUQOYK67oQmHaR9EqyOasis,14038
|
|
9
|
+
aiwaf/trainer.py,sha256=RYtSEnRKKzfwzFRX_XODbWgIqvedf4Y271DlDh5qsJE,28060
|
|
10
10
|
aiwaf/utils.py,sha256=BJk5vJCYdGPl_4QQiknjhCbkzv5HZCXgFcBJDMJpHok,3390
|
|
11
11
|
aiwaf/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
aiwaf/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -20,17 +20,17 @@ aiwaf/management/commands/check_dependencies.py,sha256=GOZl00pDwW2cJjDvIaCeB3yWx
|
|
|
20
20
|
aiwaf/management/commands/clear_blacklist.py,sha256=Tisedg0EVlc3E01mA3hBZQorwMzc5j1cns-oYshja0g,2770
|
|
21
21
|
aiwaf/management/commands/clear_cache.py,sha256=cdnuTgxkhKLqT_6k6yTcEBlREovNRQxAE51ceXlGYMA,647
|
|
22
22
|
aiwaf/management/commands/debug_csv.py,sha256=Lddqp37mIn0zdvHf4GbuNTWYyJ5h8bumDcGmFSAioi0,6801
|
|
23
|
-
aiwaf/management/commands/detect_and_train.py,sha256
|
|
23
|
+
aiwaf/management/commands/detect_and_train.py,sha256=ai8mUq-n9SW4_SX59Q9hE6eUvmBgb6i-IiTkCvaBe84,703
|
|
24
24
|
aiwaf/management/commands/diagnose_blocking.py,sha256=HKb_FdN4b6QdyqNDf54B08I5jyWfrv9Mh-SFBrr3LbU,4140
|
|
25
|
-
aiwaf/management/commands/regenerate_model.py,sha256=
|
|
25
|
+
aiwaf/management/commands/regenerate_model.py,sha256=teguV17TlkE5IiefIDOx-ow4KfKvh35vYdhsdcfMpwg,4195
|
|
26
26
|
aiwaf/management/commands/setup_models.py,sha256=JzuxwAqO3e-8L4PdFlXkyEQmOA8EGCXBfaOwfCNv1Gg,1678
|
|
27
27
|
aiwaf/management/commands/test_exemption.py,sha256=ENmWFMJE8iQyzJGAPdw_5PUPknLajE8JjwHgH8DCFsE,5518
|
|
28
28
|
aiwaf/management/commands/test_exemption_fix.py,sha256=ngyGaHUCmQQ6y--6j4q1viZJtR-RvI526yDqvEaEXPs,2553
|
|
29
29
|
aiwaf/resources/model.pkl,sha256=5t6h9BX8yoh2xct85MXOO60jdlWyg1APskUOW0jZE1Y,1288265
|
|
30
30
|
aiwaf/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
31
|
aiwaf/templatetags/aiwaf_tags.py,sha256=XXfb7Tl4DjU3Sc40GbqdaqOEtKTUKELBEk58u83wBNw,357
|
|
32
|
-
aiwaf-0.1.9.2.
|
|
33
|
-
aiwaf-0.1.9.2.
|
|
34
|
-
aiwaf-0.1.9.2.
|
|
35
|
-
aiwaf-0.1.9.2.
|
|
36
|
-
aiwaf-0.1.9.2.
|
|
32
|
+
aiwaf-0.1.9.2.9.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
|
|
33
|
+
aiwaf-0.1.9.2.9.dist-info/METADATA,sha256=nUd5bp6VMsOJcDRK6qXQy6cO5eYGJ_arEdZhrrHft6M,27208
|
|
34
|
+
aiwaf-0.1.9.2.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
35
|
+
aiwaf-0.1.9.2.9.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
|
|
36
|
+
aiwaf-0.1.9.2.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|