aiwaf 0.1.8.2__py3-none-any.whl → 0.1.8.4__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 +18 -0
- aiwaf/trainer.py +37 -7
- {aiwaf-0.1.8.2.dist-info → aiwaf-0.1.8.4.dist-info}/METADATA +13 -2
- {aiwaf-0.1.8.2.dist-info → aiwaf-0.1.8.4.dist-info}/RECORD +7 -7
- {aiwaf-0.1.8.2.dist-info → aiwaf-0.1.8.4.dist-info}/WHEEL +0 -0
- {aiwaf-0.1.8.2.dist-info → aiwaf-0.1.8.4.dist-info}/licenses/LICENSE +0 -0
- {aiwaf-0.1.8.2.dist-info → aiwaf-0.1.8.4.dist-info}/top_level.txt +0 -0
aiwaf/middleware.py
CHANGED
|
@@ -22,10 +22,28 @@ def is_ip_exempted(ip):
|
|
|
22
22
|
|
|
23
23
|
def is_exempt_path(path):
|
|
24
24
|
path = path.lower()
|
|
25
|
+
|
|
26
|
+
# Default login paths that should always be exempt
|
|
27
|
+
default_login_paths = [
|
|
28
|
+
"/admin/login/",
|
|
29
|
+
"/admin/",
|
|
30
|
+
"/login/",
|
|
31
|
+
"/accounts/login/",
|
|
32
|
+
"/auth/login/",
|
|
33
|
+
"/signin/",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# Check default login paths
|
|
37
|
+
for login_path in default_login_paths:
|
|
38
|
+
if path.startswith(login_path):
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
# Check user-configured exempt paths
|
|
25
42
|
exempt_paths = getattr(settings, "AIWAF_EXEMPT_PATHS", [])
|
|
26
43
|
for exempt in exempt_paths:
|
|
27
44
|
if path == exempt or path.startswith(exempt.rstrip("/") + "/"):
|
|
28
45
|
return True
|
|
46
|
+
|
|
29
47
|
return False
|
|
30
48
|
|
|
31
49
|
MODEL_PATH = getattr(
|
aiwaf/trainer.py
CHANGED
|
@@ -34,6 +34,23 @@ IPExemption = apps.get_model("aiwaf", "IPExemption")
|
|
|
34
34
|
|
|
35
35
|
def is_exempt_path(path: str) -> bool:
|
|
36
36
|
path = path.lower()
|
|
37
|
+
|
|
38
|
+
# Default login paths that should always be exempt
|
|
39
|
+
default_login_paths = [
|
|
40
|
+
"/admin/login/",
|
|
41
|
+
"/admin/",
|
|
42
|
+
"/login/",
|
|
43
|
+
"/accounts/login/",
|
|
44
|
+
"/auth/login/",
|
|
45
|
+
"/signin/",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# Check default login paths
|
|
49
|
+
for login_path in default_login_paths:
|
|
50
|
+
if path.startswith(login_path):
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
# Check user-configured exempt paths
|
|
37
54
|
for exempt in getattr(settings, "AIWAF_EXEMPT_PATHS", []):
|
|
38
55
|
if path == exempt or path.startswith(exempt.rstrip("/") + "/"):
|
|
39
56
|
return True
|
|
@@ -116,6 +133,7 @@ def train() -> None:
|
|
|
116
133
|
|
|
117
134
|
parsed = []
|
|
118
135
|
ip_404 = defaultdict(int)
|
|
136
|
+
ip_404_login = defaultdict(int) # Track 404s on login paths separately
|
|
119
137
|
ip_times = defaultdict(list)
|
|
120
138
|
|
|
121
139
|
for line in raw_lines:
|
|
@@ -125,15 +143,24 @@ def train() -> None:
|
|
|
125
143
|
parsed.append(rec)
|
|
126
144
|
ip_times[rec["ip"]].append(rec["timestamp"])
|
|
127
145
|
if rec["status"] == "404":
|
|
128
|
-
|
|
146
|
+
if is_exempt_path(rec["path"]):
|
|
147
|
+
ip_404_login[rec["ip"]] += 1 # Login path 404s
|
|
148
|
+
else:
|
|
149
|
+
ip_404[rec["ip"]] += 1 # Non-login path 404s
|
|
129
150
|
|
|
130
|
-
# 3. Optional immediate 404‐flood blocking
|
|
151
|
+
# 3. Optional immediate 404‐flood blocking (only for non-login paths)
|
|
131
152
|
for ip, count in ip_404.items():
|
|
132
153
|
if count >= 6:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
154
|
+
# Only block if they have significant non-login 404s
|
|
155
|
+
login_404s = ip_404_login.get(ip, 0)
|
|
156
|
+
total_404s = count + login_404s
|
|
157
|
+
|
|
158
|
+
# Don't block if majority of 404s are on login paths
|
|
159
|
+
if count > login_404s: # More non-login 404s than login 404s
|
|
160
|
+
BlacklistEntry.objects.get_or_create(
|
|
161
|
+
ip_address=ip,
|
|
162
|
+
defaults={"reason": f"Excessive 404s (≥6 non-login, {count}/{total_404s})"}
|
|
163
|
+
)
|
|
137
164
|
|
|
138
165
|
feature_dicts = []
|
|
139
166
|
for r in parsed:
|
|
@@ -167,7 +194,10 @@ def train() -> None:
|
|
|
167
194
|
df = pd.DataFrame(feature_dicts)
|
|
168
195
|
feature_cols = [c for c in df.columns if c != "ip"]
|
|
169
196
|
X = df[feature_cols].astype(float).values
|
|
170
|
-
model = IsolationForest(
|
|
197
|
+
model = IsolationForest(
|
|
198
|
+
contamination=getattr(settings, "AIWAF_AI_CONTAMINATION", 0.05),
|
|
199
|
+
random_state=42
|
|
200
|
+
)
|
|
171
201
|
model.fit(X)
|
|
172
202
|
|
|
173
203
|
os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.8.
|
|
3
|
+
Version: 0.1.8.4
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -89,7 +89,17 @@ aiwaf/
|
|
|
89
89
|
**Exempt Path & IP Awareness**
|
|
90
90
|
|
|
91
91
|
**Exempt Paths:**
|
|
92
|
-
|
|
92
|
+
AI‑WAF automatically exempts common login paths (`/admin/`, `/login/`, `/accounts/login/`, etc.) from all blocking mechanisms. You can add additional exempt paths in your Django `settings.py`:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
AIWAF_EXEMPT_PATHS = [
|
|
96
|
+
"/api/webhooks/",
|
|
97
|
+
"/health/",
|
|
98
|
+
"/special-endpoint/",
|
|
99
|
+
]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
All exempt paths are:
|
|
93
103
|
- Skipped from keyword learning
|
|
94
104
|
- Immune to AI blocking
|
|
95
105
|
- Ignored in log training
|
|
@@ -164,6 +174,7 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
|
|
|
164
174
|
```python
|
|
165
175
|
AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
|
|
166
176
|
AIWAF_MIN_FORM_TIME = 1.0 # minimum seconds between GET and POST
|
|
177
|
+
AIWAF_AI_CONTAMINATION = 0.05 # AI anomaly detection sensitivity (5%)
|
|
167
178
|
AIWAF_RATE_WINDOW = 10 # seconds
|
|
168
179
|
AIWAF_RATE_MAX = 20 # max requests per window
|
|
169
180
|
AIWAF_RATE_FLOOD = 10 # flood threshold
|
|
@@ -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=
|
|
4
|
+
aiwaf/middleware.py,sha256=3zFW0hGPpNti2_6Vbamw8m8YiqcJ2jC5sygWNxJPsMU,9670
|
|
5
5
|
aiwaf/models.py,sha256=XaG1pd_oZu3y-fw66u4wblGlWcUY9gvsTNKGD0kQk7Y,1672
|
|
6
6
|
aiwaf/storage.py,sha256=bxCILzzvA1-q6nwclRE8WrfoRhe25H4VrsQDf0hl_lY,1903
|
|
7
|
-
aiwaf/trainer.py,sha256=
|
|
7
|
+
aiwaf/trainer.py,sha256=IUPCGVe0RT6sRHecBeNZuKop2d6APzXlvv3nirgKQLI,7319
|
|
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
|
|
@@ -14,8 +14,8 @@ aiwaf/management/commands/detect_and_train.py,sha256=-o-LZ7QZ5GeJPCekryox1DGXKMm
|
|
|
14
14
|
aiwaf/resources/model.pkl,sha256=5t6h9BX8yoh2xct85MXOO60jdlWyg1APskUOW0jZE1Y,1288265
|
|
15
15
|
aiwaf/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
aiwaf/templatetags/aiwaf_tags.py,sha256=XXfb7Tl4DjU3Sc40GbqdaqOEtKTUKELBEk58u83wBNw,357
|
|
17
|
-
aiwaf-0.1.8.
|
|
18
|
-
aiwaf-0.1.8.
|
|
19
|
-
aiwaf-0.1.8.
|
|
20
|
-
aiwaf-0.1.8.
|
|
21
|
-
aiwaf-0.1.8.
|
|
17
|
+
aiwaf-0.1.8.4.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
|
|
18
|
+
aiwaf-0.1.8.4.dist-info/METADATA,sha256=abO7KBH2-WR5UDKt_bawhvXohcIToLXKQm_J33uwjRY,7435
|
|
19
|
+
aiwaf-0.1.8.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
aiwaf-0.1.8.4.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
|
|
21
|
+
aiwaf-0.1.8.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|