aiwaf 0.1.9.1.1__tar.gz → 0.1.9.1.3__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.1.1 → aiwaf-0.1.9.1.3}/PKG-INFO +66 -2
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/README.md +63 -1
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/__init__.py +1 -1
- aiwaf-0.1.9.1.3/aiwaf/management/commands/check_dependencies.py +457 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/trainer.py +37 -1
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf.egg-info/PKG-INFO +66 -2
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf.egg-info/SOURCES.txt +1 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf.egg-info/requires.txt +2 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/pyproject.toml +4 -2
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/setup.py +3 -1
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/LICENSE +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/apps.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/blacklist_manager.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/decorators.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/__init__.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/__init__.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/add_exemption.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/add_ipexemption.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/aiwaf_diagnose.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/aiwaf_logging.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/aiwaf_reset.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/clear_cache.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/debug_csv.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/detect_and_train.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/diagnose_blocking.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/regenerate_model.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/setup_models.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/test_exemption.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/management/commands/test_exemption_fix.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/middleware.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/middleware_logger.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/models.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/resources/model.pkl +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/storage.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/templatetags/__init__.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/templatetags/aiwaf_tags.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf/utils.py +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf.egg-info/dependency_links.txt +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/aiwaf.egg-info/top_level.txt +0 -0
- {aiwaf-0.1.9.1.1 → aiwaf-0.1.9.1.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.9.1.
|
|
3
|
+
Version: 0.1.9.1.3
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -14,6 +14,8 @@ Requires-Dist: numpy>=1.21
|
|
|
14
14
|
Requires-Dist: pandas>=1.3
|
|
15
15
|
Requires-Dist: scikit-learn<2.0,>=1.0
|
|
16
16
|
Requires-Dist: joblib>=1.1
|
|
17
|
+
Requires-Dist: packaging>=21.0
|
|
18
|
+
Requires-Dist: requests>=2.25.0
|
|
17
19
|
Dynamic: author
|
|
18
20
|
Dynamic: home-page
|
|
19
21
|
Dynamic: license-file
|
|
@@ -59,7 +61,11 @@ aiwaf/
|
|
|
59
61
|
│ └── dynamic_keywords.json # evolves daily
|
|
60
62
|
├── management/
|
|
61
63
|
│ └── commands/
|
|
62
|
-
│
|
|
64
|
+
│ ├── detect_and_train.py # `python manage.py detect_and_train`
|
|
65
|
+
│ ├── check_dependencies.py # `python manage.py check_dependencies`
|
|
66
|
+
│ ├── add_ipexemption.py # `python manage.py add_ipexemption`
|
|
67
|
+
│ ├── aiwaf_reset.py # `python manage.py aiwaf_reset`
|
|
68
|
+
│ └── aiwaf_logging.py # `python manage.py aiwaf_logging`
|
|
63
69
|
└── LICENSE
|
|
64
70
|
```
|
|
65
71
|
|
|
@@ -104,6 +110,20 @@ aiwaf/
|
|
|
104
110
|
- **Captures response times** for better anomaly detection
|
|
105
111
|
- **Zero configuration** - works out of the box
|
|
106
112
|
|
|
113
|
+
- **Smart Training System**
|
|
114
|
+
AI trainer automatically uses the best available data source:
|
|
115
|
+
- **Primary**: Configured access log files (`AIWAF_ACCESS_LOG`)
|
|
116
|
+
- **Fallback**: Database RequestLog model when files unavailable
|
|
117
|
+
- **Seamless switching** between data sources
|
|
118
|
+
- **Enhanced compatibility** with exemption system
|
|
119
|
+
|
|
120
|
+
- **Dependency Management**
|
|
121
|
+
Built-in dependency checker ensures package compatibility:
|
|
122
|
+
- **Version compatibility** checking (NumPy 2.0 vs pandas, etc.)
|
|
123
|
+
- **Missing dependency** detection
|
|
124
|
+
- **Security vulnerability** scanning
|
|
125
|
+
- **Smart upgrade suggestions** with compatibility validation
|
|
126
|
+
|
|
107
127
|
|
|
108
128
|
**Exempt Path & IP Awareness**
|
|
109
129
|
|
|
@@ -189,6 +209,50 @@ python manage.py aiwaf_reset --blacklist-only
|
|
|
189
209
|
python manage.py aiwaf_reset --exemptions-only
|
|
190
210
|
```
|
|
191
211
|
|
|
212
|
+
### Checking Dependencies
|
|
213
|
+
|
|
214
|
+
Check your project's dependencies for updates and compatibility issues:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Basic dependency check
|
|
218
|
+
python manage.py check_dependencies
|
|
219
|
+
|
|
220
|
+
# JSON format output
|
|
221
|
+
python manage.py check_dependencies --format json
|
|
222
|
+
|
|
223
|
+
# Include security vulnerability scanning
|
|
224
|
+
python manage.py check_dependencies --check-security
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Features:**
|
|
228
|
+
- ✅ **Parses pyproject.toml and requirements.txt**
|
|
229
|
+
- ✅ **Shows current vs latest versions**
|
|
230
|
+
- ✅ **Checks package compatibility** (NumPy 2.0 vs pandas, etc.)
|
|
231
|
+
- ✅ **Detects missing dependencies**
|
|
232
|
+
- ✅ **Security vulnerability scanning** (requires `safety` package)
|
|
233
|
+
- ✅ **Provides upgrade commands**
|
|
234
|
+
|
|
235
|
+
**Example Output:**
|
|
236
|
+
```
|
|
237
|
+
🔍 Checking project dependencies...
|
|
238
|
+
|
|
239
|
+
📊 Summary: 5 packages checked
|
|
240
|
+
✅ Up to date: 2
|
|
241
|
+
⚠️ Outdated: 2
|
|
242
|
+
❌ Not installed: 0
|
|
243
|
+
|
|
244
|
+
⚠️ OUTDATED PACKAGES:
|
|
245
|
+
────────────────────────────────────────
|
|
246
|
+
📦 pandas 1.3.5 → 2.2.2 (constraint: pandas>=1.3)
|
|
247
|
+
📦 numpy 1.21.0 → 1.26.4 (constraint: numpy>=1.21)
|
|
248
|
+
|
|
249
|
+
🔍 Checking package compatibility...
|
|
250
|
+
✅ All packages appear to be compatible!
|
|
251
|
+
|
|
252
|
+
💡 To update outdated packages, run:
|
|
253
|
+
pip install --upgrade pandas numpy
|
|
254
|
+
```
|
|
255
|
+
|
|
192
256
|
This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
|
|
193
257
|
|
|
194
258
|
- **Daily Retraining**
|
|
@@ -38,7 +38,11 @@ aiwaf/
|
|
|
38
38
|
│ └── dynamic_keywords.json # evolves daily
|
|
39
39
|
├── management/
|
|
40
40
|
│ └── commands/
|
|
41
|
-
│
|
|
41
|
+
│ ├── detect_and_train.py # `python manage.py detect_and_train`
|
|
42
|
+
│ ├── check_dependencies.py # `python manage.py check_dependencies`
|
|
43
|
+
│ ├── add_ipexemption.py # `python manage.py add_ipexemption`
|
|
44
|
+
│ ├── aiwaf_reset.py # `python manage.py aiwaf_reset`
|
|
45
|
+
│ └── aiwaf_logging.py # `python manage.py aiwaf_logging`
|
|
42
46
|
└── LICENSE
|
|
43
47
|
```
|
|
44
48
|
|
|
@@ -83,6 +87,20 @@ aiwaf/
|
|
|
83
87
|
- **Captures response times** for better anomaly detection
|
|
84
88
|
- **Zero configuration** - works out of the box
|
|
85
89
|
|
|
90
|
+
- **Smart Training System**
|
|
91
|
+
AI trainer automatically uses the best available data source:
|
|
92
|
+
- **Primary**: Configured access log files (`AIWAF_ACCESS_LOG`)
|
|
93
|
+
- **Fallback**: Database RequestLog model when files unavailable
|
|
94
|
+
- **Seamless switching** between data sources
|
|
95
|
+
- **Enhanced compatibility** with exemption system
|
|
96
|
+
|
|
97
|
+
- **Dependency Management**
|
|
98
|
+
Built-in dependency checker ensures package compatibility:
|
|
99
|
+
- **Version compatibility** checking (NumPy 2.0 vs pandas, etc.)
|
|
100
|
+
- **Missing dependency** detection
|
|
101
|
+
- **Security vulnerability** scanning
|
|
102
|
+
- **Smart upgrade suggestions** with compatibility validation
|
|
103
|
+
|
|
86
104
|
|
|
87
105
|
**Exempt Path & IP Awareness**
|
|
88
106
|
|
|
@@ -168,6 +186,50 @@ python manage.py aiwaf_reset --blacklist-only
|
|
|
168
186
|
python manage.py aiwaf_reset --exemptions-only
|
|
169
187
|
```
|
|
170
188
|
|
|
189
|
+
### Checking Dependencies
|
|
190
|
+
|
|
191
|
+
Check your project's dependencies for updates and compatibility issues:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Basic dependency check
|
|
195
|
+
python manage.py check_dependencies
|
|
196
|
+
|
|
197
|
+
# JSON format output
|
|
198
|
+
python manage.py check_dependencies --format json
|
|
199
|
+
|
|
200
|
+
# Include security vulnerability scanning
|
|
201
|
+
python manage.py check_dependencies --check-security
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Features:**
|
|
205
|
+
- ✅ **Parses pyproject.toml and requirements.txt**
|
|
206
|
+
- ✅ **Shows current vs latest versions**
|
|
207
|
+
- ✅ **Checks package compatibility** (NumPy 2.0 vs pandas, etc.)
|
|
208
|
+
- ✅ **Detects missing dependencies**
|
|
209
|
+
- ✅ **Security vulnerability scanning** (requires `safety` package)
|
|
210
|
+
- ✅ **Provides upgrade commands**
|
|
211
|
+
|
|
212
|
+
**Example Output:**
|
|
213
|
+
```
|
|
214
|
+
🔍 Checking project dependencies...
|
|
215
|
+
|
|
216
|
+
📊 Summary: 5 packages checked
|
|
217
|
+
✅ Up to date: 2
|
|
218
|
+
⚠️ Outdated: 2
|
|
219
|
+
❌ Not installed: 0
|
|
220
|
+
|
|
221
|
+
⚠️ OUTDATED PACKAGES:
|
|
222
|
+
────────────────────────────────────────
|
|
223
|
+
📦 pandas 1.3.5 → 2.2.2 (constraint: pandas>=1.3)
|
|
224
|
+
📦 numpy 1.21.0 → 1.26.4 (constraint: numpy>=1.21)
|
|
225
|
+
|
|
226
|
+
🔍 Checking package compatibility...
|
|
227
|
+
✅ All packages appear to be compatible!
|
|
228
|
+
|
|
229
|
+
💡 To update outdated packages, run:
|
|
230
|
+
pip install --upgrade pandas numpy
|
|
231
|
+
```
|
|
232
|
+
|
|
171
233
|
This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
|
|
172
234
|
|
|
173
235
|
- **Daily Retraining**
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import subprocess
|
|
4
|
+
import re
|
|
5
|
+
from django.core.management.base import BaseCommand
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
import pkg_resources
|
|
8
|
+
import requests
|
|
9
|
+
from packaging import version, specifiers
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Command(BaseCommand):
|
|
13
|
+
help = 'Check project dependencies for updates'
|
|
14
|
+
|
|
15
|
+
def add_arguments(self, parser):
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
'--format',
|
|
18
|
+
choices=['table', 'json'],
|
|
19
|
+
default='table',
|
|
20
|
+
help='Output format (default: table)'
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
'--check-security',
|
|
24
|
+
action='store_true',
|
|
25
|
+
help='Also check for known security vulnerabilities'
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
'--check-compatibility',
|
|
29
|
+
action='store_true',
|
|
30
|
+
default=True,
|
|
31
|
+
help='Check for package compatibility issues (default: True)'
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def handle(self, *args, **options):
|
|
35
|
+
self.stdout.write(self.style.SUCCESS('🔍 Checking project dependencies...\n'))
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
dependencies = self.get_project_dependencies()
|
|
39
|
+
if not dependencies:
|
|
40
|
+
self.stdout.write(self.style.WARNING('No dependencies found to check.'))
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
results = []
|
|
44
|
+
for dep_name, current_constraint in dependencies.items():
|
|
45
|
+
result = self.check_package(dep_name, current_constraint)
|
|
46
|
+
results.append(result)
|
|
47
|
+
|
|
48
|
+
self.display_results(results, options['format'])
|
|
49
|
+
|
|
50
|
+
if options['check_compatibility']:
|
|
51
|
+
self.check_compatibility(results)
|
|
52
|
+
|
|
53
|
+
if options['check_security']:
|
|
54
|
+
self.check_security_vulnerabilities(dependencies)
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
self.stdout.write(self.style.ERROR(f'Error checking dependencies: {e}'))
|
|
58
|
+
|
|
59
|
+
def get_project_dependencies(self):
|
|
60
|
+
"""Get dependencies from pyproject.toml or requirements.txt"""
|
|
61
|
+
dependencies = {}
|
|
62
|
+
|
|
63
|
+
# Check pyproject.toml first
|
|
64
|
+
pyproject_path = os.path.join(settings.BASE_DIR, 'pyproject.toml')
|
|
65
|
+
if os.path.exists(pyproject_path):
|
|
66
|
+
dependencies.update(self.parse_pyproject_toml(pyproject_path))
|
|
67
|
+
|
|
68
|
+
# Check requirements.txt
|
|
69
|
+
requirements_path = os.path.join(settings.BASE_DIR, 'requirements.txt')
|
|
70
|
+
if os.path.exists(requirements_path):
|
|
71
|
+
dependencies.update(self.parse_requirements_txt(requirements_path))
|
|
72
|
+
|
|
73
|
+
return dependencies
|
|
74
|
+
|
|
75
|
+
def parse_pyproject_toml(self, filepath):
|
|
76
|
+
"""Parse dependencies from pyproject.toml"""
|
|
77
|
+
dependencies = {}
|
|
78
|
+
try:
|
|
79
|
+
with open(filepath, 'r') as f:
|
|
80
|
+
content = f.read()
|
|
81
|
+
|
|
82
|
+
# Simple regex to extract dependencies
|
|
83
|
+
import re
|
|
84
|
+
deps_match = re.search(r'dependencies\s*=\s*\[(.*?)\]', content, re.DOTALL)
|
|
85
|
+
if deps_match:
|
|
86
|
+
deps_text = deps_match.group(1)
|
|
87
|
+
for line in deps_text.split(','):
|
|
88
|
+
line = line.strip().strip('"\'')
|
|
89
|
+
if line and not line.startswith('#'):
|
|
90
|
+
if '>=' in line or '==' in line or '~=' in line or '<' in line:
|
|
91
|
+
name = re.split(r'[><=~!]', line)[0].strip()
|
|
92
|
+
dependencies[name] = line
|
|
93
|
+
else:
|
|
94
|
+
dependencies[line] = line
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self.stdout.write(self.style.WARNING(f'Could not parse pyproject.toml: {e}'))
|
|
97
|
+
|
|
98
|
+
return dependencies
|
|
99
|
+
|
|
100
|
+
def parse_requirements_txt(self, filepath):
|
|
101
|
+
"""Parse dependencies from requirements.txt"""
|
|
102
|
+
dependencies = {}
|
|
103
|
+
try:
|
|
104
|
+
with open(filepath, 'r') as f:
|
|
105
|
+
for line in f:
|
|
106
|
+
line = line.strip()
|
|
107
|
+
if line and not line.startswith('#'):
|
|
108
|
+
if '>=' in line or '==' in line or '~=' in line or '<' in line:
|
|
109
|
+
name = re.split(r'[><=~!]', line)[0].strip()
|
|
110
|
+
dependencies[name] = line
|
|
111
|
+
else:
|
|
112
|
+
dependencies[line] = line
|
|
113
|
+
except Exception as e:
|
|
114
|
+
self.stdout.write(self.style.WARNING(f'Could not parse requirements.txt: {e}'))
|
|
115
|
+
|
|
116
|
+
return dependencies
|
|
117
|
+
|
|
118
|
+
def check_package(self, package_name, constraint):
|
|
119
|
+
"""Check a single package for updates"""
|
|
120
|
+
try:
|
|
121
|
+
# Get currently installed version
|
|
122
|
+
try:
|
|
123
|
+
installed_version = pkg_resources.get_distribution(package_name).version
|
|
124
|
+
except pkg_resources.DistributionNotFound:
|
|
125
|
+
return {
|
|
126
|
+
'name': package_name,
|
|
127
|
+
'constraint': constraint,
|
|
128
|
+
'installed': 'Not installed',
|
|
129
|
+
'latest': 'Unknown',
|
|
130
|
+
'status': 'not_installed',
|
|
131
|
+
'update_available': False
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Get latest version from PyPI
|
|
135
|
+
latest_version = self.get_latest_version(package_name)
|
|
136
|
+
|
|
137
|
+
# Determine status
|
|
138
|
+
status = 'up_to_date'
|
|
139
|
+
update_available = False
|
|
140
|
+
|
|
141
|
+
if latest_version and version.parse(installed_version) < version.parse(latest_version):
|
|
142
|
+
status = 'outdated'
|
|
143
|
+
update_available = True
|
|
144
|
+
elif latest_version and version.parse(installed_version) > version.parse(latest_version):
|
|
145
|
+
status = 'ahead'
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
'name': package_name,
|
|
149
|
+
'constraint': constraint,
|
|
150
|
+
'installed': installed_version,
|
|
151
|
+
'latest': latest_version or 'Unknown',
|
|
152
|
+
'status': status,
|
|
153
|
+
'update_available': update_available
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
return {
|
|
158
|
+
'name': package_name,
|
|
159
|
+
'constraint': constraint,
|
|
160
|
+
'installed': 'Error',
|
|
161
|
+
'latest': 'Error',
|
|
162
|
+
'status': 'error',
|
|
163
|
+
'update_available': False,
|
|
164
|
+
'error': str(e)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def get_latest_version(self, package_name):
|
|
168
|
+
"""Get latest version from PyPI"""
|
|
169
|
+
try:
|
|
170
|
+
response = requests.get(f'https://pypi.org/pypi/{package_name}/json', timeout=10)
|
|
171
|
+
if response.status_code == 200:
|
|
172
|
+
data = response.json()
|
|
173
|
+
return data['info']['version']
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
def display_results(self, results, format_type):
|
|
179
|
+
"""Display results in specified format"""
|
|
180
|
+
if format_type == 'json':
|
|
181
|
+
import json
|
|
182
|
+
self.stdout.write(json.dumps(results, indent=2))
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
# Table format
|
|
186
|
+
up_to_date = [r for r in results if r['status'] == 'up_to_date']
|
|
187
|
+
outdated = [r for r in results if r['status'] == 'outdated']
|
|
188
|
+
not_installed = [r for r in results if r['status'] == 'not_installed']
|
|
189
|
+
errors = [r for r in results if r['status'] == 'error']
|
|
190
|
+
|
|
191
|
+
# Summary
|
|
192
|
+
total = len(results)
|
|
193
|
+
self.stdout.write(f"📊 Summary: {total} packages checked")
|
|
194
|
+
self.stdout.write(f" ✅ Up to date: {len(up_to_date)}")
|
|
195
|
+
self.stdout.write(f" ⚠️ Outdated: {len(outdated)}")
|
|
196
|
+
self.stdout.write(f" ❌ Not installed: {len(not_installed)}")
|
|
197
|
+
self.stdout.write(f" 🔥 Errors: {len(errors)}\n")
|
|
198
|
+
|
|
199
|
+
# Outdated packages
|
|
200
|
+
if outdated:
|
|
201
|
+
self.stdout.write(self.style.WARNING("⚠️ OUTDATED PACKAGES:"))
|
|
202
|
+
self.stdout.write("─" * 80)
|
|
203
|
+
for pkg in outdated:
|
|
204
|
+
self.stdout.write(
|
|
205
|
+
f"📦 {pkg['name']:<20} {pkg['installed']:<12} → {pkg['latest']:<12} "
|
|
206
|
+
f"(constraint: {pkg['constraint']})"
|
|
207
|
+
)
|
|
208
|
+
self.stdout.write("")
|
|
209
|
+
|
|
210
|
+
# Up to date packages
|
|
211
|
+
if up_to_date:
|
|
212
|
+
self.stdout.write(self.style.SUCCESS("✅ UP TO DATE PACKAGES:"))
|
|
213
|
+
self.stdout.write("─" * 80)
|
|
214
|
+
for pkg in up_to_date:
|
|
215
|
+
self.stdout.write(
|
|
216
|
+
f"📦 {pkg['name']:<20} {pkg['installed']:<12} "
|
|
217
|
+
f"(constraint: {pkg['constraint']})"
|
|
218
|
+
)
|
|
219
|
+
self.stdout.write("")
|
|
220
|
+
|
|
221
|
+
# Not installed packages
|
|
222
|
+
if not_installed:
|
|
223
|
+
self.stdout.write(self.style.ERROR("❌ NOT INSTALLED PACKAGES:"))
|
|
224
|
+
self.stdout.write("─" * 80)
|
|
225
|
+
for pkg in not_installed:
|
|
226
|
+
self.stdout.write(f"📦 {pkg['name']:<20} (constraint: {pkg['constraint']})")
|
|
227
|
+
self.stdout.write("")
|
|
228
|
+
|
|
229
|
+
# Errors
|
|
230
|
+
if errors:
|
|
231
|
+
self.stdout.write(self.style.ERROR("🔥 PACKAGES WITH ERRORS:"))
|
|
232
|
+
self.stdout.write("─" * 80)
|
|
233
|
+
for pkg in errors:
|
|
234
|
+
error_msg = pkg.get('error', 'Unknown error')
|
|
235
|
+
self.stdout.write(f"📦 {pkg['name']:<20} Error: {error_msg}")
|
|
236
|
+
self.stdout.write("")
|
|
237
|
+
|
|
238
|
+
# Update command suggestions
|
|
239
|
+
if outdated:
|
|
240
|
+
self.stdout.write(self.style.HTTP_INFO("💡 To update outdated packages, run:"))
|
|
241
|
+
update_cmd = "pip install --upgrade " + " ".join([pkg['name'] for pkg in outdated])
|
|
242
|
+
self.stdout.write(f" {update_cmd}")
|
|
243
|
+
|
|
244
|
+
def check_compatibility(self, results):
|
|
245
|
+
"""Check for package compatibility issues"""
|
|
246
|
+
self.stdout.write(self.style.HTTP_INFO("\n🔍 Checking package compatibility..."))
|
|
247
|
+
|
|
248
|
+
# Known compatibility rules
|
|
249
|
+
compatibility_rules = self.get_compatibility_rules()
|
|
250
|
+
conflicts = []
|
|
251
|
+
warnings = []
|
|
252
|
+
|
|
253
|
+
# Check for version conflicts
|
|
254
|
+
for pkg in results:
|
|
255
|
+
if pkg['status'] in ['up_to_date', 'outdated', 'ahead']:
|
|
256
|
+
pkg_name = pkg['name'].lower()
|
|
257
|
+
installed_ver = pkg['installed']
|
|
258
|
+
latest_ver = pkg['latest']
|
|
259
|
+
|
|
260
|
+
# Check against compatibility rules
|
|
261
|
+
for rule in compatibility_rules:
|
|
262
|
+
if rule['package'] == pkg_name:
|
|
263
|
+
conflict = self.check_package_rule(pkg, rule, results)
|
|
264
|
+
if conflict:
|
|
265
|
+
if conflict['severity'] == 'error':
|
|
266
|
+
conflicts.append(conflict)
|
|
267
|
+
else:
|
|
268
|
+
warnings.append(conflict)
|
|
269
|
+
|
|
270
|
+
# Check for missing dependencies of packages
|
|
271
|
+
missing_deps = self.check_missing_dependencies(results)
|
|
272
|
+
conflicts.extend(missing_deps)
|
|
273
|
+
|
|
274
|
+
# Display results
|
|
275
|
+
if conflicts:
|
|
276
|
+
self.stdout.write(self.style.ERROR("\n❌ COMPATIBILITY CONFLICTS:"))
|
|
277
|
+
self.stdout.write("─" * 80)
|
|
278
|
+
for conflict in conflicts:
|
|
279
|
+
self.stdout.write(f"🚨 {conflict['message']}")
|
|
280
|
+
if 'suggestion' in conflict:
|
|
281
|
+
self.stdout.write(f" 💡 {conflict['suggestion']}")
|
|
282
|
+
self.stdout.write("")
|
|
283
|
+
|
|
284
|
+
if warnings:
|
|
285
|
+
self.stdout.write(self.style.WARNING("\n⚠️ COMPATIBILITY WARNINGS:"))
|
|
286
|
+
self.stdout.write("─" * 80)
|
|
287
|
+
for warning in warnings:
|
|
288
|
+
self.stdout.write(f"⚠️ {warning['message']}")
|
|
289
|
+
if 'suggestion' in warning:
|
|
290
|
+
self.stdout.write(f" 💡 {warning['suggestion']}")
|
|
291
|
+
self.stdout.write("")
|
|
292
|
+
|
|
293
|
+
if not conflicts and not warnings:
|
|
294
|
+
self.stdout.write(self.style.SUCCESS("✅ All packages appear to be compatible!"))
|
|
295
|
+
|
|
296
|
+
def get_compatibility_rules(self):
|
|
297
|
+
"""Define known compatibility rules between packages"""
|
|
298
|
+
return [
|
|
299
|
+
{
|
|
300
|
+
'package': 'numpy',
|
|
301
|
+
'conflicts_with': [
|
|
302
|
+
{
|
|
303
|
+
'package': 'pandas',
|
|
304
|
+
'numpy_versions': '>=2.0',
|
|
305
|
+
'pandas_versions': '<2.1',
|
|
306
|
+
'message': 'NumPy 2.0+ requires pandas 2.1+ for compatibility',
|
|
307
|
+
'severity': 'error'
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
'package': 'scikit-learn',
|
|
313
|
+
'conflicts_with': [
|
|
314
|
+
{
|
|
315
|
+
'package': 'numpy',
|
|
316
|
+
'sklearn_versions': '>=1.3',
|
|
317
|
+
'numpy_versions': '<1.19',
|
|
318
|
+
'message': 'scikit-learn 1.3+ requires NumPy 1.19+',
|
|
319
|
+
'severity': 'error'
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
'package': 'pandas',
|
|
323
|
+
'sklearn_versions': '>=1.2',
|
|
324
|
+
'pandas_versions': '<1.0',
|
|
325
|
+
'message': 'scikit-learn 1.2+ works best with pandas 1.0+',
|
|
326
|
+
'severity': 'warning'
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
'package': 'pandas',
|
|
332
|
+
'conflicts_with': [
|
|
333
|
+
{
|
|
334
|
+
'package': 'numpy',
|
|
335
|
+
'pandas_versions': '>=2.0',
|
|
336
|
+
'numpy_versions': '<1.22',
|
|
337
|
+
'message': 'pandas 2.0+ requires NumPy 1.22+',
|
|
338
|
+
'severity': 'error'
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
'package': 'django',
|
|
344
|
+
'conflicts_with': [
|
|
345
|
+
{
|
|
346
|
+
'package': 'pandas',
|
|
347
|
+
'django_versions': '>=4.0',
|
|
348
|
+
'pandas_versions': '<1.3',
|
|
349
|
+
'message': 'Django 4.0+ works better with pandas 1.3+',
|
|
350
|
+
'severity': 'warning'
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
def check_package_rule(self, pkg, rule, all_results):
|
|
357
|
+
"""Check a specific package against compatibility rules"""
|
|
358
|
+
pkg_name = pkg['name'].lower()
|
|
359
|
+
|
|
360
|
+
for conflict_rule in rule.get('conflicts_with', []):
|
|
361
|
+
# Find the conflicting package in results
|
|
362
|
+
conflicting_pkg = None
|
|
363
|
+
for other_pkg in all_results:
|
|
364
|
+
if other_pkg['name'].lower() == conflict_rule['package']:
|
|
365
|
+
conflicting_pkg = other_pkg
|
|
366
|
+
break
|
|
367
|
+
|
|
368
|
+
if not conflicting_pkg or conflicting_pkg['status'] == 'not_installed':
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
# Check version constraints
|
|
372
|
+
pkg_version = pkg['installed']
|
|
373
|
+
other_version = conflicting_pkg['installed']
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
# Check if this package version matches the conflict rule
|
|
377
|
+
pkg_constraint_key = f"{pkg_name}_versions"
|
|
378
|
+
other_constraint_key = f"{conflict_rule['package']}_versions"
|
|
379
|
+
|
|
380
|
+
pkg_constraint = conflict_rule.get(pkg_constraint_key)
|
|
381
|
+
other_constraint = conflict_rule.get(other_constraint_key)
|
|
382
|
+
|
|
383
|
+
pkg_matches = self.version_matches_constraint(pkg_version, pkg_constraint) if pkg_constraint else True
|
|
384
|
+
other_matches = self.version_matches_constraint(other_version, other_constraint) if other_constraint else True
|
|
385
|
+
|
|
386
|
+
if pkg_matches and other_matches:
|
|
387
|
+
suggestion = f"Consider updating {conflict_rule['package']} or {pkg_name}"
|
|
388
|
+
if conflict_rule['severity'] == 'error':
|
|
389
|
+
suggestion = f"REQUIRED: Update {conflict_rule['package']} or downgrade {pkg_name}"
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
'message': f"{pkg_name} {pkg_version} + {conflict_rule['package']} {other_version}: {conflict_rule['message']}",
|
|
393
|
+
'suggestion': suggestion,
|
|
394
|
+
'severity': conflict_rule['severity']
|
|
395
|
+
}
|
|
396
|
+
except Exception:
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
def version_matches_constraint(self, version_str, constraint_str):
|
|
402
|
+
"""Check if a version matches a constraint"""
|
|
403
|
+
try:
|
|
404
|
+
spec = specifiers.SpecifierSet(constraint_str)
|
|
405
|
+
return version.parse(version_str) in spec
|
|
406
|
+
except Exception:
|
|
407
|
+
return False
|
|
408
|
+
|
|
409
|
+
def check_missing_dependencies(self, results):
|
|
410
|
+
"""Check for missing dependencies that packages might need"""
|
|
411
|
+
conflicts = []
|
|
412
|
+
|
|
413
|
+
# Get installed packages
|
|
414
|
+
installed_packages = {pkg['name'].lower(): pkg for pkg in results
|
|
415
|
+
if pkg['status'] in ['up_to_date', 'outdated', 'ahead']}
|
|
416
|
+
|
|
417
|
+
# Check key dependencies
|
|
418
|
+
key_dependencies = {
|
|
419
|
+
'pandas': ['numpy'],
|
|
420
|
+
'scikit-learn': ['numpy', 'joblib'],
|
|
421
|
+
'django': [] # Django has its own dependency management
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
for pkg_name, required_deps in key_dependencies.items():
|
|
425
|
+
if pkg_name in installed_packages:
|
|
426
|
+
for dep in required_deps:
|
|
427
|
+
if dep not in installed_packages:
|
|
428
|
+
conflicts.append({
|
|
429
|
+
'message': f"{pkg_name} requires {dep} but it's not installed",
|
|
430
|
+
'suggestion': f"Install {dep}: pip install {dep}",
|
|
431
|
+
'severity': 'error'
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
return conflicts
|
|
435
|
+
|
|
436
|
+
def check_security_vulnerabilities(self, dependencies):
|
|
437
|
+
"""Check for known security vulnerabilities using safety"""
|
|
438
|
+
self.stdout.write(self.style.HTTP_INFO("\n🔒 Checking for security vulnerabilities..."))
|
|
439
|
+
try:
|
|
440
|
+
# Try to use safety if available
|
|
441
|
+
result = subprocess.run(['safety', 'check', '--json'],
|
|
442
|
+
capture_output=True, text=True, timeout=30)
|
|
443
|
+
if result.returncode == 0:
|
|
444
|
+
import json
|
|
445
|
+
vulns = json.loads(result.stdout)
|
|
446
|
+
if vulns:
|
|
447
|
+
self.stdout.write(self.style.ERROR(f"⚠️ Found {len(vulns)} security vulnerabilities!"))
|
|
448
|
+
for vuln in vulns[:5]: # Show first 5
|
|
449
|
+
self.stdout.write(f" 📦 {vuln.get('package')}: {vuln.get('vulnerability')}")
|
|
450
|
+
else:
|
|
451
|
+
self.stdout.write(self.style.SUCCESS("✅ No known security vulnerabilities found"))
|
|
452
|
+
else:
|
|
453
|
+
self.stdout.write(self.style.WARNING("Could not check vulnerabilities (safety not available)"))
|
|
454
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
455
|
+
self.stdout.write(self.style.WARNING("Security check skipped (install 'safety' package for vulnerability scanning)"))
|
|
456
|
+
except Exception as e:
|
|
457
|
+
self.stdout.write(self.style.WARNING(f"Security check failed: {e}"))
|
|
@@ -67,7 +67,7 @@ def remove_exempt_keywords() -> None:
|
|
|
67
67
|
def _read_all_logs() -> list[str]:
|
|
68
68
|
lines = []
|
|
69
69
|
|
|
70
|
-
# First try to read from main access log
|
|
70
|
+
# First try to read from main access log files
|
|
71
71
|
if LOG_PATH and os.path.exists(LOG_PATH):
|
|
72
72
|
with open(LOG_PATH, "r", errors="ignore") as f:
|
|
73
73
|
lines.extend(f.readlines())
|
|
@@ -79,9 +79,45 @@ def _read_all_logs() -> list[str]:
|
|
|
79
79
|
except OSError:
|
|
80
80
|
continue
|
|
81
81
|
|
|
82
|
+
# If no log files found, fall back to RequestLog model data
|
|
83
|
+
if not lines:
|
|
84
|
+
lines = _get_logs_from_model()
|
|
85
|
+
|
|
82
86
|
return lines
|
|
83
87
|
|
|
84
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
|
+
|
|
85
121
|
def _parse(line: str) -> dict | None:
|
|
86
122
|
m = _LOG_RX.search(line)
|
|
87
123
|
if not m:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.9.1.
|
|
3
|
+
Version: 0.1.9.1.3
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -14,6 +14,8 @@ Requires-Dist: numpy>=1.21
|
|
|
14
14
|
Requires-Dist: pandas>=1.3
|
|
15
15
|
Requires-Dist: scikit-learn<2.0,>=1.0
|
|
16
16
|
Requires-Dist: joblib>=1.1
|
|
17
|
+
Requires-Dist: packaging>=21.0
|
|
18
|
+
Requires-Dist: requests>=2.25.0
|
|
17
19
|
Dynamic: author
|
|
18
20
|
Dynamic: home-page
|
|
19
21
|
Dynamic: license-file
|
|
@@ -59,7 +61,11 @@ aiwaf/
|
|
|
59
61
|
│ └── dynamic_keywords.json # evolves daily
|
|
60
62
|
├── management/
|
|
61
63
|
│ └── commands/
|
|
62
|
-
│
|
|
64
|
+
│ ├── detect_and_train.py # `python manage.py detect_and_train`
|
|
65
|
+
│ ├── check_dependencies.py # `python manage.py check_dependencies`
|
|
66
|
+
│ ├── add_ipexemption.py # `python manage.py add_ipexemption`
|
|
67
|
+
│ ├── aiwaf_reset.py # `python manage.py aiwaf_reset`
|
|
68
|
+
│ └── aiwaf_logging.py # `python manage.py aiwaf_logging`
|
|
63
69
|
└── LICENSE
|
|
64
70
|
```
|
|
65
71
|
|
|
@@ -104,6 +110,20 @@ aiwaf/
|
|
|
104
110
|
- **Captures response times** for better anomaly detection
|
|
105
111
|
- **Zero configuration** - works out of the box
|
|
106
112
|
|
|
113
|
+
- **Smart Training System**
|
|
114
|
+
AI trainer automatically uses the best available data source:
|
|
115
|
+
- **Primary**: Configured access log files (`AIWAF_ACCESS_LOG`)
|
|
116
|
+
- **Fallback**: Database RequestLog model when files unavailable
|
|
117
|
+
- **Seamless switching** between data sources
|
|
118
|
+
- **Enhanced compatibility** with exemption system
|
|
119
|
+
|
|
120
|
+
- **Dependency Management**
|
|
121
|
+
Built-in dependency checker ensures package compatibility:
|
|
122
|
+
- **Version compatibility** checking (NumPy 2.0 vs pandas, etc.)
|
|
123
|
+
- **Missing dependency** detection
|
|
124
|
+
- **Security vulnerability** scanning
|
|
125
|
+
- **Smart upgrade suggestions** with compatibility validation
|
|
126
|
+
|
|
107
127
|
|
|
108
128
|
**Exempt Path & IP Awareness**
|
|
109
129
|
|
|
@@ -189,6 +209,50 @@ python manage.py aiwaf_reset --blacklist-only
|
|
|
189
209
|
python manage.py aiwaf_reset --exemptions-only
|
|
190
210
|
```
|
|
191
211
|
|
|
212
|
+
### Checking Dependencies
|
|
213
|
+
|
|
214
|
+
Check your project's dependencies for updates and compatibility issues:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Basic dependency check
|
|
218
|
+
python manage.py check_dependencies
|
|
219
|
+
|
|
220
|
+
# JSON format output
|
|
221
|
+
python manage.py check_dependencies --format json
|
|
222
|
+
|
|
223
|
+
# Include security vulnerability scanning
|
|
224
|
+
python manage.py check_dependencies --check-security
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Features:**
|
|
228
|
+
- ✅ **Parses pyproject.toml and requirements.txt**
|
|
229
|
+
- ✅ **Shows current vs latest versions**
|
|
230
|
+
- ✅ **Checks package compatibility** (NumPy 2.0 vs pandas, etc.)
|
|
231
|
+
- ✅ **Detects missing dependencies**
|
|
232
|
+
- ✅ **Security vulnerability scanning** (requires `safety` package)
|
|
233
|
+
- ✅ **Provides upgrade commands**
|
|
234
|
+
|
|
235
|
+
**Example Output:**
|
|
236
|
+
```
|
|
237
|
+
🔍 Checking project dependencies...
|
|
238
|
+
|
|
239
|
+
📊 Summary: 5 packages checked
|
|
240
|
+
✅ Up to date: 2
|
|
241
|
+
⚠️ Outdated: 2
|
|
242
|
+
❌ Not installed: 0
|
|
243
|
+
|
|
244
|
+
⚠️ OUTDATED PACKAGES:
|
|
245
|
+
────────────────────────────────────────
|
|
246
|
+
📦 pandas 1.3.5 → 2.2.2 (constraint: pandas>=1.3)
|
|
247
|
+
📦 numpy 1.21.0 → 1.26.4 (constraint: numpy>=1.21)
|
|
248
|
+
|
|
249
|
+
🔍 Checking package compatibility...
|
|
250
|
+
✅ All packages appear to be compatible!
|
|
251
|
+
|
|
252
|
+
💡 To update outdated packages, run:
|
|
253
|
+
pip install --upgrade pandas numpy
|
|
254
|
+
```
|
|
255
|
+
|
|
192
256
|
This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
|
|
193
257
|
|
|
194
258
|
- **Daily Retraining**
|
|
@@ -24,6 +24,7 @@ aiwaf/management/commands/add_ipexemption.py
|
|
|
24
24
|
aiwaf/management/commands/aiwaf_diagnose.py
|
|
25
25
|
aiwaf/management/commands/aiwaf_logging.py
|
|
26
26
|
aiwaf/management/commands/aiwaf_reset.py
|
|
27
|
+
aiwaf/management/commands/check_dependencies.py
|
|
27
28
|
aiwaf/management/commands/clear_cache.py
|
|
28
29
|
aiwaf/management/commands/debug_csv.py
|
|
29
30
|
aiwaf/management/commands/detect_and_train.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aiwaf"
|
|
3
|
-
version = "0.1.9.1.
|
|
3
|
+
version = "0.1.9.1.3"
|
|
4
4
|
description = "AI-powered Web Application Firewall"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.8"
|
|
@@ -11,5 +11,7 @@ dependencies = [
|
|
|
11
11
|
"numpy>=1.21",
|
|
12
12
|
"pandas>=1.3",
|
|
13
13
|
"scikit-learn>=1.0,<2.0",
|
|
14
|
-
"joblib>=1.1"
|
|
14
|
+
"joblib>=1.1",
|
|
15
|
+
"packaging>=21.0",
|
|
16
|
+
"requests>=2.25.0"
|
|
15
17
|
]
|
|
@@ -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.1.
|
|
12
|
+
version="0.1.9.1.3",
|
|
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",
|
|
@@ -24,6 +24,8 @@ setup(
|
|
|
24
24
|
"pandas>=1.3",
|
|
25
25
|
"scikit-learn>=1.0,<2.0",
|
|
26
26
|
"joblib>=1.1",
|
|
27
|
+
"packaging>=21.0",
|
|
28
|
+
"requests>=2.25.0",
|
|
27
29
|
],
|
|
28
30
|
include_package_data=True,
|
|
29
31
|
package_data={
|
|
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
|