aiwaf 0.1.0__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 -0
- aiwaf/apps.py +5 -0
- aiwaf/blacklist_manager.py +14 -0
- aiwaf/management/__init__.py +0 -0
- aiwaf/management/commands/__init__.py +0 -0
- aiwaf/management/commands/detect_and_train.py +10 -0
- aiwaf/middleware.py +115 -0
- aiwaf/models.py +28 -0
- aiwaf/resources/model.pkl +0 -0
- aiwaf/storage.py +61 -0
- aiwaf/template_tags/__init__.py +0 -0
- aiwaf/template_tags/aiwaf_tags.py +14 -0
- aiwaf/trainer.py +123 -0
- aiwaf/utils.py +50 -0
- aiwaf-0.1.0.dist-info/METADATA +13 -0
- aiwaf-0.1.0.dist-info/RECORD +19 -0
- aiwaf-0.1.0.dist-info/WHEEL +5 -0
- aiwaf-0.1.0.dist-info/entry_points.txt +2 -0
- aiwaf-0.1.0.dist-info/top_level.txt +1 -0
aiwaf/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
default_app_config = "aiwaf.apps.AiwafConfig"
|
aiwaf/apps.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .models import BlacklistEntry
|
|
2
|
+
|
|
3
|
+
class BlacklistManager:
|
|
4
|
+
@staticmethod
|
|
5
|
+
def block(ip, reason):
|
|
6
|
+
BlacklistEntry.objects.get_or_create(ip_address=ip, defaults={"reason": reason})
|
|
7
|
+
|
|
8
|
+
@staticmethod
|
|
9
|
+
def is_blocked(ip):
|
|
10
|
+
return BlacklistEntry.objects.filter(ip_address=ip).exists()
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def all_blocked():
|
|
14
|
+
return BlacklistEntry.objects.all()
|
|
File without changes
|
|
File without changes
|
aiwaf/middleware.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import numpy as np
|
|
3
|
+
import joblib
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
6
|
+
from django.http import JsonResponse
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.core.cache import cache
|
|
9
|
+
from django.urls import resolve
|
|
10
|
+
from django.apps import apps
|
|
11
|
+
from .blacklist_manager import BlacklistManager
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
MODEL_PATH = settings.AIWAF_MODEL_PATH
|
|
15
|
+
except AttributeError:
|
|
16
|
+
import importlib.resources
|
|
17
|
+
MODEL_PATH = importlib.resources.files("aiwaf").joinpath("resources/model.pkl")
|
|
18
|
+
|
|
19
|
+
MODEL = joblib.load(MODEL_PATH)
|
|
20
|
+
|
|
21
|
+
def get_ip(request):
|
|
22
|
+
xff = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
23
|
+
if xff:
|
|
24
|
+
return xff.split(",")[0].strip()
|
|
25
|
+
return request.META.get("REMOTE_ADDR", "")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class IPBlockMiddleware:
|
|
29
|
+
def __init__(self, get_response):
|
|
30
|
+
self.get_response = get_response
|
|
31
|
+
|
|
32
|
+
def __call__(self, request):
|
|
33
|
+
ip = get_ip(request)
|
|
34
|
+
if BlacklistManager.is_blocked(ip):
|
|
35
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
36
|
+
return self.get_response(request)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RateLimitMiddleware:
|
|
40
|
+
WINDOW = getattr(settings, "AIWAF_RATE_WINDOW", 10)
|
|
41
|
+
MAX = getattr(settings, "AIWAF_RATE_MAX", 20)
|
|
42
|
+
FLOOD = getattr(settings, "AIWAF_RATE_FLOOD", 10)
|
|
43
|
+
def __init__(self, get_response):
|
|
44
|
+
self.get_response = get_response
|
|
45
|
+
self.logs = defaultdict(list)
|
|
46
|
+
def __call__(self, request):
|
|
47
|
+
ip = get_ip(request)
|
|
48
|
+
now = time.time()
|
|
49
|
+
recs = [t for t in self.logs[ip] if now - t < self.WINDOW]
|
|
50
|
+
recs.append(now)
|
|
51
|
+
self.logs[ip] = recs
|
|
52
|
+
if len(recs) > self.MAX:
|
|
53
|
+
return JsonResponse({"error": "too_many_requests"}, status=429)
|
|
54
|
+
if len(recs) > self.FLOOD:
|
|
55
|
+
BlacklistManager.block(ip, "Flood pattern")
|
|
56
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
57
|
+
|
|
58
|
+
return self.get_response(request)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class AIAnomalyMiddleware(MiddlewareMixin):
|
|
62
|
+
WINDOW_SECONDS = getattr(settings, "AIWAF_WINDOW_SECONDS", 60)
|
|
63
|
+
def process_request(self, request):
|
|
64
|
+
ip = get_ip(request)
|
|
65
|
+
if BlacklistManager.is_blocked(ip):
|
|
66
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
67
|
+
now = time.time()
|
|
68
|
+
key = f"aiwaf:{ip}"
|
|
69
|
+
data = cache.get(key, [])
|
|
70
|
+
data.append((now, request.path, 0, 0.0))
|
|
71
|
+
data = [d for d in data if now - d[0] < self.WINDOW_SECONDS]
|
|
72
|
+
cache.set(key, data, timeout=self.WINDOW_SECONDS)
|
|
73
|
+
if len(data) < 5:
|
|
74
|
+
return None
|
|
75
|
+
total = len(data)
|
|
76
|
+
ratio_404 = sum(1 for (_, _, st, _) in data if st == 404) / total
|
|
77
|
+
hits = sum(
|
|
78
|
+
any(k in path.lower() for k in settings.AIWAF_MALICIOUS_KEYWORDS)
|
|
79
|
+
for (_, path, _, _) in data
|
|
80
|
+
)
|
|
81
|
+
avg_rt = np.mean([rt for (_, _, _, rt) in data]) if data else 0.0
|
|
82
|
+
intervals = [
|
|
83
|
+
data[i][0] - data[i-1][0] for i in range(1, total)
|
|
84
|
+
]
|
|
85
|
+
avg_iv = np.mean(intervals) if intervals else 0.0
|
|
86
|
+
X = np.array([[total, ratio_404, hits, avg_rt, avg_iv]], dtype=float)
|
|
87
|
+
if MODEL.predict(X)[0] == -1:
|
|
88
|
+
BlacklistManager.block(ip, "AI anomaly")
|
|
89
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class HoneypotMiddleware(MiddlewareMixin):
|
|
94
|
+
def process_view(self, request, view_func, view_args, view_kwargs):
|
|
95
|
+
trap = request.POST.get(settings.AIWAF_HONEYPOT_FIELD, "")
|
|
96
|
+
if trap:
|
|
97
|
+
ip = get_ip(request)
|
|
98
|
+
BlacklistManager.block(ip, "HONEYPOT triggered")
|
|
99
|
+
return JsonResponse({"error": "bot_detected"}, status=403)
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class UUIDTamperMiddleware(MiddlewareMixin):
|
|
104
|
+
def process_view(self, request, view_func, view_args, view_kwargs):
|
|
105
|
+
uid = view_kwargs.get("uuid")
|
|
106
|
+
if not uid:
|
|
107
|
+
return None
|
|
108
|
+
ip = get_ip(request)
|
|
109
|
+
app_label = view_kwargs.get("app_label") or view_func.__module__.split('.')[0]
|
|
110
|
+
app_config = apps.get_app_config(app_label)
|
|
111
|
+
for Model in app_config.get_models():
|
|
112
|
+
if Model.objects.filter(pk=uid).exists():
|
|
113
|
+
return None
|
|
114
|
+
BlacklistManager.block(ip, "UUID tampering")
|
|
115
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
aiwaf/models.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
class FeatureSample(models.Model):
|
|
4
|
+
ip = models.GenericIPAddressField(db_index=True)
|
|
5
|
+
path_len = models.IntegerField()
|
|
6
|
+
kw_hits = models.IntegerField()
|
|
7
|
+
resp_time = models.FloatField()
|
|
8
|
+
status_idx = models.IntegerField()
|
|
9
|
+
burst_count = models.IntegerField()
|
|
10
|
+
total_404 = models.IntegerField()
|
|
11
|
+
label = models.CharField(max_length=20, default="unlabeled")
|
|
12
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
13
|
+
|
|
14
|
+
class Meta:
|
|
15
|
+
verbose_name = "WAF Feature Sample"
|
|
16
|
+
verbose_name_plural = "WAF Feature Samples"
|
|
17
|
+
indexes = [
|
|
18
|
+
models.Index(fields=["ip"]),
|
|
19
|
+
models.Index(fields=["created_at"]),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
class BlacklistEntry(models.Model):
|
|
23
|
+
ip_address = models.GenericIPAddressField(unique=True, db_index=True)
|
|
24
|
+
reason = models.CharField(max_length=100)
|
|
25
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
26
|
+
|
|
27
|
+
def __str__(self):
|
|
28
|
+
return f"{self.ip_address} ({self.reason})"
|
|
Binary file
|
aiwaf/storage.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os, csv, gzip, glob
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from .models import FeatureSample
|
|
6
|
+
|
|
7
|
+
DATA_FILE = getattr(settings, "AIWAF_CSV_PATH", "access_samples.csv")
|
|
8
|
+
CSV_HEADER = [
|
|
9
|
+
"ip","path_len","kw_hits","resp_time",
|
|
10
|
+
"status_idx","burst_count","total_404","label"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
class CsvFeatureStore:
|
|
14
|
+
@staticmethod
|
|
15
|
+
def persist_rows(rows):
|
|
16
|
+
new_file = not os.path.exists(DATA_FILE)
|
|
17
|
+
with open(DATA_FILE, "a", newline="", encoding="utf-8") as f:
|
|
18
|
+
w = csv.writer(f)
|
|
19
|
+
if new_file:
|
|
20
|
+
w.writerow(CSV_HEADER)
|
|
21
|
+
w.writerows(rows)
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def load_matrix():
|
|
25
|
+
if not os.path.exists(DATA_FILE):
|
|
26
|
+
return np.empty((0,6))
|
|
27
|
+
df = pd.read_csv(
|
|
28
|
+
DATA_FILE,
|
|
29
|
+
names=CSV_HEADER,
|
|
30
|
+
skiprows=1,
|
|
31
|
+
engine="python",
|
|
32
|
+
on_bad_lines="skip"
|
|
33
|
+
)
|
|
34
|
+
feature_cols = CSV_HEADER[1:7]
|
|
35
|
+
df[feature_cols] = df[feature_cols].apply(pd.to_numeric, errors="coerce").fillna(0)
|
|
36
|
+
return df[feature_cols].to_numpy()
|
|
37
|
+
|
|
38
|
+
class DbFeatureStore:
|
|
39
|
+
@staticmethod
|
|
40
|
+
def persist_rows(rows):
|
|
41
|
+
objs = []
|
|
42
|
+
for ip,pl,kw,rt,si,bc,t404,label in rows:
|
|
43
|
+
objs.append(FeatureSample(
|
|
44
|
+
ip=ip, path_len=pl, kw_hits=kw,
|
|
45
|
+
resp_time=rt, status_idx=si,
|
|
46
|
+
burst_count=bc, total_404=t404,
|
|
47
|
+
label=label
|
|
48
|
+
))
|
|
49
|
+
FeatureSample.objects.bulk_create(objs, ignore_conflicts=True)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def load_matrix():
|
|
53
|
+
qs = FeatureSample.objects.all().values_list(
|
|
54
|
+
"path_len","kw_hits","resp_time","status_idx","burst_count","total_404"
|
|
55
|
+
)
|
|
56
|
+
return np.array(list(qs), dtype=float)
|
|
57
|
+
|
|
58
|
+
def get_store():
|
|
59
|
+
if getattr(settings, "AIWAF_FEATURE_STORE", "csv") == "db":
|
|
60
|
+
return DbFeatureStore
|
|
61
|
+
return CsvFeatureStore
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from django import template
|
|
2
|
+
from django.utils.html import format_html
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
|
|
5
|
+
register = template.Library()
|
|
6
|
+
|
|
7
|
+
@register.simple_tag
|
|
8
|
+
def honeypot_field(field_name=None):
|
|
9
|
+
|
|
10
|
+
name = field_name or getattr(settings, "AIWAF_HONEYPOT_FIELD", "hp_field")
|
|
11
|
+
return format_html(
|
|
12
|
+
'<input type="text" name="{}" hidden autocomplete="off" tabindex="-1" />',
|
|
13
|
+
name
|
|
14
|
+
)
|
aiwaf/trainer.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# aiwaf/trainer.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import glob
|
|
5
|
+
import gzip
|
|
6
|
+
import re
|
|
7
|
+
import joblib
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from .models import BlacklistEntry
|
|
11
|
+
import pandas as pd
|
|
12
|
+
from sklearn.ensemble import IsolationForest
|
|
13
|
+
from django.conf import settings
|
|
14
|
+
from django.apps import apps
|
|
15
|
+
|
|
16
|
+
LOG_PATH = settings.AIWAF_ACCESS_LOG
|
|
17
|
+
MODEL_PATH = os.path.join(
|
|
18
|
+
os.path.dirname(__file__),
|
|
19
|
+
"resources",
|
|
20
|
+
"model.pkl"
|
|
21
|
+
)
|
|
22
|
+
MALICIOUS_KEYWORDS = [".php", "xmlrpc", "wp-", ".env", ".git", ".bak", "conflg", "shell", "filemanager"]
|
|
23
|
+
STATUS_CODES = ["200", "403", "404", "500"]
|
|
24
|
+
_LOG_RX = re.compile(
|
|
25
|
+
r'(\d+\.\d+\.\d+\.\d+).*\[(.*?)\].*"(?:GET|POST) (.*?) HTTP/.*?" (\d{3}).*?"(.*?)" "(.*?)".*?response-time=(\d+\.\d+)'
|
|
26
|
+
)
|
|
27
|
+
BlacklistedIP = BlacklistEntry.objects.all()
|
|
28
|
+
def _read_all_logs():
|
|
29
|
+
lines = []
|
|
30
|
+
if LOG_PATH and os.path.exists(LOG_PATH):
|
|
31
|
+
with open(LOG_PATH, "r", errors="ignore") as f:
|
|
32
|
+
lines += f.readlines()
|
|
33
|
+
for path in sorted(glob.glob(LOG_PATH + ".*")):
|
|
34
|
+
opener = gzip.open if path.endswith(".gz") else open
|
|
35
|
+
try:
|
|
36
|
+
with opener(path, "rt", errors="ignore") as f:
|
|
37
|
+
lines += f.readlines()
|
|
38
|
+
except OSError:
|
|
39
|
+
continue
|
|
40
|
+
return lines
|
|
41
|
+
|
|
42
|
+
def _parse(line):
|
|
43
|
+
m = _LOG_RX.search(line)
|
|
44
|
+
if not m:
|
|
45
|
+
return None
|
|
46
|
+
ip, ts_str, path, status, ref, ua, rt = m.groups()
|
|
47
|
+
try:
|
|
48
|
+
ts = datetime.strptime(ts_str.split()[0], "%d/%b/%Y:%H:%M:%S")
|
|
49
|
+
except ValueError:
|
|
50
|
+
return None
|
|
51
|
+
return {
|
|
52
|
+
"ip": ip,
|
|
53
|
+
"timestamp": ts,
|
|
54
|
+
"path": path,
|
|
55
|
+
"status": status,
|
|
56
|
+
"ua": ua,
|
|
57
|
+
"response_time": float(rt),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def train():
|
|
62
|
+
raw = _read_all_logs()
|
|
63
|
+
if not raw:
|
|
64
|
+
print("No log lines found – check AIWAF_ACCESS_LOG")
|
|
65
|
+
return
|
|
66
|
+
parsed = []
|
|
67
|
+
ip_404 = defaultdict(int)
|
|
68
|
+
ip_times = defaultdict(list)
|
|
69
|
+
for ln in raw:
|
|
70
|
+
rec = _parse(ln)
|
|
71
|
+
if not rec:
|
|
72
|
+
continue
|
|
73
|
+
parsed.append(rec)
|
|
74
|
+
ip_times[rec["ip"]].append(rec["timestamp"])
|
|
75
|
+
if rec["status"] == "404":
|
|
76
|
+
ip_404[rec["ip"]] += 1
|
|
77
|
+
blocked = []
|
|
78
|
+
for ip, count in ip_404.items():
|
|
79
|
+
if count >= 6:
|
|
80
|
+
obj, created = BlacklistEntry.objects.get_or_create(
|
|
81
|
+
ip_address=ip,
|
|
82
|
+
defaults={"reason": "Excessive 404s (≥6)"}
|
|
83
|
+
)
|
|
84
|
+
if created:
|
|
85
|
+
blocked.append(ip)
|
|
86
|
+
if blocked:
|
|
87
|
+
print(f"Auto‑blocked {len(blocked)} IPs for ≥6 404s: {', '.join(blocked)}")
|
|
88
|
+
rows = []
|
|
89
|
+
for r in parsed:
|
|
90
|
+
ip = r["ip"]
|
|
91
|
+
burst = sum(
|
|
92
|
+
1 for t in ip_times[ip]
|
|
93
|
+
if (r["timestamp"] - t).total_seconds() <= 10
|
|
94
|
+
)
|
|
95
|
+
total404 = ip_404[ip]
|
|
96
|
+
kw_hits = sum(k in r["path"].lower() for k in MALICIOUS_KEYWORDS)
|
|
97
|
+
status_idx = STATUS_CODES.index(r["status"]) if r["status"] in STATUS_CODES else -1
|
|
98
|
+
rows.append([
|
|
99
|
+
len(r["path"]),
|
|
100
|
+
kw_hits,
|
|
101
|
+
r["response_time"],
|
|
102
|
+
status_idx,
|
|
103
|
+
burst,
|
|
104
|
+
total404
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
if not rows:
|
|
108
|
+
print("No entries to train on!")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
df = pd.DataFrame(
|
|
112
|
+
rows,
|
|
113
|
+
columns=[
|
|
114
|
+
"path_len", "kw_hits", "resp_time",
|
|
115
|
+
"status_idx", "burst_count", "total_404"
|
|
116
|
+
]
|
|
117
|
+
).fillna(0).astype(float)
|
|
118
|
+
clf = IsolationForest(contamination=0.01, random_state=42)
|
|
119
|
+
clf.fit(df.values)
|
|
120
|
+
os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
|
|
121
|
+
joblib.dump(clf, MODEL_PATH)
|
|
122
|
+
print(f"Model trained on {len(df)} samples and saved to {MODEL_PATH}")
|
|
123
|
+
|
aiwaf/utils.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import glob
|
|
4
|
+
import gzip
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
_LOG_RX = re.compile(
|
|
8
|
+
r'(\d+\.\d+\.\d+\.\d+).*\[(.*?)\].*"(GET|POST) (.*?) HTTP/.*?" (\d{3}).*?"(.*?)" "(.*?)"'
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
def get_ip(request):
|
|
12
|
+
xff = request.META.get("HTTP_X_FORWARDED_FOR", "")
|
|
13
|
+
if xff:
|
|
14
|
+
return xff.split(",")[0].strip()
|
|
15
|
+
return request.META.get("REMOTE_ADDR", "")
|
|
16
|
+
|
|
17
|
+
def read_rotated_logs(base_path):
|
|
18
|
+
lines = []
|
|
19
|
+
if os.path.exists(base_path):
|
|
20
|
+
with open(base_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
21
|
+
lines.extend(f.readlines())
|
|
22
|
+
for path in sorted(glob.glob(base_path + ".*")):
|
|
23
|
+
opener = gzip.open if path.endswith(".gz") else open
|
|
24
|
+
try:
|
|
25
|
+
with opener(path, "rt", encoding="utf-8", errors="ignore") as f:
|
|
26
|
+
lines.extend(f.readlines())
|
|
27
|
+
except OSError:
|
|
28
|
+
continue
|
|
29
|
+
return lines
|
|
30
|
+
|
|
31
|
+
def parse_log_line(line):
|
|
32
|
+
m = _LOG_RX.search(line)
|
|
33
|
+
if not m:
|
|
34
|
+
return None
|
|
35
|
+
ip, ts_str, _, path, status, ref, ua = m.groups()
|
|
36
|
+
try:
|
|
37
|
+
ts = datetime.strptime(ts_str.split()[0], "%d/%b/%Y:%H:%M:%S")
|
|
38
|
+
except ValueError:
|
|
39
|
+
return None
|
|
40
|
+
rt_m = re.search(r'response-time=(\d+\.\d+)', line)
|
|
41
|
+
rt = float(rt_m.group(1)) if rt_m else 0.0
|
|
42
|
+
return {
|
|
43
|
+
"ip": ip,
|
|
44
|
+
"timestamp": ts,
|
|
45
|
+
"path": path,
|
|
46
|
+
"status": status,
|
|
47
|
+
"referer": ref,
|
|
48
|
+
"user_agent": ua,
|
|
49
|
+
"response_time": rt
|
|
50
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiwaf
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI‑driven pluggable Web Application Firewall for Django (CSV or DB storage)
|
|
5
|
+
Author: Aayush Gauba
|
|
6
|
+
Requires-Dist: django>=3.0
|
|
7
|
+
Requires-Dist: scikit-learn
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: pandas
|
|
10
|
+
Requires-Dist: joblib
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: requires-dist
|
|
13
|
+
Dynamic: summary
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
aiwaf/__init__.py,sha256=nQFpJ1YpX48snzLjEQCf8zD2YNh8v0b_kPTrXx8uBYc,46
|
|
2
|
+
aiwaf/apps.py,sha256=nCez-Ptlv2kaEk5HenA8b1pATz1VfhrHP1344gwcY1A,142
|
|
3
|
+
aiwaf/blacklist_manager.py,sha256=sM6uTH7zD6MOPGb0kzqV2aFut2vxKgft_UVeRJr7klw,392
|
|
4
|
+
aiwaf/middleware.py,sha256=WHoXMtKZ_WpueSPylH4XXpKzM9-cqPDunDM0fuklzg0,4220
|
|
5
|
+
aiwaf/models.py,sha256=GTojMOZ3M5pDDNmpU4o353M3w59jEclzHqJgofHzfBA,1021
|
|
6
|
+
aiwaf/storage.py,sha256=bxCILzzvA1-q6nwclRE8WrfoRhe25H4VrsQDf0hl_lY,1903
|
|
7
|
+
aiwaf/trainer.py,sha256=8eLrq3bOmRle4KDRWnzxnc-Gv6oo5IErqoDGO_v_qG4,3629
|
|
8
|
+
aiwaf/utils.py,sha256=RkEUWhhHy6tOk7V0UYv3cN4xhOR_7aBy9bjhwuV2cdA,1436
|
|
9
|
+
aiwaf/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
aiwaf/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
aiwaf/management/commands/detect_and_train.py,sha256=-o-LZ7QZ5GeJPCekryox1DGXKMmFEkwwrcDsiM166K0,269
|
|
12
|
+
aiwaf/resources/model.pkl,sha256=rCCXH38SJrnaOba2WZrU1LQVzWT34x6bTVkq20XJU-Q,1091129
|
|
13
|
+
aiwaf/template_tags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
aiwaf/template_tags/aiwaf_tags.py,sha256=1KGqeioYmgKACDUiPkykSqI7DLQ6-Ypy1k00weWj9iY,399
|
|
15
|
+
aiwaf-0.1.0.dist-info/METADATA,sha256=NOtlP9C7VA-ElW3ugD0UZ_6NOqRy4fdTiZc1kl99lbE,333
|
|
16
|
+
aiwaf-0.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
17
|
+
aiwaf-0.1.0.dist-info/entry_points.txt,sha256=1ruNtKPkJSI3NcwM8pEsO87YjsTbO44-5cK-mD8TdMU,64
|
|
18
|
+
aiwaf-0.1.0.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
|
|
19
|
+
aiwaf-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aiwaf
|