aiwaf 0.1.6__py3-none-any.whl → 0.1.7__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/middleware.py CHANGED
@@ -5,7 +5,7 @@ import re
5
5
  import os
6
6
  import numpy as np
7
7
  import joblib
8
-
8
+ from django.db.models import UUIDField
9
9
  from collections import defaultdict
10
10
  from django.utils.deprecation import MiddlewareMixin
11
11
  from django.http import JsonResponse
@@ -43,25 +43,29 @@ def get_ip(request):
43
43
  class IPAndKeywordBlockMiddleware:
44
44
  def __init__(self, get_response):
45
45
  self.get_response = get_response
46
- self.url_patterns = self._collect_view_paths()
46
+ self.safe_prefixes = self._collect_safe_prefixes()
47
47
 
48
- def _collect_view_paths(self):
48
+ def _collect_safe_prefixes(self):
49
49
  resolver = get_resolver()
50
- patterns = set()
50
+ prefixes = set()
51
51
 
52
52
  def extract(patterns_list, prefix=""):
53
53
  for p in patterns_list:
54
- if hasattr(p, "url_patterns"):
54
+ if hasattr(p, "url_patterns"): # include()
55
+ full_prefix = (prefix + str(p.pattern)).strip("^/").split("/")[0]
56
+ prefixes.add(full_prefix)
55
57
  extract(p.url_patterns, prefix + str(p.pattern))
56
58
  else:
57
59
  pat = (prefix + str(p.pattern)).strip("^$")
58
- patterns.add(pat)
60
+ path_parts = pat.strip("/").split("/")
61
+ if path_parts:
62
+ prefixes.add(path_parts[0])
59
63
  extract(resolver.url_patterns)
60
- return patterns
64
+ return prefixes
61
65
 
62
66
  def __call__(self, request):
63
67
  ip = get_ip(request)
64
- path = request.path.lower()
68
+ path = request.path.lower().lstrip("/")
65
69
  if BlacklistManager.is_blocked(ip):
66
70
  return JsonResponse({"error": "blocked"}, status=403)
67
71
  segments = [seg for seg in re.split(r"\W+", path) if len(seg) > 3]
@@ -74,8 +78,10 @@ class IPAndKeywordBlockMiddleware:
74
78
  .values_list("keyword", flat=True)[: getattr(settings, "AIWAF_DYNAMIC_TOP_N", 10)]
75
79
  )
76
80
  all_kw = set(STATIC_KW) | set(dynamic_top)
77
- safe_kw = {kw for kw in all_kw if any(kw in pat for pat in self.url_patterns)}
78
- suspicious_kw = all_kw - safe_kw
81
+ suspicious_kw = {
82
+ kw for kw in all_kw
83
+ if not any(path.startswith(prefix) for prefix in self.safe_prefixes if prefix)
84
+ }
79
85
  for seg in segments:
80
86
  if seg in suspicious_kw:
81
87
  BlacklistManager.block(ip, f"Keyword block: {seg}")
@@ -120,12 +126,9 @@ class AIAnomalyMiddleware(MiddlewareMixin):
120
126
  now = time.time()
121
127
  key = f"aiwaf:{ip}"
122
128
  data = cache.get(key, [])
123
- # TODO: you may want to capture real status & response_time in process_response
124
129
  data.append((now, request.path, 0, 0.0))
125
130
  data = [d for d in data if now - d[0] < self.WINDOW]
126
131
  cache.set(key, data, timeout=self.WINDOW)
127
-
128
- # update dynamic‐keyword counts
129
132
  for seg in re.split(r"\W+", request.path.lower()):
130
133
  if len(seg) > 3:
131
134
  obj, _ = DynamicKeyword.objects.get_or_create(keyword=seg)
@@ -133,8 +136,6 @@ class AIAnomalyMiddleware(MiddlewareMixin):
133
136
 
134
137
  if len(data) < 5:
135
138
  return None
136
-
137
- # pull top‐N dynamic tokens
138
139
  top_dynamic = list(
139
140
  DynamicKeyword.objects
140
141
  .order_by("-count")
@@ -177,8 +178,12 @@ class UUIDTamperMiddleware(MiddlewareMixin):
177
178
  app_label = view_func.__module__.split(".")[0]
178
179
  app_cfg = apps.get_app_config(app_label)
179
180
  for Model in app_cfg.get_models():
180
- if Model.objects.filter(pk=uid).exists():
181
- return None
181
+ if isinstance(Model._meta.pk, UUIDField):
182
+ try:
183
+ if Model.objects.filter(pk=uid).exists():
184
+ return None
185
+ except (ValueError, TypeError):
186
+ continue
182
187
 
183
188
  BlacklistManager.block(ip, "UUID tampering")
184
189
  return JsonResponse({"error": "blocked"}, status=403)
aiwaf/trainer.py CHANGED
@@ -12,6 +12,9 @@ import joblib
12
12
  from django.conf import settings
13
13
  from django.apps import apps
14
14
  from django.db.models import F
15
+ from django.urls import get_resolver
16
+
17
+ # ─── CONFIG ────────────────────────────────────────────────────────────────
15
18
 
16
19
  LOG_PATH = settings.AIWAF_ACCESS_LOG
17
20
  MODEL_PATH = os.path.join(os.path.dirname(__file__), "resources", "model.pkl")
@@ -28,6 +31,27 @@ BlacklistEntry = apps.get_model("aiwaf", "BlacklistEntry")
28
31
  DynamicKeyword = apps.get_model("aiwaf", "DynamicKeyword")
29
32
 
30
33
 
34
+
35
+ def path_exists_in_django(path):
36
+ from django.urls import get_resolver
37
+ from django.urls.resolvers import URLPattern, URLResolver
38
+
39
+ path = path.split("?")[0].lstrip("/")
40
+ try:
41
+ get_resolver().resolve(f"/{path}")
42
+ return True
43
+ except:
44
+ pass
45
+ parts = path.split("/")
46
+ root_resolver = get_resolver()
47
+ for pattern in root_resolver.url_patterns:
48
+ if isinstance(pattern, URLResolver):
49
+ prefix = pattern.pattern.describe().strip("^/")
50
+ if prefix and path.startswith(prefix):
51
+ return True
52
+ return False
53
+
54
+
31
55
  def _read_all_logs():
32
56
  lines = []
33
57
  if LOG_PATH and os.path.exists(LOG_PATH):
@@ -62,10 +86,11 @@ def _parse(line):
62
86
  }
63
87
 
64
88
 
89
+
65
90
  def train():
66
91
  raw_lines = _read_all_logs()
67
92
  if not raw_lines:
68
- print(" No log lines found – check AIWAF_ACCESS_LOG setting.")
93
+ print("No log lines found – check AIWAF_ACCESS_LOG setting.")
69
94
  return
70
95
  parsed = []
71
96
  ip_404 = defaultdict(int)
@@ -89,6 +114,7 @@ def train():
89
114
  blocked_404.append(ip)
90
115
  if blocked_404:
91
116
  print(f"Blocked {len(blocked_404)} IPs for 404 flood: {blocked_404}")
117
+
92
118
  feature_dicts = []
93
119
  for r in parsed:
94
120
  ip = r["ip"]
@@ -97,7 +123,10 @@ def train():
97
123
  if (r["timestamp"] - t).total_seconds() <= 10
98
124
  )
99
125
  total404 = ip_404[ip]
100
- kw_hits = sum(k in r["path"].lower() for k in STATIC_KW)
126
+ is_known_path = path_exists_in_django(r["path"])
127
+ kw_hits = 0
128
+ if not is_known_path:
129
+ kw_hits = sum(k in r["path"].lower() for k in STATIC_KW)
101
130
  status_idx = STATUS_IDX.index(r["status"]) if r["status"] in STATUS_IDX else -1
102
131
  feature_dicts.append({
103
132
  "ip": ip,
@@ -112,7 +141,6 @@ def train():
112
141
  if not feature_dicts:
113
142
  print("⚠️ Nothing to train on – no valid log entries.")
114
143
  return
115
-
116
144
  df = pd.DataFrame(feature_dicts)
117
145
  feature_cols = [c for c in df.columns if c != "ip"]
118
146
  X = df[feature_cols].astype(float).values
@@ -120,8 +148,8 @@ def train():
120
148
  model.fit(X)
121
149
  os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
122
150
  joblib.dump(model, MODEL_PATH)
123
- print(f"Model trained on {len(X)} samples → {MODEL_PATH}")
124
- preds = model.predict(X) # -1 for outliers
151
+ print(f"Model trained on {len(X)} samples → {MODEL_PATH}")
152
+ preds = model.predict(X)
125
153
  anomalous_ips = set(df.loc[preds == -1, 'ip'])
126
154
  blocked_anom = []
127
155
  for ip in anomalous_ips:
@@ -132,19 +160,21 @@ def train():
132
160
  if created:
133
161
  blocked_anom.append(ip)
134
162
  if blocked_anom:
135
- print(f" Blocked {len(blocked_anom)} anomalous IPs: {blocked_anom}")
136
-
163
+ print(f"🚫 Blocked {len(blocked_anom)} anomalous IPs: {blocked_anom}")
137
164
  tokens = Counter()
138
165
  for r in parsed:
139
- if r["status"].startswith(("4", "5")):
166
+ if r["status"].startswith(("4", "5")) and not path_exists_in_django(r["path"]):
140
167
  for seg in re.split(r"\W+", r["path"].lower()):
141
168
  if len(seg) > 3 and seg not in STATIC_KW:
142
169
  tokens[seg] += 1
170
+
143
171
  top_tokens = tokens.most_common(10)
144
172
  for kw, cnt in top_tokens:
145
173
  obj, _ = DynamicKeyword.objects.get_or_create(keyword=kw)
146
174
  DynamicKeyword.objects.filter(pk=obj.pk).update(count=F("count") + cnt)
147
- print(f"DynamicKeyword DB updated with top tokens: {[kw for kw, _ in top_tokens]}")
175
+
176
+ print(f" DynamicKeyword DB updated with top tokens: {[kw for kw, _ in top_tokens]}")
177
+
148
178
 
149
179
 
150
180
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiwaf
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: AI-powered Web Application Firewall
5
5
  Home-page: https://github.com/aayushgauba/aiwaf
6
6
  Author: Aayush Gauba
@@ -1,10 +1,10 @@
1
1
  aiwaf/__init__.py,sha256=nQFpJ1YpX48snzLjEQCf8zD2YNh8v0b_kPTrXx8uBYc,46
2
2
  aiwaf/apps.py,sha256=nCez-Ptlv2kaEk5HenA8b1pATz1VfhrHP1344gwcY1A,142
3
3
  aiwaf/blacklist_manager.py,sha256=sM6uTH7zD6MOPGb0kzqV2aFut2vxKgft_UVeRJr7klw,392
4
- aiwaf/middleware.py,sha256=04AbNgkwLMaYSiuEtw59A-O02tt4cqaKmP7XDNlkIG0,6359
4
+ aiwaf/middleware.py,sha256=2sNCqDULvuASo6dlbvrGpLzwhgHtHXwgVR8u3IhvrDI,6698
5
5
  aiwaf/models.py,sha256=8au1umopgCo0lthztTTRrYRJQUM7uX8eAeXgs3z45K4,1282
6
6
  aiwaf/storage.py,sha256=bxCILzzvA1-q6nwclRE8WrfoRhe25H4VrsQDf0hl_lY,1903
7
- aiwaf/trainer.py,sha256=TKWJZzWTg892vdoSGWdCA0i-dKof2b29buWqJUrkr6k,4820
7
+ aiwaf/trainer.py,sha256=IwL-BHbjGunOLX2HuGE12-W_PB0aDwbiZ62izPpfOEo,5796
8
8
  aiwaf/utils.py,sha256=RkEUWhhHy6tOk7V0UYv3cN4xhOR_7aBy9bjhwuV2cdA,1436
9
9
  aiwaf/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  aiwaf/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -12,8 +12,8 @@ aiwaf/management/commands/detect_and_train.py,sha256=-o-LZ7QZ5GeJPCekryox1DGXKMm
12
12
  aiwaf/resources/model.pkl,sha256=rCCXH38SJrnaOba2WZrU1LQVzWT34x6bTVkq20XJU-Q,1091129
13
13
  aiwaf/template_tags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  aiwaf/template_tags/aiwaf_tags.py,sha256=1KGqeioYmgKACDUiPkykSqI7DLQ6-Ypy1k00weWj9iY,399
15
- aiwaf-0.1.6.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
16
- aiwaf-0.1.6.dist-info/METADATA,sha256=xwayhSMTf_thMyNrS2-E5Wa8S1vXpqiOuwRZiI8-6Pw,5414
17
- aiwaf-0.1.6.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
18
- aiwaf-0.1.6.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
19
- aiwaf-0.1.6.dist-info/RECORD,,
15
+ aiwaf-0.1.7.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
16
+ aiwaf-0.1.7.dist-info/METADATA,sha256=uyKj5eHph-ufrCwZWOtGWxMZD1OtQOXu_6JXz0SRB2Q,5414
17
+ aiwaf-0.1.7.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
18
+ aiwaf-0.1.7.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
19
+ aiwaf-0.1.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5