aiwaf 0.1.9.2.8__tar.gz → 0.1.9.2.9__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 (43) hide show
  1. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/PKG-INFO +16 -4
  2. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/README.md +15 -3
  3. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/__init__.py +1 -1
  4. aiwaf-0.1.9.2.9/aiwaf/management/commands/detect_and_train.py +22 -0
  5. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/regenerate_model.py +23 -6
  6. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/middleware.py +8 -2
  7. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/storage.py +14 -4
  8. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/trainer.py +118 -86
  9. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf.egg-info/PKG-INFO +16 -4
  10. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/pyproject.toml +1 -1
  11. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/setup.py +1 -1
  12. aiwaf-0.1.9.2.8/aiwaf/management/commands/detect_and_train.py +0 -10
  13. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/LICENSE +0 -0
  14. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/apps.py +0 -0
  15. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/blacklist_manager.py +0 -0
  16. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/decorators.py +0 -0
  17. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/__init__.py +0 -0
  18. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/__init__.py +0 -0
  19. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/add_exemption.py +0 -0
  20. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/add_ipexemption.py +0 -0
  21. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/aiwaf_diagnose.py +0 -0
  22. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/aiwaf_list.py +0 -0
  23. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/aiwaf_logging.py +0 -0
  24. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/aiwaf_reset.py +0 -0
  25. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/check_dependencies.py +0 -0
  26. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/clear_blacklist.py +0 -0
  27. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/clear_cache.py +0 -0
  28. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/debug_csv.py +0 -0
  29. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/diagnose_blocking.py +0 -0
  30. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/setup_models.py +0 -0
  31. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/test_exemption.py +0 -0
  32. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/management/commands/test_exemption_fix.py +0 -0
  33. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/middleware_logger.py +0 -0
  34. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/models.py +0 -0
  35. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/resources/model.pkl +0 -0
  36. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/templatetags/__init__.py +0 -0
  37. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/templatetags/aiwaf_tags.py +0 -0
  38. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf/utils.py +0 -0
  39. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf.egg-info/SOURCES.txt +0 -0
  40. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf.egg-info/dependency_links.txt +0 -0
  41. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf.egg-info/requires.txt +0 -0
  42. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/aiwaf.egg-info/top_level.txt +0 -0
  43. {aiwaf-0.1.9.2.8 → aiwaf-0.1.9.2.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiwaf
3
- Version: 0.1.9.2.8
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
- ## 📄 License
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
- ## 👤 Credits
827
+ ## Credits
816
828
 
817
829
  **AI‑WAF** by [Aayush Gauba](https://github.com/aayushgauba)
818
- > Let your firewall learn and evolve — keep your site a fortress.”
830
+ > "Let your firewall learn and evolve — keep your site a fortress." your Django `INSTALLED_APPS` to avoid setup errors.
@@ -783,13 +783,25 @@ python manage.py aiwaf_reset --blacklist --confirm
783
783
 
784
784
  ---
785
785
 
786
- ## 📄 License
786
+ ## Sponsors
787
+
788
+ This project is proudly supported by:
789
+
790
+ <a href="https://www.digitalocean.com/">
791
+ <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
792
+ </a>
793
+
794
+ [DigitalOcean](https://www.digitalocean.com/) provides the cloud infrastructure that powers AIWAF development.
795
+
796
+ ---
797
+
798
+ ## License
787
799
 
788
800
  This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.
789
801
 
790
802
  ---
791
803
 
792
- ## 👤 Credits
804
+ ## Credits
793
805
 
794
806
  **AI‑WAF** by [Aayush Gauba](https://github.com/aayushgauba)
795
- > Let your firewall learn and evolve — keep your site a fortress.”
807
+ > "Let your firewall learn and evolve — keep your site a fortress." your Django `INSTALLED_APPS` to avoid setup errors.
@@ -1,6 +1,6 @@
1
1
  default_app_config = "aiwaf.apps.AiwafConfig"
2
2
 
3
- __version__ = "0.1.9.2.8"
3
+ __version__ = "0.1.9.2.9"
4
4
 
5
5
  # Note: Middleware classes are available from aiwaf.middleware
6
6
  # Import them only when needed to avoid circular imports during Django app loading
@@ -0,0 +1,22 @@
1
+ from django.core.management.base import BaseCommand
2
+ from aiwaf.trainer import train
3
+
4
+ class Command(BaseCommand):
5
+ help = "Run AI‑WAF detect & retrain"
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
+
14
+ def handle(self, *args, **options):
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)
21
+ self.stdout.write(self.style.SUCCESS("Done."))
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
- self.stdout.write("🚀 Starting model training...")
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
- self.stdout.write("The model is now compatible with your current scikit-learn version.")
70
- self.stdout.write("Version warnings should no longer appear.")
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("")
@@ -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"ℹ️ Model was trained with sklearn v{stored_version}, current v{current_version}")
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("ℹ️ Using legacy model format. Consider retraining for better compatibility.")
61
+ print("Using legacy model format. Consider retraining for better compatibility.")
56
62
  return model_data
57
63
 
58
64
  except Exception as e:
@@ -19,7 +19,6 @@ def _import_models():
19
19
 
20
20
  if FeatureSample is not None:
21
21
  return # Already imported
22
-
23
22
  try:
24
23
  from django.apps import apps
25
24
  if apps.ready:
@@ -84,7 +83,6 @@ class ModelFeatureStore:
84
83
  if df.empty:
85
84
  return df
86
85
 
87
- # Ensure proper column order and types
88
86
  feature_cols = ['path_len', 'kw_hits', 'resp_time', 'status_idx', 'burst_count', 'total_404']
89
87
  for col in feature_cols:
90
88
  if col in df.columns:
@@ -292,12 +290,12 @@ class ModelKeywordStore:
292
290
  """Add a keyword to the dynamic keyword list"""
293
291
  _import_models()
294
292
  if DynamicKeyword is None:
295
- # Use fallback storage
293
+ # Use fallback storage when Django models not available
296
294
  ModelKeywordStore._load_fallback_keywords()
297
295
  _fallback_keywords[keyword] += count
298
296
  ModelKeywordStore._save_fallback_keywords()
299
297
  import sys
300
- print(f"Warning: Using fallback storage for keyword '{keyword}' - Django models not available.", file=sys.stderr)
298
+ print(f"Info: Using fallback storage for keyword '{keyword}' - Django models not available.", file=sys.stderr)
301
299
  return
302
300
  try:
303
301
  obj, created = DynamicKeyword.objects.get_or_create(keyword=keyword)
@@ -372,6 +370,18 @@ class ModelKeywordStore:
372
370
  except Exception as e:
373
371
  print(f"Error resetting keywords: {e}")
374
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
+
375
385
  # Factory functions that only return Django model stores
376
386
  def get_feature_store():
377
387
  """Get the feature store (Django models only)"""
@@ -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
- print("🚀 Starting AIWAF enhanced training...")
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"🛡️ Found {len(exempted_ips)} exempted IPs - clearing from blacklist")
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("⚠️ Nothing to train on – no valid log entries.")
491
+ print(" Nothing to train on – no valid log entries.")
488
492
  return
489
493
 
490
- df = pd.DataFrame(feature_dicts)
491
- feature_cols = [c for c in df.columns if c != "ip"]
492
- X = df[feature_cols].astype(float).values
493
- model = IsolationForest(
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
- for ip in anomalous_ips:
532
- # Skip if IP is exempted
533
- if exemption_store.is_exempted(ip):
534
- continue
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
- # Get this IP's behavior from the data
537
- ip_data = df[df["ip"] == ip]
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
- # Criteria to determine if this is likely a legitimate user vs threat:
540
- avg_kw_hits = ip_data["kw_hits"].mean()
541
- max_404s = ip_data["total_404"].max()
542
- avg_burst = ip_data["burst_count"].mean()
543
- total_requests = len(ip_data)
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
- # Don't block if it looks like legitimate behavior:
546
- if (
547
- avg_kw_hits < 2 and # Not hitting many malicious keywords
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
- # Block if it shows clear signs of malicious behavior
556
- BlacklistManager.block(ip, f"AI anomaly + suspicious patterns (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f})")
557
- blocked_count += 1
558
- print(f" - {ip}: Blocked for suspicious behavior (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f})")
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
- print(f" → Blocked {blocked_count}/{len(anomalous_ips)} anomalous IPs (others looked legitimate)")
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"🔍 Learning keywords from {len(parsed)} parsed requests...")
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"📝 Added {len(filtered_tokens)} suspicious keywords: {[kw for kw, _ in filtered_tokens]}")
607
- print(f"🎯 Example malicious paths learned from: {learned_from_paths[:5]}") # Show first 5
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("No new suspicious keywords learned (good sign!)")
628
+ print("No new suspicious keywords learned (good sign!)")
610
629
 
611
- print(f"🎯 Smart keyword learning complete. Excluded {len(legitimate_keywords)} legitimate keywords.")
612
- print(f"🔒 Used malicious context analysis to filter out false positives.")
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
- print("🎉 AIWAF ENHANCED TRAINING COMPLETE")
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"📊 Training Data: {len(parsed)} log entries processed")
619
- print(f"🤖 AI Model: Trained with {len(feature_cols)} features")
620
- print(f"🚫 Blocked IPs: {blocked_count if 'blocked_count' in locals() else 0} suspicious IPs blocked")
621
- print(f"🔑 Keywords: {len(filtered_tokens)} new suspicious keywords learned")
622
- print(f"🛡️ Exemptions: {len(exempted_ips)} IPs protected from blocking")
623
- print(f"✅ Enhanced protection now active with context-aware filtering!")
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.8
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
- ## 📄 License
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
- ## 👤 Credits
827
+ ## Credits
816
828
 
817
829
  **AI‑WAF** by [Aayush Gauba](https://github.com/aayushgauba)
818
- > Let your firewall learn and evolve — keep your site a fortress.”
830
+ > "Let your firewall learn and evolve — keep your site a fortress." your Django `INSTALLED_APPS` to avoid setup errors.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aiwaf"
3
- version = "0.1.9.2.8"
3
+ version = "0.1.9.2.9"
4
4
  description = "AI-powered Web Application Firewall"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.8"
@@ -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.9.2.8",
12
+ version="0.1.9.2.9",
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",
@@ -1,10 +0,0 @@
1
- from django.core.management.base import BaseCommand
2
- from aiwaf.trainer import train
3
-
4
- class Command(BaseCommand):
5
- help = "Run AI‑WAF detect & retrain"
6
-
7
- def handle(self, *args, **options):
8
- train()
9
- self.stdout.write(self.style.SUCCESS("Done."))
10
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes