aiwaf 0.1.9.1.0__py3-none-any.whl → 0.1.9.1.2__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/trainer.py +42 -7
- {aiwaf-0.1.9.1.0.dist-info → aiwaf-0.1.9.1.2.dist-info}/METADATA +1 -1
- {aiwaf-0.1.9.1.0.dist-info → aiwaf-0.1.9.1.2.dist-info}/RECORD +7 -7
- {aiwaf-0.1.9.1.0.dist-info → aiwaf-0.1.9.1.2.dist-info}/WHEEL +0 -0
- {aiwaf-0.1.9.1.0.dist-info → aiwaf-0.1.9.1.2.dist-info}/licenses/LICENSE +0 -0
- {aiwaf-0.1.9.1.0.dist-info → aiwaf-0.1.9.1.2.dist-info}/top_level.txt +0 -0
aiwaf/__init__.py
CHANGED
aiwaf/trainer.py
CHANGED
|
@@ -15,6 +15,7 @@ from django.apps import apps
|
|
|
15
15
|
from django.db.models import F
|
|
16
16
|
from .utils import is_exempt_path
|
|
17
17
|
from .storage import get_blacklist_store, get_exemption_store, get_keyword_store
|
|
18
|
+
from .blacklist_manager import BlacklistManager
|
|
18
19
|
|
|
19
20
|
# ─────────── Configuration ───────────
|
|
20
21
|
LOG_PATH = getattr(settings, 'AIWAF_ACCESS_LOG', None)
|
|
@@ -66,7 +67,7 @@ def remove_exempt_keywords() -> None:
|
|
|
66
67
|
def _read_all_logs() -> list[str]:
|
|
67
68
|
lines = []
|
|
68
69
|
|
|
69
|
-
# First try to read from main access log
|
|
70
|
+
# First try to read from main access log files
|
|
70
71
|
if LOG_PATH and os.path.exists(LOG_PATH):
|
|
71
72
|
with open(LOG_PATH, "r", errors="ignore") as f:
|
|
72
73
|
lines.extend(f.readlines())
|
|
@@ -78,9 +79,45 @@ def _read_all_logs() -> list[str]:
|
|
|
78
79
|
except OSError:
|
|
79
80
|
continue
|
|
80
81
|
|
|
82
|
+
# If no log files found, fall back to RequestLog model data
|
|
83
|
+
if not lines:
|
|
84
|
+
lines = _get_logs_from_model()
|
|
85
|
+
|
|
81
86
|
return lines
|
|
82
87
|
|
|
83
88
|
|
|
89
|
+
def _get_logs_from_model() -> list[str]:
|
|
90
|
+
"""Get log data from RequestLog model when log files are not available"""
|
|
91
|
+
try:
|
|
92
|
+
# Import here to avoid circular imports
|
|
93
|
+
from .models import RequestLog
|
|
94
|
+
from datetime import datetime, timedelta
|
|
95
|
+
|
|
96
|
+
# Get logs from the last 30 days
|
|
97
|
+
cutoff_date = datetime.now() - timedelta(days=30)
|
|
98
|
+
request_logs = RequestLog.objects.filter(timestamp__gte=cutoff_date).order_by('timestamp')
|
|
99
|
+
|
|
100
|
+
log_lines = []
|
|
101
|
+
for log in request_logs:
|
|
102
|
+
# Convert RequestLog to Apache-style log format that _parse() expects
|
|
103
|
+
# Format: IP - - [timestamp] "METHOD path HTTP/1.1" status content_length "referer" "user_agent" response-time=X.X
|
|
104
|
+
timestamp_str = log.timestamp.strftime("%d/%b/%Y:%H:%M:%S %z")
|
|
105
|
+
log_line = (
|
|
106
|
+
f'{log.ip_address} - - [{timestamp_str}] '
|
|
107
|
+
f'"{log.method} {log.path} HTTP/1.1" {log.status_code} '
|
|
108
|
+
f'{log.content_length} "{log.referer}" "{log.user_agent}" '
|
|
109
|
+
f'response-time={log.response_time}\n'
|
|
110
|
+
)
|
|
111
|
+
log_lines.append(log_line)
|
|
112
|
+
|
|
113
|
+
print(f"Loaded {len(log_lines)} log entries from RequestLog model")
|
|
114
|
+
return log_lines
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"Warning: Could not load logs from RequestLog model: {e}")
|
|
118
|
+
return []
|
|
119
|
+
|
|
120
|
+
|
|
84
121
|
def _parse(line: str) -> dict | None:
|
|
85
122
|
m = _LOG_RX.search(line)
|
|
86
123
|
if not m:
|
|
@@ -102,13 +139,12 @@ def _parse(line: str) -> dict | None:
|
|
|
102
139
|
def train() -> None:
|
|
103
140
|
remove_exempt_keywords()
|
|
104
141
|
|
|
105
|
-
# Remove any IPs in IPExemption from the blacklist using
|
|
142
|
+
# Remove any IPs in IPExemption from the blacklist using BlacklistManager
|
|
106
143
|
exemption_store = get_exemption_store()
|
|
107
|
-
blacklist_store = get_blacklist_store()
|
|
108
144
|
|
|
109
145
|
exempted_ips = [entry['ip_address'] for entry in exemption_store.get_all()]
|
|
110
146
|
for ip in exempted_ips:
|
|
111
|
-
|
|
147
|
+
BlacklistManager.unblock(ip)
|
|
112
148
|
|
|
113
149
|
raw_lines = _read_all_logs()
|
|
114
150
|
if not raw_lines:
|
|
@@ -141,8 +177,7 @@ def train() -> None:
|
|
|
141
177
|
|
|
142
178
|
# Don't block if majority of 404s are on login paths
|
|
143
179
|
if count > login_404s: # More non-login 404s than login 404s
|
|
144
|
-
|
|
145
|
-
blacklist_store.add_ip(ip, f"Excessive 404s (≥6 non-login, {count}/{total_404s})")
|
|
180
|
+
BlacklistManager.block(ip, f"Excessive 404s (≥6 non-login, {count}/{total_404s})")
|
|
146
181
|
|
|
147
182
|
feature_dicts = []
|
|
148
183
|
for r in parsed:
|
|
@@ -239,7 +274,7 @@ def train() -> None:
|
|
|
239
274
|
continue
|
|
240
275
|
|
|
241
276
|
# Block if it shows clear signs of malicious behavior
|
|
242
|
-
|
|
277
|
+
BlacklistManager.block(ip, f"AI anomaly + suspicious patterns (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f})")
|
|
243
278
|
blocked_count += 1
|
|
244
279
|
print(f" - {ip}: Blocked for suspicious behavior (kw:{avg_kw_hits:.1f}, 404s:{max_404s}, burst:{avg_burst:.1f})")
|
|
245
280
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
aiwaf/__init__.py,sha256=
|
|
1
|
+
aiwaf/__init__.py,sha256=vIH1m7iiywRvUB5Q4Lj6MFEkqDOZLB1N5nFPgaV4erg,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
|
|
@@ -6,7 +6,7 @@ aiwaf/middleware.py,sha256=4Ox0pUdB7rMT1Sw5XHO6-udQrfqyF9VGdkkkgLioRJ0,12470
|
|
|
6
6
|
aiwaf/middleware_logger.py,sha256=LWZVDAnjh6CGESirA8eMbhGgJKB7lVDGRQqVroH95Lo,4742
|
|
7
7
|
aiwaf/models.py,sha256=vQxgY19BDVMjoO903UNrTZC1pNoLltMU6wbyWPoAEns,2719
|
|
8
8
|
aiwaf/storage.py,sha256=HYSnis7S8ETsos_NxWkd05OoiHXMhIWQy8FcFTqO4vk,8408
|
|
9
|
-
aiwaf/trainer.py,sha256=
|
|
9
|
+
aiwaf/trainer.py,sha256=1RPjWVOdGQ3qSrjFopw8HKu7THVTMvF4nNYouij6i_A,10685
|
|
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
|
|
@@ -26,8 +26,8 @@ aiwaf/management/commands/test_exemption_fix.py,sha256=ngyGaHUCmQQ6y--6j4q1viZJt
|
|
|
26
26
|
aiwaf/resources/model.pkl,sha256=5t6h9BX8yoh2xct85MXOO60jdlWyg1APskUOW0jZE1Y,1288265
|
|
27
27
|
aiwaf/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
aiwaf/templatetags/aiwaf_tags.py,sha256=XXfb7Tl4DjU3Sc40GbqdaqOEtKTUKELBEk58u83wBNw,357
|
|
29
|
-
aiwaf-0.1.9.1.
|
|
30
|
-
aiwaf-0.1.9.1.
|
|
31
|
-
aiwaf-0.1.9.1.
|
|
32
|
-
aiwaf-0.1.9.1.
|
|
33
|
-
aiwaf-0.1.9.1.
|
|
29
|
+
aiwaf-0.1.9.1.2.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
|
|
30
|
+
aiwaf-0.1.9.1.2.dist-info/METADATA,sha256=_whzaxN1jPkWXyncKhKvrkZqV2GnI34v3lpyBneU4FM,13763
|
|
31
|
+
aiwaf-0.1.9.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
32
|
+
aiwaf-0.1.9.1.2.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
|
|
33
|
+
aiwaf-0.1.9.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|