aiwaf 0.1.9.2.0__tar.gz → 0.1.9.2.1__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.9.2.0 → aiwaf-0.1.9.2.1}/PKG-INFO +8 -3
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/README.md +7 -2
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/__init__.py +1 -1
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/middleware.py +133 -32
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/trainer.py +94 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf.egg-info/PKG-INFO +8 -3
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/pyproject.toml +1 -1
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/setup.py +1 -1
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/LICENSE +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/apps.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/blacklist_manager.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/decorators.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/__init__.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/__init__.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/add_exemption.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/add_ipexemption.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/aiwaf_diagnose.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/aiwaf_logging.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/aiwaf_reset.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/check_dependencies.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/clear_blacklist.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/clear_cache.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/debug_csv.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/detect_and_train.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/diagnose_blocking.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/regenerate_model.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/setup_models.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/test_exemption.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/management/commands/test_exemption_fix.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/middleware_logger.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/models.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/resources/model.pkl +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/storage.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/templatetags/__init__.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/templatetags/aiwaf_tags.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf/utils.py +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf.egg-info/SOURCES.txt +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf.egg-info/dependency_links.txt +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf.egg-info/requires.txt +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/aiwaf.egg-info/top_level.txt +0 -0
- {aiwaf-0.1.9.2.0 → aiwaf-0.1.9.2.1}/setup.cfg +0 -0
|
@@ -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.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
|
-
- **
|
|
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.
|
|
@@ -71,14 +71,19 @@ aiwaf/
|
|
|
71
71
|
- Burst count
|
|
72
72
|
- Total 404s
|
|
73
73
|
|
|
74
|
-
- **Enhanced Dynamic Keyword Learning**
|
|
74
|
+
- **Enhanced Dynamic Keyword Learning with Django Route Protection**
|
|
75
75
|
- **Smart Context-Aware Learning**: Only learns keywords from suspicious requests on non-existent paths
|
|
76
|
-
- **
|
|
76
|
+
- **Automatic Django Route Extraction**: Automatically excludes keywords from:
|
|
77
|
+
- Valid Django URL patterns (`/profile/`, `/admin/`, `/api/`, etc.)
|
|
78
|
+
- Django app names and model names (users, posts, categories)
|
|
79
|
+
- View function names and URL namespaces
|
|
80
|
+
- **Unified Logic**: Both trainer and middleware use identical legitimate keyword detection
|
|
77
81
|
- **Configuration Options**:
|
|
78
82
|
- `AIWAF_ALLOWED_PATH_KEYWORDS` - Explicitly allow certain keywords in legitimate paths
|
|
79
83
|
- `AIWAF_EXEMPT_KEYWORDS` - Keywords that should never trigger blocking
|
|
80
84
|
- **Automatic Cleanup**: Keywords from `AIWAF_EXEMPT_PATHS` are automatically removed from the database
|
|
81
85
|
- **False Positive Prevention**: Stops learning legitimate site functionality as "malicious"
|
|
86
|
+
- **Inherent Malicious Detection**: Middleware also blocks obviously malicious keywords (`hack`, `exploit`, `attack`) even if not yet learned
|
|
82
87
|
|
|
83
88
|
- **File‑Extension Probing Detection**
|
|
84
89
|
Tracks repeated 404s on common extensions (e.g. `.php`, `.asp`) and blocks IPs.
|
|
@@ -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
|
-
#
|
|
108
|
-
|
|
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
|
-
#
|
|
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", "
|
|
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
|
-
|
|
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
|
-
|
|
138
|
+
legitimate.update(allowed_path_keywords)
|
|
124
139
|
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
# Add exempt keywords
|
|
141
|
+
exempt_keywords = getattr(settings, "AIWAF_EXEMPT_KEYWORDS", [])
|
|
142
|
+
legitimate.update(exempt_keywords)
|
|
128
143
|
|
|
129
|
-
return
|
|
144
|
+
return legitimate
|
|
130
145
|
|
|
131
|
-
def
|
|
132
|
-
"""Extract legitimate keywords from Django URL patterns"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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: {
|
|
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)
|
|
@@ -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.
|
|
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
|
-
- **
|
|
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.
|
|
@@ -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.
|
|
12
|
+
version="0.1.9.2.1",
|
|
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
|
|
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
|