aiwaf 0.1.9.2.0__py3-none-any.whl → 0.1.9.2.1__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 CHANGED
@@ -1,6 +1,6 @@
1
1
  default_app_config = "aiwaf.apps.AiwafConfig"
2
2
 
3
- __version__ = "0.1.9.2.0"
3
+ __version__ = "0.1.9.2.1"
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
aiwaf/middleware.py CHANGED
@@ -103,47 +103,131 @@ class IPAndKeywordBlockMiddleware:
103
103
  return exempt_tokens
104
104
 
105
105
  def _get_legitimate_path_keywords(self):
106
- """Get keywords that are legitimate in URL paths"""
107
- # Extract from Django URL patterns
108
- legitimate_keywords = set()
106
+ """Get keywords that are legitimate in URL paths - uses same logic as trainer"""
107
+ # Import the enhanced function from trainer to ensure consistency
108
+ try:
109
+ from .trainer import get_legitimate_keywords
110
+ return get_legitimate_keywords()
111
+ except ImportError:
112
+ # Fallback to local implementation if trainer import fails
113
+ return self._get_legitimate_keywords_fallback()
114
+
115
+ def _get_legitimate_keywords_fallback(self):
116
+ """Fallback implementation matching trainer.py logic"""
117
+ legitimate = set()
109
118
 
110
- # Add common legitimate path segments
119
+ # Common legitimate path segments - matches trainer.py
111
120
  default_legitimate = {
112
- "profile", "user", "account", "settings", "dashboard",
113
- "home", "about", "contact", "help", "search", "list",
114
- "view", "edit", "create", "update", "delete", "detail",
115
- "api", "auth", "login", "logout", "register", "signup",
116
- "reset", "confirm", "activate", "verify", "page",
117
- "category", "tag", "post", "article", "blog", "news"
121
+ "profile", "user", "users", "account", "accounts", "settings", "dashboard",
122
+ "home", "about", "contact", "help", "search", "list", "lists",
123
+ "view", "views", "edit", "create", "update", "delete", "detail", "details",
124
+ "api", "auth", "login", "logout", "register", "signup", "signin",
125
+ "reset", "confirm", "activate", "verify", "page", "pages",
126
+ "category", "categories", "tag", "tags", "post", "posts",
127
+ "article", "articles", "blog", "blogs", "news", "item", "items",
128
+ "admin", "administration", "manage", "manager", "control", "panel",
129
+ "config", "configuration", "option", "options", "preference", "preferences"
118
130
  }
119
- legitimate_keywords.update(default_legitimate)
131
+ legitimate.update(default_legitimate)
132
+
133
+ # Extract keywords from Django URL patterns and app names - matches trainer.py
134
+ legitimate.update(self._extract_django_route_keywords())
120
135
 
121
136
  # Add from Django settings
122
137
  allowed_path_keywords = getattr(settings, "AIWAF_ALLOWED_PATH_KEYWORDS", [])
123
- legitimate_keywords.update(allowed_path_keywords)
138
+ legitimate.update(allowed_path_keywords)
124
139
 
125
- # Extract from actual Django URL patterns
126
- resolver = get_resolver()
127
- self._extract_path_keywords_from_urls(resolver.url_patterns, legitimate_keywords)
140
+ # Add exempt keywords
141
+ exempt_keywords = getattr(settings, "AIWAF_EXEMPT_KEYWORDS", [])
142
+ legitimate.update(exempt_keywords)
128
143
 
129
- return legitimate_keywords
144
+ return legitimate
130
145
 
131
- def _extract_path_keywords_from_urls(self, url_patterns, keywords, prefix=""):
132
- """Extract legitimate keywords from Django URL patterns"""
133
- for pattern in url_patterns:
134
- if hasattr(pattern, 'url_patterns'): # include()
135
- new_prefix = prefix + str(pattern.pattern).strip('^$/')
136
- self._extract_path_keywords_from_urls(pattern.url_patterns, keywords, new_prefix)
137
- else:
138
- # Extract static path segments from URL pattern
139
- pattern_str = str(pattern.pattern).strip('^$/')
140
- full_path = (prefix + '/' + pattern_str).strip('/')
146
+ def _extract_django_route_keywords(self):
147
+ """Extract legitimate keywords from Django URL patterns, app names, and model names - matches trainer.py"""
148
+ keywords = set()
149
+
150
+ try:
151
+ from django.urls.resolvers import URLResolver, URLPattern
152
+
153
+ # Extract from app names and labels
154
+ for app_config in apps.get_app_configs():
155
+ # Add app name and label
156
+ if app_config.name:
157
+ for segment in re.split(r'[._-]', app_config.name.lower()):
158
+ if len(segment) > 2:
159
+ keywords.add(segment)
160
+
161
+ if app_config.label and app_config.label != app_config.name:
162
+ for segment in re.split(r'[._-]', app_config.label.lower()):
163
+ if len(segment) > 2:
164
+ keywords.add(segment)
165
+
166
+ # Extract from model names in the app
167
+ try:
168
+ for model in app_config.get_models():
169
+ model_name = model._meta.model_name.lower()
170
+ if len(model_name) > 2:
171
+ keywords.add(model_name)
172
+ # Add plural form
173
+ if not model_name.endswith('s'):
174
+ keywords.add(f"{model_name}s")
175
+ except Exception:
176
+ continue
177
+
178
+ # Extract from URL patterns
179
+ def extract_from_pattern(pattern, prefix=""):
180
+ try:
181
+ if isinstance(pattern, URLResolver):
182
+ # Handle include() patterns
183
+ namespace = getattr(pattern, 'namespace', None)
184
+ if namespace:
185
+ for segment in re.split(r'[._-]', namespace.lower()):
186
+ if len(segment) > 2:
187
+ keywords.add(segment)
188
+
189
+ # Extract from the pattern itself
190
+ pattern_str = str(pattern.pattern)
191
+ for segment in re.findall(r'([a-zA-Z]\w{2,})', pattern_str):
192
+ keywords.add(segment.lower())
193
+
194
+ # Recurse into nested patterns
195
+ for nested_pattern in pattern.url_patterns:
196
+ extract_from_pattern(nested_pattern, prefix)
197
+
198
+ elif isinstance(pattern, URLPattern):
199
+ # Extract from URL pattern
200
+ pattern_str = str(pattern.pattern)
201
+ for segment in re.findall(r'([a-zA-Z]\w{2,})', pattern_str):
202
+ keywords.add(segment.lower())
203
+
204
+ # Extract from view name if available
205
+ if hasattr(pattern.callback, '__name__'):
206
+ view_name = pattern.callback.__name__.lower()
207
+ for segment in re.split(r'[._-]', view_name):
208
+ if len(segment) > 2 and segment != 'view':
209
+ keywords.add(segment)
210
+
211
+ except Exception:
212
+ pass
213
+
214
+ # Process all URL patterns
215
+ root_resolver = get_resolver()
216
+ for pattern in root_resolver.url_patterns:
217
+ extract_from_pattern(pattern)
141
218
 
142
- # Extract meaningful segments (not regex patterns)
143
- segments = re.findall(r'[a-zA-Z]{3,}', full_path)
144
- for seg in segments:
145
- if seg.lower() not in {'http', 'https', 'www'}:
146
- keywords.add(seg.lower())
219
+ except Exception as e:
220
+ # Silently continue if extraction fails
221
+ pass
222
+
223
+ # Filter out very common/generic words that might be suspicious
224
+ filtered_keywords = set()
225
+ for keyword in keywords:
226
+ if (len(keyword) >= 3 and
227
+ keyword not in ['www', 'com', 'org', 'net', 'int', 'str', 'obj', 'get', 'set', 'put', 'del']):
228
+ filtered_keywords.add(keyword)
229
+
230
+ return filtered_keywords
147
231
 
148
232
  def _is_malicious_context(self, request, segment):
149
233
  """Determine if a keyword appears in a malicious context"""
@@ -243,12 +327,29 @@ class IPAndKeywordBlockMiddleware:
243
327
 
244
328
  # Check segments against suspicious keywords
245
329
  for seg in segments:
330
+ is_suspicious = False
331
+ block_reason = ""
332
+
333
+ # Check if segment is in learned suspicious keywords
246
334
  if seg in suspicious_kw:
335
+ is_suspicious = True
336
+ block_reason = f"Learned keyword: {seg}"
337
+
338
+ # Also check if segment appears to be inherently malicious
339
+ elif (not path_exists and
340
+ seg not in self.legitimate_path_keywords and
341
+ (self._is_malicious_context(request, seg) or
342
+ any(malicious_pattern in seg for malicious_pattern in
343
+ ['hack', 'exploit', 'attack', 'malicious', 'evil', 'backdoor', 'inject', 'xss']))):
344
+ is_suspicious = True
345
+ block_reason = f"Inherently suspicious: {seg}"
346
+
347
+ if is_suspicious:
247
348
  # Additional context check before blocking
248
349
  if self._is_malicious_context(request, seg) or not path_exists:
249
350
  # Double-check exemption before blocking
250
351
  if not exemption_store.is_exempted(ip):
251
- BlacklistManager.block(ip, f"Keyword block: {seg} (context: malicious)")
352
+ BlacklistManager.block(ip, f"Keyword block: {block_reason}")
252
353
  # Check again after blocking attempt (exempted IPs won't be blocked)
253
354
  if BlacklistManager.is_blocked(ip):
254
355
  return JsonResponse({"error": "blocked"}, status=403)
aiwaf/trainer.py CHANGED
@@ -95,6 +95,9 @@ def get_legitimate_keywords() -> set:
95
95
  }
96
96
  legitimate.update(default_legitimate)
97
97
 
98
+ # Extract keywords from Django URL patterns and app names
99
+ legitimate.update(_extract_django_route_keywords())
100
+
98
101
  # Add from Django settings
99
102
  allowed_path_keywords = getattr(settings, "AIWAF_ALLOWED_PATH_KEYWORDS", [])
100
103
  legitimate.update(allowed_path_keywords)
@@ -106,6 +109,97 @@ def get_legitimate_keywords() -> set:
106
109
  return legitimate
107
110
 
108
111
 
112
+ def _extract_django_route_keywords() -> set:
113
+ """Extract legitimate keywords from Django URL patterns, app names, and model names"""
114
+ keywords = set()
115
+
116
+ try:
117
+ from django.urls import get_resolver
118
+ from django.urls.resolvers import URLResolver, URLPattern
119
+ from django.apps import apps
120
+
121
+ # Extract from app names and labels
122
+ for app_config in apps.get_app_configs():
123
+ # Add app name and label
124
+ if app_config.name:
125
+ for segment in re.split(r'[._-]', app_config.name.lower()):
126
+ if len(segment) > 2:
127
+ keywords.add(segment)
128
+
129
+ if app_config.label and app_config.label != app_config.name:
130
+ for segment in re.split(r'[._-]', app_config.label.lower()):
131
+ if len(segment) > 2:
132
+ keywords.add(segment)
133
+
134
+ # Extract from model names in the app
135
+ try:
136
+ for model in app_config.get_models():
137
+ model_name = model._meta.model_name.lower()
138
+ if len(model_name) > 2:
139
+ keywords.add(model_name)
140
+ # Add plural form
141
+ if not model_name.endswith('s'):
142
+ keywords.add(f"{model_name}s")
143
+ except Exception:
144
+ continue
145
+
146
+ # Extract from URL patterns
147
+ def extract_from_pattern(pattern, prefix=""):
148
+ try:
149
+ if isinstance(pattern, URLResolver):
150
+ # Handle include() patterns
151
+ namespace = getattr(pattern, 'namespace', None)
152
+ if namespace:
153
+ for segment in re.split(r'[._-]', namespace.lower()):
154
+ if len(segment) > 2:
155
+ keywords.add(segment)
156
+
157
+ # Extract from the pattern itself
158
+ pattern_str = str(pattern.pattern)
159
+ for segment in re.findall(r'([a-zA-Z]\w{2,})', pattern_str):
160
+ keywords.add(segment.lower())
161
+
162
+ # Recurse into nested patterns
163
+ for nested_pattern in pattern.url_patterns:
164
+ extract_from_pattern(nested_pattern, prefix)
165
+
166
+ elif isinstance(pattern, URLPattern):
167
+ # Extract from URL pattern
168
+ pattern_str = str(pattern.pattern)
169
+ for segment in re.findall(r'([a-zA-Z]\w{2,})', pattern_str):
170
+ keywords.add(segment.lower())
171
+
172
+ # Extract from view name if available
173
+ if hasattr(pattern.callback, '__name__'):
174
+ view_name = pattern.callback.__name__.lower()
175
+ for segment in re.split(r'[._-]', view_name):
176
+ if len(segment) > 2 and segment != 'view':
177
+ keywords.add(segment)
178
+
179
+ except Exception:
180
+ pass
181
+
182
+ # Process all URL patterns
183
+ root_resolver = get_resolver()
184
+ for pattern in root_resolver.url_patterns:
185
+ extract_from_pattern(pattern)
186
+
187
+ except Exception as e:
188
+ print(f"Warning: Could not extract Django route keywords: {e}")
189
+
190
+ # Filter out very common/generic words that might be suspicious
191
+ filtered_keywords = set()
192
+ for keyword in keywords:
193
+ if (len(keyword) >= 3 and
194
+ keyword not in ['www', 'com', 'org', 'net', 'int', 'str', 'obj', 'get', 'set', 'put', 'del']):
195
+ filtered_keywords.add(keyword)
196
+
197
+ if filtered_keywords:
198
+ print(f"🔗 Extracted {len(filtered_keywords)} legitimate keywords from Django routes and apps")
199
+
200
+ return filtered_keywords
201
+
202
+
109
203
  def _read_all_logs() -> list[str]:
110
204
  lines = []
111
205
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiwaf
3
- Version: 0.1.9.2.0
3
+ Version: 0.1.9.2.1
4
4
  Summary: AI-powered Web Application Firewall
5
5
  Home-page: https://github.com/aayushgauba/aiwaf
6
6
  Author: Aayush Gauba
@@ -94,14 +94,19 @@ aiwaf/
94
94
  - Burst count
95
95
  - Total 404s
96
96
 
97
- - **Enhanced Dynamic Keyword Learning**
97
+ - **Enhanced Dynamic Keyword Learning with Django Route Protection**
98
98
  - **Smart Context-Aware Learning**: Only learns keywords from suspicious requests on non-existent paths
99
- - **Legitimate Path Protection**: Automatically excludes keywords from valid Django URLs (like `/profile/`, `/admin/`)
99
+ - **Automatic Django Route Extraction**: Automatically excludes keywords from:
100
+ - Valid Django URL patterns (`/profile/`, `/admin/`, `/api/`, etc.)
101
+ - Django app names and model names (users, posts, categories)
102
+ - View function names and URL namespaces
103
+ - **Unified Logic**: Both trainer and middleware use identical legitimate keyword detection
100
104
  - **Configuration Options**:
101
105
  - `AIWAF_ALLOWED_PATH_KEYWORDS` - Explicitly allow certain keywords in legitimate paths
102
106
  - `AIWAF_EXEMPT_KEYWORDS` - Keywords that should never trigger blocking
103
107
  - **Automatic Cleanup**: Keywords from `AIWAF_EXEMPT_PATHS` are automatically removed from the database
104
108
  - **False Positive Prevention**: Stops learning legitimate site functionality as "malicious"
109
+ - **Inherent Malicious Detection**: Middleware also blocks obviously malicious keywords (`hack`, `exploit`, `attack`) even if not yet learned
105
110
 
106
111
  - **File‑Extension Probing Detection**
107
112
  Tracks repeated 404s on common extensions (e.g. `.php`, `.asp`) and blocks IPs.
@@ -1,12 +1,12 @@
1
- aiwaf/__init__.py,sha256=Bn2DcnLiYvx-vkOUfIbQtnKUDidKK1rAUNuXuPa36MM,220
1
+ aiwaf/__init__.py,sha256=SLcMD_OTXr3DXtHpuCKxFvNl_pjrg-J5KLcJ-Swutuo,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=D1HavBGJbpPneOtkkCVFddlOQwCdoWcugmHOvn5THDU,22614
5
+ aiwaf/middleware.py,sha256=8EC4AKfUjHhmVSKpquimkMUebBekr92pqyVF97wlbx0,27408
6
6
  aiwaf/middleware_logger.py,sha256=LWZVDAnjh6CGESirA8eMbhGgJKB7lVDGRQqVroH95Lo,4742
7
7
  aiwaf/models.py,sha256=vQxgY19BDVMjoO903UNrTZC1pNoLltMU6wbyWPoAEns,2719
8
8
  aiwaf/storage.py,sha256=5ImrZMRn3u7HNsPH0fDjWhDrD2tgG2IHVnOXtLz0fk4,10253
9
- aiwaf/trainer.py,sha256=UHkfrbJI47bGJPCz0Vws6r23WvGpemMHf5ScHWG_I1I,14568
9
+ aiwaf/trainer.py,sha256=47HP81kTaJCOfyONUm18r-FVc1YeRvcliO_akpX3BqI,18613
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
@@ -28,8 +28,8 @@ aiwaf/management/commands/test_exemption_fix.py,sha256=ngyGaHUCmQQ6y--6j4q1viZJt
28
28
  aiwaf/resources/model.pkl,sha256=5t6h9BX8yoh2xct85MXOO60jdlWyg1APskUOW0jZE1Y,1288265
29
29
  aiwaf/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  aiwaf/templatetags/aiwaf_tags.py,sha256=XXfb7Tl4DjU3Sc40GbqdaqOEtKTUKELBEk58u83wBNw,357
31
- aiwaf-0.1.9.2.0.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
32
- aiwaf-0.1.9.2.0.dist-info/METADATA,sha256=HM_8Dh89XWhMKtLDkqrvA7fxzR97F9Ph1sY2bYOk9Mc,26414
33
- aiwaf-0.1.9.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- aiwaf-0.1.9.2.0.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
35
- aiwaf-0.1.9.2.0.dist-info/RECORD,,
31
+ aiwaf-0.1.9.2.1.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
32
+ aiwaf-0.1.9.2.1.dist-info/METADATA,sha256=OgVYn0PPKBDcGCVlhYEFa7uc9XU4Rn-0ZS-W2CE9a1Q,26824
33
+ aiwaf-0.1.9.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ aiwaf-0.1.9.2.1.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
35
+ aiwaf-0.1.9.2.1.dist-info/RECORD,,