Merge branch 'main' into project/systemd-persistence-scanner
This commit is contained in:
commit
cccd383b30
|
|
@ -62,6 +62,12 @@ jobs:
|
|||
- name: ai-threat-detection-backend
|
||||
type: ruff
|
||||
path: PROJECTS/advanced/ai-threat-detection/backend
|
||||
- name: linux-ebpf-security-tracer
|
||||
type: ruff
|
||||
path: PROJECTS/beginner/linux-ebpf-security-tracer
|
||||
- name: dlp-scanner
|
||||
type: ruff
|
||||
path: PROJECTS/intermediate/dlp-scanner
|
||||
# Biome (frontend)
|
||||
- name: bug-bounty-platform-frontend
|
||||
type: biome
|
||||
|
|
@ -154,9 +160,9 @@ jobs:
|
|||
# Nim Setup
|
||||
- name: Setup Nim
|
||||
if: matrix.type == 'nim'
|
||||
uses: jiro4989/setup-nim-action@v2
|
||||
with:
|
||||
nim-version: '2.2.x'
|
||||
run: |
|
||||
curl https://nim-lang.org/choosenim/init.sh -sSf | bash -s -- -y
|
||||
echo "$HOME/.nimble/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install nph
|
||||
if: matrix.type == 'nim'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
# ©AngelaMos | 2026
|
||||
# alembic.ini
|
||||
#
|
||||
# Alembic database migration configuration
|
||||
#
|
||||
# Points script_location to the alembic/ directory and sets
|
||||
# the default asyncpg connection URL (overridden at runtime
|
||||
# by env.py from settings). Configures Python logging with
|
||||
# WARN level for root and sqlalchemy.engine, INFO for
|
||||
# alembic, all routed to a stderr console handler with
|
||||
# generic format. Connects to alembic/env.py,
|
||||
# alembic/versions/, app/config
|
||||
|
||||
[alembic]
|
||||
script_location = alembic
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
env.py
|
||||
|
||||
Alembic migration environment with async PostgreSQL engine
|
||||
support
|
||||
|
||||
Configures SQLModel.metadata as the target for autogenerate,
|
||||
imports model registrations (ModelMetadata, ThreatEvent) to
|
||||
ensure table definitions are available. run_migrations_
|
||||
offline generates SQL scripts without a connection. run_
|
||||
migrations_online creates an async engine with NullPool and
|
||||
executes migrations via run_sync. Mode is selected based on
|
||||
context.is_offline_mode()
|
||||
|
||||
Connects to:
|
||||
app/config - settings.database_url
|
||||
app/models - SQLModel table registrations
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__main__.py
|
||||
|
||||
Uvicorn entry point for the AngelusVigil API server
|
||||
|
||||
Launches app.main:app via uvicorn using host, port, and
|
||||
reload settings from app.config.settings
|
||||
|
||||
Connects to:
|
||||
config.py - settings.host, settings.port, settings.debug
|
||||
main.py - ASGI application instance
|
||||
"""
|
||||
|
||||
import uvicorn
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
API package containing FastAPI route modules for health,
|
||||
ingest, threats, stats, models, and websocket endpoints
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
deps.py
|
||||
|
||||
FastAPI dependency injection providers for API key
|
||||
authentication and async database sessions
|
||||
|
||||
require_api_key checks the X-API-Key header against
|
||||
settings.api_key, returning 401 if mismatched (no-op
|
||||
when api_key is unconfigured). get_session yields an
|
||||
AsyncSession from the app-level session_factory stored
|
||||
on app.state during lifespan initialization
|
||||
|
||||
Connects to:
|
||||
config.py - settings.api_key
|
||||
factory.py - app.state.session_factory
|
||||
api/ - injected via Depends() in route handlers
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
health.py
|
||||
|
||||
Health and readiness probe endpoints for container
|
||||
orchestration
|
||||
|
||||
GET /health returns liveness status with uptime_seconds
|
||||
and pipeline_running flag. GET /ready checks database
|
||||
connectivity (SELECT 1) and Redis ping, reports
|
||||
models_loaded status, and returns 503 if any dependency
|
||||
is down. Both endpoints read from app.state set during
|
||||
lifespan
|
||||
|
||||
Connects to:
|
||||
factory.py - app.state.startup_time,
|
||||
pipeline_running, db_engine
|
||||
core/redis_manager - redis_manager.ping()
|
||||
"""
|
||||
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
ingest.py
|
||||
|
||||
Batch log ingestion endpoint for pushing raw log lines
|
||||
into the detection pipeline
|
||||
|
||||
POST /ingest/batch accepts a BatchIngestRequest (list of
|
||||
raw log line strings), pushes each into the pipeline's
|
||||
raw_queue via put_nowait, stops on QueueFull, and returns
|
||||
the count of successfully queued lines. Protected by
|
||||
require_api_key dependency
|
||||
|
||||
Connects to:
|
||||
deps.py - require_api_key
|
||||
core/ingestion/
|
||||
pipeline.py - pipeline.raw_queue
|
||||
factory.py - app.state.pipeline
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,6 +1,28 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
models_api.py
|
||||
|
||||
ML model status and retraining endpoints
|
||||
|
||||
GET /models/status returns models_loaded flag, detection
|
||||
_mode (hybrid or rules), and active model metadata from
|
||||
the database. POST /models/retrain dispatches a
|
||||
background retraining job that loads stored ThreatEvents,
|
||||
labels them using review_label or score thresholds
|
||||
(SCORE_ATTACK_THRESHOLD 0.5, SCORE_NORMAL_CEILING 0.3),
|
||||
supplements with synthetic data if below MIN_TRAINING_
|
||||
SAMPLES (200), runs TrainingOrchestrator, and writes
|
||||
model metadata. _fallback_synthetic spawns a subprocess
|
||||
CLI train command when no real events exist
|
||||
|
||||
Connects to:
|
||||
config.py - settings.model_dir, ensemble
|
||||
weights
|
||||
models/model_metadata - ModelMetadata queries
|
||||
models/threat_event - ThreatEvent training data
|
||||
ml/orchestrator - TrainingOrchestrator
|
||||
ml/synthetic - generate_mixed_dataset
|
||||
cli/main - _write_metadata
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,6 +1,19 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
stats.py
|
||||
|
||||
Threat statistics endpoint returning aggregated metrics
|
||||
for a configurable time window
|
||||
|
||||
GET /stats accepts a range query parameter (default
|
||||
"24h") and delegates to stats_service.get_stats for
|
||||
database aggregation, returning a StatsResponse
|
||||
|
||||
Connects to:
|
||||
deps.py - get_session dependency
|
||||
schemas/stats - StatsResponse model
|
||||
services/stats_
|
||||
service - get_stats business logic
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
|
|
|||
|
|
@ -1,6 +1,23 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
threats.py
|
||||
|
||||
Threat event CRUD endpoints with filtering and
|
||||
pagination
|
||||
|
||||
GET /threats lists events with optional severity,
|
||||
source_ip, since/until datetime filters, and limit/
|
||||
offset pagination (max 100). GET /threats/{threat_id}
|
||||
fetches a single event by UUID, returning 404 if not
|
||||
found. Both delegate to threat_service for database
|
||||
queries
|
||||
|
||||
Connects to:
|
||||
deps.py - get_session dependency
|
||||
schemas/threats - ThreatEventResponse,
|
||||
ThreatListResponse
|
||||
services/threat_
|
||||
service - get_threats, get_threat_by_id
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
websocket.py
|
||||
|
||||
WebSocket endpoint streaming real-time threat alerts via
|
||||
Redis pub/sub relay
|
||||
|
||||
WS /ws/alerts accepts a client connection, subscribes to
|
||||
the ALERTS_CHANNEL via a per-client Redis pubsub instance,
|
||||
and runs two concurrent tasks: _relay forwards published
|
||||
messages as WebSocket text frames, _receive drains client
|
||||
messages until disconnect. asyncio.wait with FIRST_
|
||||
COMPLETED cancels the other task on disconnect, then
|
||||
unsubscribes and closes the pubsub. Per-client subscribers
|
||||
ensure correct multi-worker behavior
|
||||
|
||||
Connects to:
|
||||
core/alerts - ALERTS_CHANNEL constant
|
||||
core/redis_manager- redis_manager.client
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,6 +1,27 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
config.py
|
||||
|
||||
Pydantic-settings application configuration loaded from
|
||||
environment variables and .env file
|
||||
|
||||
Defines the Settings model with defaults for: server
|
||||
(host 0.0.0.0, port 8000, debug, log_level), database
|
||||
(postgresql+asyncpg URL), Redis URL, GeoIP MaxMind
|
||||
database path, nginx log path, pipeline queue sizes
|
||||
(raw 1000, parsed 500, feature 200, alert 100), batch
|
||||
settings (size 32, timeout 50ms), and ML configuration
|
||||
(model_dir, detection_mode, ensemble weights for
|
||||
autoencoder/random-forest/isolation-forest at 0.40/0.40
|
||||
/0.20, ae_threshold_percentile 99.5, MLflow tracking
|
||||
URI). Exports a module-level singleton settings instance
|
||||
|
||||
Connects to:
|
||||
factory.py - consumed in lifespan and create_app
|
||||
__main__.py - server host/port/reload
|
||||
core/ingestion/ - queue sizes, log path
|
||||
core/detection/ - model_dir, ensemble weights
|
||||
core/enrichment/ - geoip_db_path
|
||||
"""
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Core package containing detection, ingestion, feature
|
||||
engineering, enrichment, and alert subsystems
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Alerts package defining the ALERTS_CHANNEL constant for
|
||||
Redis pub/sub real-time threat notification
|
||||
"""
|
||||
|
||||
ALERTS_CHANNEL = "alerts"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,26 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
dispatcher.py
|
||||
|
||||
Alert dispatcher routing scored threat events to storage,
|
||||
Redis pub/sub, and structured logging
|
||||
|
||||
AlertDispatcher.dispatch receives a ScoredRequest from the
|
||||
pipeline, classifies severity via classify_severity, logs
|
||||
every event, and for MEDIUM+ severity persists to
|
||||
PostgreSQL via create_threat_event and publishes a
|
||||
WebSocketAlert JSON payload to the ALERTS_CHANNEL for
|
||||
real-time WebSocket relay
|
||||
|
||||
Connects to:
|
||||
core/alerts/__init__ - ALERTS_CHANNEL
|
||||
core/detection/
|
||||
ensemble - classify_severity
|
||||
core/ingestion/
|
||||
pipeline - ScoredRequest dataclass
|
||||
schemas/websocket - WebSocketAlert model
|
||||
services/threat_
|
||||
service - create_threat_event
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Detection package containing the rule engine, ONNX
|
||||
inference engine, and ensemble scoring utilities
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,27 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
ensemble.py
|
||||
|
||||
Score normalization, fusion, and severity classification
|
||||
utilities for the ML ensemble
|
||||
|
||||
normalize_ae_score maps autoencoder reconstruction error
|
||||
to [0,1] using 2x threshold scaling. normalize_if_score
|
||||
inverts sklearn isolation forest scores to [0,1].
|
||||
fuse_scores computes a weighted average across available
|
||||
model scores. blend_scores combines ML ensemble and rule
|
||||
engine scores with configurable ml_weight (default 0.7).
|
||||
classify_severity maps unified score to HIGH (>=0.7),
|
||||
MEDIUM (>=0.5), or LOW
|
||||
|
||||
Connects to:
|
||||
core/detection/
|
||||
inference - raw model scores passed to normalizers
|
||||
core/detection/
|
||||
rules - classify_severity used for rule results
|
||||
core/ingestion/
|
||||
pipeline - fuse_scores and blend_scores in
|
||||
scoring stage
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,27 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
inference.py
|
||||
|
||||
ONNX-based inference engine for the 3-model ML ensemble
|
||||
|
||||
InferenceEngine loads autoencoder (ae.onnx), random
|
||||
forest (rf.onnx), and isolation forest (if.onnx) sessions
|
||||
plus RobustScaler parameters (scaler.json) and anomaly
|
||||
threshold (threshold.json) from a model directory. predict
|
||||
runs all 3 models on a batch of feature vectors: applies
|
||||
_scale_for_ae to normalize autoencoder input, computes
|
||||
reconstruction MSE for ae scores, extracts attack
|
||||
probability from skl2onnx RF output format via _extract_
|
||||
rf_proba, and returns raw IF decision scores. Returns
|
||||
None when models are unavailable. Each ONNX session uses
|
||||
single-threaded execution (inter/intra_op_num_threads=1)
|
||||
|
||||
Connects to:
|
||||
config.py - settings.model_dir
|
||||
factory.py - _load_inference_engine at startup
|
||||
core/ingestion/
|
||||
pipeline - batch inference in scoring stage
|
||||
ml/export_onnx - produces the ONNX model files
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -1,6 +1,32 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
rules.py
|
||||
|
||||
Cold-start rule-based detection engine inspired by
|
||||
ModSecurity Core Rule Set
|
||||
|
||||
RuleEngine.score_request evaluates requests against 7
|
||||
regex-based _PatternRules (LOG4SHELL 0.95, COMMAND_
|
||||
INJECTION 0.90, SQL_INJECTION 0.85, XSS 0.80, FILE_
|
||||
INCLUSION 0.75, SSRF 0.70, PATH_TRAVERSAL 0.60),
|
||||
double-encoding detection (0.40), scanner user-agent
|
||||
signature matching (0.35), and 2 _ThresholdRules
|
||||
(RATE_ANOMALY >100 req/min 0.30, HIGH_ERROR_RATE >50%
|
||||
0.25). Final score takes the highest match plus 0.05
|
||||
boost per additional rule, capped at 1.0. Returns a
|
||||
RuleResult with threat_score, severity, matched_rules,
|
||||
and component_scores
|
||||
|
||||
Connects to:
|
||||
core/features/
|
||||
patterns - compiled regex patterns (SQLI,
|
||||
XSS, LOG4SHELL, etc.)
|
||||
core/features/
|
||||
signatures - SCANNER_USER_AGENTS list
|
||||
core/detection/
|
||||
ensemble - classify_severity
|
||||
core/ingestion/
|
||||
parsers - ParsedLogEntry
|
||||
"""
|
||||
|
||||
import re
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Enrichment package providing GeoIP lookup services for
|
||||
IP-to-location resolution
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
geoip.py
|
||||
|
||||
Async GeoIP lookup service backed by MaxMind GeoLite2-City
|
||||
database
|
||||
|
||||
GeoIPService loads a .mmdb reader on init, returning None
|
||||
for missing databases. lookup resolves an IP to a GeoResult
|
||||
(country ISO code, city, lat, lon), skipping private/
|
||||
loopback addresses and unknown entries. swap_reader
|
||||
atomically replaces the database reader for hot-reload
|
||||
after .mmdb updates. All blocking geoip2 calls run in a
|
||||
thread via asyncio.to_thread
|
||||
|
||||
Connects to:
|
||||
config.py - settings.geoip_db_path
|
||||
factory.py - initialized and closed in
|
||||
lifespan
|
||||
core/ingestion/
|
||||
pipeline - lookup called in feature_worker
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Feature engineering package with per-request extraction,
|
||||
windowed aggregation, encoding, patterns, and signatures
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,27 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
aggregator.py
|
||||
|
||||
Per-IP sliding window feature aggregator backed by Redis
|
||||
sorted sets
|
||||
|
||||
WindowAggregator.record_and_aggregate records each request
|
||||
into 7 Redis sorted sets (requests, paths, statuses, UAs,
|
||||
sizes, methods, depths) keyed by IP, trims entries older
|
||||
than KEY_TTL (900s), and computes 12 windowed features in
|
||||
a single pipelined round-trip: req_count at 1m/5m/10m
|
||||
windows, error_rate_5m (4xx/5xx ratio), unique_paths_5m,
|
||||
unique_uas_10m, method_entropy_5m (Shannon), avg_response
|
||||
_size_5m, status_diversity_5m (distinct codes), path_depth
|
||||
_variance_5m, and inter_request_time mean/std in ms.
|
||||
Members are MD5-hashed for deduplication where needed
|
||||
|
||||
Connects to:
|
||||
core/ingestion/
|
||||
pipeline - called in feature_worker stage
|
||||
core/features/
|
||||
mappings - WINDOWED_FEATURE_NAMES defines the
|
||||
12 windowed feature keys
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
|
|
|
|||
|
|
@ -1,6 +1,23 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
encoder.py
|
||||
|
||||
Feature vector encoder transforming a combined feature
|
||||
dict into a 35-element float vector for ML inference
|
||||
|
||||
encode_for_inference iterates FEATURE_ORDER, applying
|
||||
boolean 0/1 encoding for 7 BOOLEAN_FEATURES, ordinal
|
||||
lookup via CATEGORICAL_ENCODERS for http_method, status_
|
||||
class, and file_extension, deterministic country code
|
||||
encoding via _encode_country (A-Z ordinal to 1-676), and
|
||||
direct float cast for all numeric features
|
||||
|
||||
Connects to:
|
||||
core/features/
|
||||
mappings - FEATURE_ORDER, BOOLEAN_FEATURES,
|
||||
CATEGORICAL_ENCODERS
|
||||
core/ingestion/
|
||||
pipeline - called after feature merge
|
||||
"""
|
||||
|
||||
from app.core.features.mappings import (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,31 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
extractor.py
|
||||
|
||||
Stateless per-request feature extraction producing 23
|
||||
features from a parsed log entry
|
||||
|
||||
extract_request_features computes: http_method, path_depth,
|
||||
path_entropy (Shannon), path_length, query_string_length,
|
||||
query_param_count, has_encoded_chars, has_double_encoding,
|
||||
status_code, status_class (Nxx), response_size, hour_of_
|
||||
day, day_of_week, is_weekend, ua_length, ua_entropy,
|
||||
is_known_bot, is_known_scanner, has_attack_pattern,
|
||||
special_char_ratio, file_extension, country_code, and
|
||||
is_private_ip. Pattern detection uses compiled regexes
|
||||
from patterns module, bot/scanner detection uses signature
|
||||
sets
|
||||
|
||||
Connects to:
|
||||
core/features/
|
||||
patterns - ATTACK_COMBINED, DOUBLE_ENCODED,
|
||||
ENCODED_CHARS
|
||||
core/features/
|
||||
signatures - BOT_USER_AGENTS, SCANNER_USER_AGENTS
|
||||
core/ingestion/
|
||||
parsers - ParsedLogEntry input
|
||||
core/ingestion/
|
||||
pipeline - called in feature_worker stage
|
||||
"""
|
||||
|
||||
import ipaddress
|
||||
|
|
|
|||
|
|
@ -1,6 +1,25 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
mappings.py
|
||||
|
||||
Feature encoding mappings, canonical feature order, and
|
||||
classification constants for the 35-feature ML input spec
|
||||
|
||||
Defines METHOD_MAP (7 HTTP verbs), STATUS_CLASS_MAP (5
|
||||
response classes), EXTENSION_MAP (25 file extensions),
|
||||
FEATURE_ORDER (35-element canonical list: 23 per-request
|
||||
+ 12 windowed), CATEGORICAL_ENCODERS routing http_method/
|
||||
status_class/file_extension to their ordinal maps,
|
||||
WINDOWED_FEATURE_NAMES (last 12 features), and BOOLEAN_
|
||||
FEATURES (7 binary flags). These mappings ensure training
|
||||
and inference use identical encoding
|
||||
|
||||
Connects to:
|
||||
core/features/
|
||||
encoder - FEATURE_ORDER, BOOLEAN_FEATURES,
|
||||
CATEGORICAL_ENCODERS
|
||||
ml/data_loader - FEATURE_ORDER for column alignment
|
||||
ml/synthetic - FEATURE_ORDER for sample generation
|
||||
"""
|
||||
|
||||
METHOD_MAP: dict[str, int] = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,32 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
patterns.py
|
||||
|
||||
Compiled regex patterns for web attack detection covering
|
||||
7 OWASP categories plus encoding anomalies
|
||||
|
||||
Defines case-insensitive compiled patterns for: SQLI
|
||||
(union select, sleep, benchmark, information_schema, hex
|
||||
literals, comment injection), XSS (script tags, event
|
||||
handlers, javascript/vbscript URIs, DOM sinks like
|
||||
document.cookie/write, eval/alert/prompt), PATH_TRAVERSAL
|
||||
(../ sequences, %2e encoding, sensitive file paths like
|
||||
etc/passwd, .git/config, .env), COMMAND_INJECTION
|
||||
(semicolon/pipe chaining to shell commands, $() and
|
||||
backtick substitution, ${} expansion), FILE_INCLUSION
|
||||
(php://, file://, data://, phar:// wrapper schemes), SSRF
|
||||
(cloud metadata IPs 169.254.169.254, localhost with paths,
|
||||
dict:// and gopher://), LOG4SHELL (${jndi, ${lower, ${:-
|
||||
patterns). Also provides ENCODED_CHARS, DOUBLE_ENCODED for
|
||||
evasion detection, and ATTACK_COMBINED unioning all 7
|
||||
patterns
|
||||
|
||||
Connects to:
|
||||
core/features/
|
||||
extractor - ATTACK_COMBINED, DOUBLE_ENCODED,
|
||||
ENCODED_CHARS
|
||||
core/detection/
|
||||
rules - individual patterns for scored rules
|
||||
"""
|
||||
|
||||
import re
|
||||
|
|
|
|||
|
|
@ -1,6 +1,23 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
signatures.py
|
||||
|
||||
User-agent signature sets for bot and security scanner
|
||||
detection
|
||||
|
||||
BOT_USER_AGENTS contains 34 lowercase search engine and
|
||||
crawler identifiers (googlebot, bingbot, gptbot, claudebot,
|
||||
etc.) for benign bot classification. SCANNER_USER_AGENTS
|
||||
contains 41 lowercase security tool signatures (nikto,
|
||||
sqlmap, nmap, burp, nuclei, metasploit, hydra, etc.) for
|
||||
hostile scanner detection. Both are frozensets matched via
|
||||
substring search against lowercased user-agent strings
|
||||
|
||||
Connects to:
|
||||
core/features/
|
||||
extractor - is_known_bot, is_known_scanner features
|
||||
core/detection/
|
||||
rules - SCANNER_USER_AGENTS for UA rule scoring
|
||||
"""
|
||||
|
||||
BOT_USER_AGENTS: frozenset[str] = frozenset({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Ingestion package with log parsing, file tailing, and the
|
||||
four-stage async processing pipeline
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,28 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
parsers.py
|
||||
|
||||
Nginx combined-format log line parser with fast string-
|
||||
split primary and compiled regex fallback
|
||||
|
||||
ParsedLogEntry is a frozen slotted dataclass holding ip,
|
||||
timestamp, method, path, query_string, status_code,
|
||||
response_size, referer, user_agent, and raw_line.
|
||||
parse_combined tries _parse_split first (splitting on
|
||||
quote boundaries for speed), falling back to _parse_regex
|
||||
with a compiled _COMBINED_RE pattern. Both extract the
|
||||
request line, split URI into path and query_string, parse
|
||||
timestamp via strptime with timezone, and handle dash
|
||||
placeholders for size and referer
|
||||
|
||||
Connects to:
|
||||
core/ingestion/
|
||||
pipeline - parse_combined in parse_worker stage
|
||||
core/detection/
|
||||
rules - ParsedLogEntry consumed by RuleEngine
|
||||
core/features/
|
||||
extractor - ParsedLogEntry consumed by feature
|
||||
extraction
|
||||
"""
|
||||
|
||||
import re
|
||||
|
|
|
|||
|
|
@ -1,6 +1,34 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
pipeline.py
|
||||
|
||||
Four-stage async pipeline transforming raw nginx log lines
|
||||
into scored threat candidates
|
||||
|
||||
Stage 1 (_parse_worker): parses raw lines via parse_
|
||||
combined into ParsedLogEntry. Stage 2 (_feature_worker):
|
||||
enriches with GeoIP lookup, extracts 23 per-request
|
||||
features, aggregates 12 windowed features via Redis-backed
|
||||
WindowAggregator, and encodes the merged 35-dim float
|
||||
vector. Stage 3 (_detection_worker): scores via RuleEngine,
|
||||
optionally runs ML ensemble inference (normalize AE/IF
|
||||
scores, fuse with configurable weights, blend with rule
|
||||
score at 0.7 ML weight). Stage 4 (_dispatch_worker):
|
||||
forwards ScoredRequests via the on_result callback. Stages
|
||||
are connected by sized asyncio.Queues with poison-pill
|
||||
shutdown propagation. EnrichedRequest and ScoredRequest
|
||||
dataclasses carry data between stages
|
||||
|
||||
Connects to:
|
||||
core/ingestion/parsers - parse_combined
|
||||
core/enrichment/geoip - GeoIPService.lookup
|
||||
core/features/extractor - extract_request_features
|
||||
core/features/aggregator - WindowAggregator
|
||||
core/features/encoder - encode_for_inference
|
||||
core/detection/rules - RuleEngine.score_request
|
||||
core/detection/inference - InferenceEngine.predict
|
||||
core/detection/ensemble - normalize/fuse/blend scores
|
||||
core/alerts/dispatcher - on_result callback
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,6 +1,26 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
tailer.py
|
||||
|
||||
Watchdog-based nginx log file tailer with rotation
|
||||
detection pushing raw lines into an asyncio queue
|
||||
|
||||
_LogHandler extends FileSystemEventHandler to tail a single
|
||||
target file: _open_target seeks to EOF, on_modified reads
|
||||
new lines via _read_new_lines and checks inode changes for
|
||||
rotation, on_moved handles rename-based rotation (access
|
||||
.log -> access.log.1), on_created handles new-file
|
||||
rotation. Lines are pushed via call_soon_threadsafe into
|
||||
the asyncio queue, with QueueFull drops logged. LogTailer
|
||||
wraps _LogHandler with a PollingObserver (2s interval)
|
||||
watching the target's parent directory, providing start/
|
||||
stop lifecycle and is_active property
|
||||
|
||||
Connects to:
|
||||
factory.py - started/stopped in lifespan
|
||||
core/ingestion/
|
||||
pipeline - feeds pipeline.raw_queue
|
||||
config.py - settings.nginx_log_path
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
redis_manager.py
|
||||
|
||||
Async Redis connection lifecycle manager with module-level
|
||||
singleton
|
||||
|
||||
RedisManager wraps redis.asyncio connection creation
|
||||
(from_url with decode_responses), graceful close, client
|
||||
property access, and PING health check. The module
|
||||
exports redis_manager as a singleton used by factory
|
||||
lifespan, alert dispatcher, and websocket endpoint
|
||||
|
||||
Connects to:
|
||||
config.py - settings.redis_url
|
||||
factory.py - connect/disconnect in lifespan
|
||||
api/websocket - client for pub/sub
|
||||
api/health - ping() for readiness probe
|
||||
"""
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
|
|
|
|||
|
|
@ -1,6 +1,36 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
factory.py
|
||||
|
||||
FastAPI application factory with async lifespan managing
|
||||
database, Redis, pipeline, and ML model initialization
|
||||
|
||||
lifespan creates the async SQLAlchemy engine and session
|
||||
factory, runs SQLModel.metadata.create_all, connects
|
||||
Redis, initializes GeoIPService, constructs the Alert
|
||||
Dispatcher, attempts to load the ONNX InferenceEngine
|
||||
(falling back to rules-only mode), builds the Pipeline
|
||||
with configured queue sizes and ensemble weights, starts
|
||||
the LogTailer if the nginx log directory exists, and
|
||||
stores all components on app.state. On shutdown it stops
|
||||
the tailer, pipeline, GeoIP, Redis, and disposes the DB
|
||||
engine. _load_inference_engine lazily imports onnxruntime
|
||||
-backed InferenceEngine, returning None if the dependency
|
||||
is missing or no models exist. create_app assembles the
|
||||
FastAPI instance and mounts all six API routers (health,
|
||||
ingest, threats, stats, models, websocket)
|
||||
|
||||
Connects to:
|
||||
config.py - settings for all config values
|
||||
core/ingestion/pipeline - Pipeline
|
||||
core/ingestion/tailer - LogTailer
|
||||
core/detection/rules - RuleEngine
|
||||
core/detection/inference- InferenceEngine (optional)
|
||||
core/alerts/dispatcher - AlertDispatcher
|
||||
core/enrichment/geoip - GeoIPService
|
||||
core/redis_manager - redis_manager
|
||||
api/ - all route modules
|
||||
models/ - SQLModel registration
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
main.py
|
||||
|
||||
ASGI application instance created by the factory
|
||||
|
||||
Connects to:
|
||||
factory.py - create_app builds the FastAPI instance
|
||||
"""
|
||||
|
||||
from app.factory import create_app
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Models package exporting SQLModel table classes for
|
||||
ThreatEvent and ModelMetadata
|
||||
"""
|
||||
|
||||
from app.models.model_metadata import ModelMetadata
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
base.py
|
||||
|
||||
Abstract SQLModel base class providing UUID primary key
|
||||
and timezone-aware created_at timestamp
|
||||
|
||||
TimestampedModel defines id as a uuid4 primary key and
|
||||
created_at as a DateTime(timezone=True) column with
|
||||
CURRENT_TIMESTAMP server default. All domain models
|
||||
inherit from this base
|
||||
|
||||
Connects to:
|
||||
models/threat_event - ThreatEvent inherits
|
||||
models/model_metadata - ModelMetadata inherits
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
model_metadata.py
|
||||
|
||||
SQLModel table tracking ML model versions, training
|
||||
metrics, and deployment status
|
||||
|
||||
ModelMetadata stores model_type, version, training_samples,
|
||||
metrics (JSON), artifact_path, is_active flag, optional
|
||||
mlflow_run_id, threshold, and notes. A partial index on
|
||||
model_type filtered by is_active=TRUE enables fast lookup
|
||||
of the currently deployed model per type
|
||||
|
||||
Connects to:
|
||||
models/base - inherits TimestampedModel
|
||||
api/models_api - queried for /models/status,
|
||||
written after retrain
|
||||
cli/main - _write_metadata inserts records
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Index, JSON, text
|
||||
|
|
|
|||
|
|
@ -1,6 +1,25 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
threat_event.py
|
||||
|
||||
SQLModel table for detected threat events with full
|
||||
request context and ML metadata
|
||||
|
||||
ThreatEvent stores source_ip, request_method, request_path,
|
||||
status_code, response_size, user_agent, threat_score,
|
||||
severity, component_scores (JSON), geo fields (country,
|
||||
city, lat, lon), feature_vector (JSON float array),
|
||||
matched_rules (JSON string array), model_version,
|
||||
reviewed flag, and review_label for analyst feedback.
|
||||
Indexed on created_at, source_ip, severity, threat_score,
|
||||
and a partial index on reviewed=FALSE for triage queries
|
||||
|
||||
Connects to:
|
||||
models/base - inherits TimestampedModel
|
||||
services/threat_service - CRUD operations
|
||||
api/models_api - training data source for
|
||||
retrain
|
||||
core/alerts/dispatcher - persisted on MEDIUM+ severity
|
||||
"""
|
||||
|
||||
from sqlalchemy import (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Pydantic schemas package for API request/response
|
||||
validation across stats, threats, and websocket endpoints
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
stats.py
|
||||
|
||||
Pydantic response models for the /stats endpoint
|
||||
|
||||
SeverityBreakdown holds high/medium/low threat counts.
|
||||
IPStatEntry and PathStatEntry pair a source_ip or path
|
||||
with a count. StatsResponse aggregates time_range,
|
||||
threats_stored, threats_detected, severity_breakdown,
|
||||
top_source_ips (top 10), and top_attacked_paths (top 10)
|
||||
|
||||
Connects to:
|
||||
api/stats - StatsResponse as response_model
|
||||
services/stats_service - constructs StatsResponse
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
threats.py
|
||||
|
||||
Pydantic response models for the /threats endpoints
|
||||
|
||||
GeoInfo holds optional country, city, lat, lon from GeoIP
|
||||
lookups. ThreatEventResponse is the full event schema with
|
||||
UUID id, timestamps, request details, threat_score,
|
||||
severity (Literal HIGH/MEDIUM/LOW), component_scores,
|
||||
geo info, matched_rules, model_version, and review status
|
||||
(from_attributes enabled for ORM conversion). Threat
|
||||
ListResponse wraps paginated items with total/limit/offset
|
||||
|
||||
Connects to:
|
||||
api/threats - response_model for list and
|
||||
detail endpoints
|
||||
services/threat_service - _to_response builds these
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
websocket.py
|
||||
|
||||
Pydantic model for real-time WebSocket threat alert
|
||||
payloads
|
||||
|
||||
WebSocketAlert carries event type (Literal "threat"),
|
||||
timestamp, source_ip, request_method, request_path,
|
||||
threat_score, severity, and component_scores. Serialized
|
||||
via model_dump_json for Redis pub/sub broadcast
|
||||
|
||||
Connects to:
|
||||
core/alerts/dispatcher - constructs and publishes alerts
|
||||
api/websocket - relayed to connected clients
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Service layer package with threat event CRUD and
|
||||
statistics aggregation business logic
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
stats_service.py
|
||||
|
||||
Threat statistics aggregation service computing time-
|
||||
windowed metrics from stored events
|
||||
|
||||
get_stats accepts a time_range string (1h, 6h, 24h, 7d,
|
||||
30d) mapped to timedeltas via _RANGE_MAP, queries threat
|
||||
events since the cutoff, and returns a StatsResponse with
|
||||
total count, severity breakdown (HIGH/MEDIUM/LOW counts
|
||||
via GROUP BY), top 10 source IPs, and top 10 attacked
|
||||
paths ordered by frequency
|
||||
|
||||
Connects to:
|
||||
models/threat_event - ThreatEvent queries
|
||||
schemas/stats - StatsResponse, SeverityBreakdown,
|
||||
IPStatEntry, PathStatEntry
|
||||
api/stats - called from GET /stats endpoint
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, UTC
|
||||
|
|
|
|||
|
|
@ -1,6 +1,29 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
threat_service.py
|
||||
|
||||
Threat event CRUD service for database persistence and
|
||||
retrieval
|
||||
|
||||
get_threats builds a filtered, paginated query with
|
||||
optional severity, source_ip, since/until datetime
|
||||
filters, ordered by created_at DESC. get_threat_by_id
|
||||
fetches a single event by UUID. create_threat_event
|
||||
persists a ScoredRequest as a ThreatEvent with full
|
||||
request context, GeoIP data, feature vector, matched
|
||||
rules, and severity classification. _to_response converts
|
||||
ThreatEvent ORM models to ThreatEventResponse schemas
|
||||
with nested GeoInfo
|
||||
|
||||
Connects to:
|
||||
models/threat_event - ThreatEvent table operations
|
||||
schemas/threats - ThreatEventResponse, GeoInfo,
|
||||
ThreatListResponse
|
||||
core/detection/ensemble - classify_severity for create
|
||||
core/ingestion/pipeline - ScoredRequest input type
|
||||
api/threats - called from list/detail
|
||||
endpoints
|
||||
core/alerts/dispatcher - called on MEDIUM+ dispatch
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
CLI package providing the Typer-based vigil command-line
|
||||
interface for server, training, replay, and diagnostics
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,29 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
main.py
|
||||
|
||||
Typer CLI application with serve, train, replay, config,
|
||||
and health commands
|
||||
|
||||
serve launches uvicorn with configurable host/port/reload.
|
||||
train loads CSIC 2010 dataset and/or synthetic data, runs
|
||||
TrainingOrchestrator, exports ONNX models, and writes
|
||||
metadata to the database via _write_metadata (creates an
|
||||
async engine, calls save_model_metadata). replay sends
|
||||
historical log lines in batches to a running server's
|
||||
/ingest/batch endpoint via httpx. config prints all
|
||||
settings with secrets redacted (_redact_url masks
|
||||
credentials in database URLs). health pings /health and
|
||||
displays status, uptime, and pipeline state
|
||||
|
||||
Connects to:
|
||||
app/config - settings for serve defaults
|
||||
app/main - uvicorn target "app.main:app"
|
||||
ml/orchestrator - TrainingOrchestrator for train
|
||||
ml/data_loader - load_csic_dataset for CSIC data
|
||||
ml/synthetic - generate_mixed_dataset
|
||||
ml/metadata - save_model_metadata
|
||||
api/ingest - /ingest/batch for replay
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
ML package with autoencoder, classifier training, ONNX
|
||||
export, data loading, experiment tracking, and validation
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
autoencoder.py
|
||||
|
||||
PyTorch symmetric autoencoder for HTTP request anomaly
|
||||
detection
|
||||
|
||||
ThreatAutoencoder has a 35->24->12->6 encoder and 6->12
|
||||
->24->35 decoder with BatchNorm1d, LeakyReLU(0.2), and
|
||||
Dropout(0.2) between each linear layer. Trained on normal
|
||||
traffic only so that high reconstruction error (compute_
|
||||
reconstruction_error via per-sample MSE) indicates
|
||||
anomalous requests. encode/decode expose bottleneck access
|
||||
for analysis
|
||||
|
||||
Connects to:
|
||||
ml/export_onnx - exported to ae.onnx
|
||||
ml/orchestrator - trained in _train_autoencoder
|
||||
ml/scaler - input normalized before training
|
||||
"""
|
||||
|
||||
import torch
|
||||
|
|
|
|||
|
|
@ -1,6 +1,28 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
data_loader.py
|
||||
|
||||
CSIC 2010 HTTP dataset loader with feature extraction for
|
||||
ML training
|
||||
|
||||
parse_csic_file reads a CSIC dataset file, splits on HTTP
|
||||
request line boundaries, and produces CSICRequest objects
|
||||
(method, path, query_string, headers, body, label).
|
||||
csic_to_parsed_entry converts CSICRequests to
|
||||
ParsedLogEntrys with synthetic defaults (private IP,
|
||||
random timestamp over 90 days, 200 status). load_csic_
|
||||
dataset loads normal (label=0) and attack (label=1)
|
||||
files, extracts 23 per-request features, zeros 12
|
||||
windowed features, encodes to 35-dim vectors, and returns
|
||||
(X, y) numpy arrays. load_csic_normal loads a single
|
||||
normal-only file
|
||||
|
||||
Connects to:
|
||||
core/features/extractor - extract_request_features
|
||||
core/features/encoder - encode_for_inference
|
||||
core/features/mappings - WINDOWED_FEATURE_NAMES
|
||||
core/ingestion/parsers - ParsedLogEntry
|
||||
cli/main - loaded in train command
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
download_csic.py
|
||||
|
||||
CSIC 2010 dataset downloader with progress display and
|
||||
integrity checking
|
||||
|
||||
download_csic fetches normalTrafficTraining.txt, normal
|
||||
TrafficTest.txt, and anomalousTrafficTest.txt from the
|
||||
Universidad de la Republica GitLab mirror via httpx
|
||||
streaming, writing to data/datasets/csic2010/. Skips
|
||||
files that already exist above MIN_FILE_BYTES (1MB).
|
||||
Shows download progress (percentage or MB), computes
|
||||
SHA-256 via _compute_sha256, and warns on suspiciously
|
||||
small downloads
|
||||
|
||||
Connects to:
|
||||
ml/data_loader - downloaded files consumed by
|
||||
parse_csic_file
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
|
|
|
|||
|
|
@ -1,6 +1,19 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
experiment.py
|
||||
|
||||
MLflow experiment context manager with automatic system
|
||||
metadata logging
|
||||
|
||||
VigilExperiment wraps mlflow.start_run/end_run as a context
|
||||
manager, recording Python version, platform, and git commit
|
||||
hash on entry, and setting status/error tags on exit.
|
||||
Provides log_params, log_metrics (with optional step), and
|
||||
log_artifact convenience methods. _get_git_hash shells out
|
||||
to git rev-parse --short HEAD
|
||||
|
||||
Connects to:
|
||||
ml/orchestrator - used to wrap the full training run
|
||||
"""
|
||||
|
||||
import platform
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
export_onnx.py
|
||||
|
||||
ONNX model export functions for the 3-model ML ensemble
|
||||
|
||||
export_autoencoder converts a PyTorch ThreatAutoencoder to
|
||||
ONNX with dynamic batch dimension, opset 17, constant
|
||||
folding, and named I/O (features/reconstructed). export_
|
||||
random_forest and export_isolation_forest convert sklearn
|
||||
estimators to ONNX via skl2onnx with FloatTensorType input
|
||||
and target opset {"": 17, "ai.onnx.ml": 3}. All functions
|
||||
create parent directories and return the output Path
|
||||
|
||||
Connects to:
|
||||
ml/autoencoder - ThreatAutoencoder model class
|
||||
ml/orchestrator - called after training completes
|
||||
core/detection/
|
||||
inference - loads the exported ONNX files
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
metadata.py
|
||||
|
||||
Model metadata persistence for tracking trained model
|
||||
versions and deployment status
|
||||
|
||||
compute_model_version produces a 12-char hex version from
|
||||
the SHA-256 of an ONNX artifact file. save_model_metadata
|
||||
iterates MODEL_TYPES (ae.onnx -> autoencoder, rf.onnx ->
|
||||
random_forest, if.onnx -> isolation_forest), deactivates
|
||||
any previously active version of each type, and inserts
|
||||
new ModelMetadata rows with version, training_samples,
|
||||
metrics, artifact_path, mlflow_run_id, and threshold
|
||||
|
||||
Connects to:
|
||||
models/model_metadata - ModelMetadata ORM model
|
||||
cli/main - called from _write_metadata
|
||||
api/models_api - called after retrain
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
|
|
|
|||
|
|
@ -1,6 +1,32 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
orchestrator.py
|
||||
|
||||
End-to-end training pipeline orchestrator for the 3-model
|
||||
ML ensemble
|
||||
|
||||
TrainingOrchestrator.run accepts (X, y) arrays, calls
|
||||
prepare_training_data for stratified splitting with SMOTE,
|
||||
trains the autoencoder on normal-only data, random forest
|
||||
on labeled data, and isolation forest on normal-only data,
|
||||
exports all three to ONNX (ae.onnx, rf.onnx, if.onnx)
|
||||
plus scaler.json and threshold.json, runs validate_ensemble
|
||||
against the held-out test set with PR-AUC and F1 quality
|
||||
gates, and logs all parameters, metrics, and artifacts to
|
||||
MLflow via VigilExperiment. Returns a TrainingResult
|
||||
dataclass aggregating per-model metrics, gate status,
|
||||
output directory, and MLflow run ID
|
||||
|
||||
Connects to:
|
||||
ml/experiment - VigilExperiment context manager
|
||||
ml/export_onnx - ONNX export functions
|
||||
ml/splitting - prepare_training_data
|
||||
ml/train_autoencoder - train_autoencoder
|
||||
ml/train_classifiers - train_random_forest,
|
||||
train_isolation_forest
|
||||
ml/validation - validate_ensemble
|
||||
cli/main - called from train command
|
||||
api/models_api - called from retrain endpoint
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -1,6 +1,27 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
scaler.py
|
||||
|
||||
IQR-based feature scaler with JSON persistence for the
|
||||
autoencoder preprocessing stage
|
||||
|
||||
FeatureScaler wraps sklearn RobustScaler (median/IQR
|
||||
normalization) to handle outlier-heavy HTTP traffic data.
|
||||
Provides fit, transform, fit_transform, and
|
||||
inverse_transform mirroring the sklearn API. save_json
|
||||
serializes center and scale arrays to a human-readable
|
||||
JSON file (avoiding pickle for security and portability),
|
||||
and load_json reconstructs a fitted scaler from that file.
|
||||
Only the autoencoder uses this scaler since tree-based
|
||||
models (random forest, isolation forest) are
|
||||
scale-invariant
|
||||
|
||||
Connects to:
|
||||
ml/train_autoencoder - fitted during AE training
|
||||
ml/orchestrator - scaler.json saved alongside models
|
||||
core/detection/
|
||||
inference - loaded at inference time for AE
|
||||
input normalization
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -1,6 +1,23 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
splitting.py
|
||||
|
||||
Stratified train/val/test splitting with SMOTE
|
||||
oversampling for imbalanced attack data
|
||||
|
||||
prepare_training_data performs a 70/15/15 stratified split
|
||||
preserving class ratios, extracts the normal-only subset
|
||||
from training data for the autoencoder and isolation
|
||||
forest, and conditionally applies SMOTE oversampling to
|
||||
the training set when the minority class ratio falls below
|
||||
the target strategy (default 0.3). SMOTE is skipped if the
|
||||
minority class has fewer than k_neighbors+1 samples.
|
||||
Returns a TrainingSplit dataclass with X_train, y_train,
|
||||
X_val, y_val, X_test, y_test, and X_normal_train arrays
|
||||
|
||||
Connects to:
|
||||
ml/orchestrator - called at the start of the training
|
||||
pipeline
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
|
|
|||
|
|
@ -1,6 +1,31 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
synthetic.py
|
||||
|
||||
Synthetic HTTP traffic generator for ML training and
|
||||
testing with realistic attack payloads
|
||||
|
||||
Provides per-category generators for 6 attack types:
|
||||
generate_sqli_requests (22 SQL injection payloads),
|
||||
generate_xss_requests (21 XSS vectors), generate_
|
||||
traversal_requests (15 path traversal payloads),
|
||||
generate_log4shell_requests (10 JNDI lookup variants),
|
||||
generate_ssrf_requests (11 cloud metadata and internal
|
||||
service targets), and generate_scanner_requests (11
|
||||
vulnerability scanner user-agents). generate_normal_
|
||||
requests produces benign traffic across 31 realistic
|
||||
paths. generate_mixed_dataset orchestrates all generators,
|
||||
converts ParsedLogEntry objects to 35-dim feature vectors
|
||||
via extract_request_features and encode_for_inference with
|
||||
zeroed windowed features, and returns (X, y) numpy arrays
|
||||
|
||||
Connects to:
|
||||
core/features/extractor - extract_request_features
|
||||
core/features/encoder - encode_for_inference
|
||||
core/features/mappings - WINDOWED_FEATURE_NAMES
|
||||
core/ingestion/parsers - ParsedLogEntry
|
||||
cli/main - used when no CSIC dataset is
|
||||
available
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,6 +1,27 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
train_autoencoder.py
|
||||
|
||||
PyTorch autoencoder training loop with early stopping and
|
||||
anomaly threshold calibration
|
||||
|
||||
train_autoencoder takes normal-only traffic vectors, splits
|
||||
off a 15% validation set, fits a FeatureScaler (IQR-based)
|
||||
on training data, builds DataLoaders, and trains a
|
||||
ThreatAutoencoder (35->24->12->6->12->24->35) using MSE
|
||||
loss with AdamW optimizer (weight decay 1e-5),
|
||||
ReduceLROnPlateau scheduler (factor 0.5, patience 5),
|
||||
gradient clipping at max_norm 1.0, and early stopping
|
||||
(default patience 10). After training, computes per-sample
|
||||
reconstruction error on the validation set and sets the
|
||||
anomaly threshold at the 99.5th percentile. Returns the
|
||||
trained model, fitted scaler, calibrated threshold, and
|
||||
train/val loss history
|
||||
|
||||
Connects to:
|
||||
ml/autoencoder - ThreatAutoencoder model class
|
||||
ml/scaler - FeatureScaler for input normalization
|
||||
ml/orchestrator - called during pipeline execution
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
train_classifiers.py
|
||||
|
||||
Sklearn classifier training for the random forest and
|
||||
isolation forest ensemble members
|
||||
|
||||
train_random_forest builds a 200-tree balanced-weight
|
||||
RandomForestClassifier with max_depth 20, wraps it in
|
||||
CalibratedClassifierCV with isotonic calibration (3-fold
|
||||
CV) for well-calibrated probability outputs, evaluates on
|
||||
a held-out 20% calibration split, and returns the
|
||||
calibrated model with accuracy, precision, recall, F1, and
|
||||
PR-AUC metrics. train_isolation_forest fits a 200-tree
|
||||
IsolationForest on normal-only traffic with automatic
|
||||
contamination estimation, returning the model and sample
|
||||
count
|
||||
|
||||
Connects to:
|
||||
ml/orchestrator - called during pipeline execution
|
||||
ml/export_onnx - models exported to ONNX after training
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
|
|
|||
|
|
@ -1,6 +1,28 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
validation.py
|
||||
|
||||
Post-training ensemble validation with quality gates for
|
||||
deployment readiness
|
||||
|
||||
validate_ensemble loads all 3 ONNX models via
|
||||
InferenceEngine, runs batch prediction on held-out test
|
||||
data, normalizes per-model raw scores (AE reconstruction
|
||||
error against threshold, IF anomaly scores), fuses them
|
||||
via weighted average (default weights: AE 0.4, RF 0.4,
|
||||
IF 0.2), applies a 0.5 binary threshold, and computes
|
||||
precision, recall, F1, PR-AUC, and ROC-AUC. Quality
|
||||
gates require PR-AUC >= 0.85 and F1 >= 0.80 for
|
||||
passed_gates to be True. Returns a ValidationResult
|
||||
dataclass with all metrics, confusion matrix, and
|
||||
per-gate pass/fail details
|
||||
|
||||
Connects to:
|
||||
core/detection/ensemble - normalize_ae_score,
|
||||
normalize_if_score, fuse_scores
|
||||
core/detection/inference - InferenceEngine ONNX runtime
|
||||
ml/orchestrator - called after training to gate
|
||||
deployment
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
# ©AngelaMos | 2026
|
||||
# pyproject.toml
|
||||
#
|
||||
# Python project metadata, dependencies, and tool
|
||||
# configuration for AngelusVigil
|
||||
#
|
||||
# Declares the angelusvigil package (Python 3.14+) with
|
||||
# core dependencies (FastAPI, uvicorn, SQLAlchemy, asyncpg,
|
||||
# Redis, Pydantic, watchdog, geoip2, typer), dev extras
|
||||
# (pytest, ruff, mypy, pylint, coverage, fakeredis), and ml
|
||||
# extras (torch, scikit-learn, onnxruntime, mlflow, pandas,
|
||||
# imbalanced-learn). Uses hatchling as the build backend
|
||||
# with app, cli, and ml packages. Configures ruff (line 95,
|
||||
# Python 3.14 target), mypy (strict mode), pylint (4 jobs
|
||||
# with pydantic plugin), and pytest (asyncio auto mode).
|
||||
# Connects to all backend source modules
|
||||
|
||||
[project]
|
||||
name = "angelusvigil"
|
||||
|
|
@ -29,7 +43,7 @@ dependencies = [
|
|||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=9.0.2",
|
||||
"pytest>=9.0.3",
|
||||
"pytest-asyncio>=1.3.0",
|
||||
"aiosqlite>=0.22.1",
|
||||
"ruff>=0.15.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,17 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
__init__.py
|
||||
|
||||
Test suite package for the ai-threat-detection backend
|
||||
|
||||
Contains unit, integration, and end-to-end tests covering
|
||||
the full stack: API endpoints, ingestion pipeline, feature
|
||||
extraction, rule engine, ML training and inference,
|
||||
ensemble scoring, ONNX export, model metadata persistence,
|
||||
CLI commands, and GeoIP enrichment. Uses pytest-asyncio for
|
||||
async tests, fakeredis for Redis isolation, and in-memory
|
||||
SQLite via aiosqlite for database tests
|
||||
|
||||
Connects to:
|
||||
tests/conftest - shared fixtures for DB and HTTP client
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2,7 +2,22 @@
|
|||
©AngelaMos | 2026
|
||||
conftest.py
|
||||
|
||||
Shared pytest fixtures for in-memory SQLite database and HTTPX test client setup.
|
||||
Shared pytest fixtures for in-memory SQLite database and
|
||||
HTTPX async test client setup
|
||||
|
||||
test_settings overrides Settings for the test environment
|
||||
with an in-memory SQLite URL and dummy paths. db_engine
|
||||
creates a StaticPool aiosqlite engine with all tables via
|
||||
SQLModel.metadata.create_all. db_session yields an
|
||||
AsyncSession bound to the shared engine. db_client builds
|
||||
a full HTTPX AsyncClient with ASGITransport wrapping the
|
||||
FastAPI app, overriding get_session to use the in-memory
|
||||
database with auto-commit
|
||||
|
||||
Connects to:
|
||||
app/config - Settings override
|
||||
app/factory - create_app for ASGI transport
|
||||
app/api/deps - get_session dependency override
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
|
|
|
|||
|
|
@ -2,7 +2,23 @@
|
|||
©AngelaMos | 2026
|
||||
test_api.py
|
||||
|
||||
Tests the FastAPI REST endpoints for threats, stats, health, readiness, and model management.
|
||||
Tests the FastAPI REST endpoints for health, threats, stats,
|
||||
and model management using an in-memory database
|
||||
|
||||
Validates /health returns status, uptime, and pipeline flag.
|
||||
Tests /threats CRUD: empty list returns zero total, random
|
||||
UUID returns 404, seeded event is fetchable by ID with all
|
||||
fields, and severity filter returns only matching items.
|
||||
Tests /stats returns zeroed counts on empty window.
|
||||
Tests /models/status returns detection_mode and
|
||||
active_models list, and POST /models/retrain returns 202
|
||||
with a 32-char job ID
|
||||
|
||||
Connects to:
|
||||
api/health - liveness endpoint
|
||||
api/threats - threat CRUD
|
||||
api/stats - statistics endpoint
|
||||
api/models_api - model status and retrain
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
|
|
|||
|
|
@ -2,7 +2,21 @@
|
|||
©AngelaMos | 2026
|
||||
test_autoencoder.py
|
||||
|
||||
Tests the ThreatAutoencoder architecture: shapes, output range, reconstruction error, and training behavior.
|
||||
Tests the ThreatAutoencoder PyTorch architecture for shape
|
||||
correctness, output range, reconstruction error, and
|
||||
training behavior
|
||||
|
||||
Validates output shape matches input (batch, 35), encoder
|
||||
bottleneck compresses to 6 dimensions, single-sample
|
||||
forward pass succeeds in eval mode, decoder output is
|
||||
unbounded (matching RobustScaler range), reconstruction
|
||||
error returns one positive scalar per sample, trained model
|
||||
reconstructs normal data better than anomalies after 50
|
||||
epochs, eval mode produces deterministic output (dropout
|
||||
off), and variable batch sizes (1, 8, 32, 128) are handled
|
||||
|
||||
Connects to:
|
||||
ml/autoencoder - ThreatAutoencoder
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_cli.py
|
||||
|
||||
Tests the Typer CLI command help output, argument
|
||||
validation, and metadata persistence wiring
|
||||
|
||||
TestCLICommands validates train --help shows csic-dir and
|
||||
synthetic options, nonexistent csic-dir exits with error,
|
||||
replay/serve/config/health --help exit cleanly with
|
||||
expected content, and missing replay log file fails.
|
||||
TestCLITrainMetadata mocks the orchestrator to verify that
|
||||
train emits a warning when DB metadata write is unavailable
|
||||
|
||||
Connects to:
|
||||
cli/main - Typer app with serve, train, replay, config,
|
||||
health commands
|
||||
"""
|
||||
|
||||
import re
|
||||
|
|
|
|||
|
|
@ -2,7 +2,17 @@
|
|||
©AngelaMos | 2026
|
||||
test_config_ml.py
|
||||
|
||||
Tests ML-related settings defaults: detection mode, ensemble weights, model paths, and MLflow URI.
|
||||
Tests ML-related settings defaults for detection mode,
|
||||
ensemble weights, model paths, and MLflow tracking URI
|
||||
|
||||
Validates that the default detection_mode is 'rules',
|
||||
ensemble weights (AE + RF + IF) sum to exactly 1.0,
|
||||
model_dir defaults to 'data/models', ae_threshold_
|
||||
percentile defaults to 99.5, and mlflow_tracking_uri
|
||||
defaults to 'file:./mlruns'
|
||||
|
||||
Connects to:
|
||||
app/config - Settings pydantic-settings model
|
||||
"""
|
||||
|
||||
from app.config import settings
|
||||
|
|
|
|||
|
|
@ -1,6 +1,23 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_data_loader.py
|
||||
|
||||
Tests CSIC 2010 dataset parsing, CSICRequest-to-
|
||||
ParsedLogEntry conversion, and end-to-end dataset loading
|
||||
|
||||
TestParseCSICFile validates HTTP request block splitting,
|
||||
method/path/query/header extraction, POST body capture,
|
||||
attack label assignment, malformed block skipping, and
|
||||
empty file handling using inline CSIC-format fixtures.
|
||||
TestCSICToParsedEntry verifies synthesized defaults (IP,
|
||||
timestamp, status) and POST body query string merging.
|
||||
TestLoadCSICDataset confirms 35-column X shape, dual-label
|
||||
y arrays, correct per-file label counts, and finite feature
|
||||
values
|
||||
|
||||
Connects to:
|
||||
ml/data_loader - parse_csic_file, csic_to_parsed_entry,
|
||||
load_csic_dataset
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
|
|
|||
|
|
@ -2,7 +2,25 @@
|
|||
©AngelaMos | 2026
|
||||
test_detection.py
|
||||
|
||||
Tests the rule engine's threat scoring, severity classification, and attack pattern matching.
|
||||
Tests the RuleEngine threat scoring, severity
|
||||
classification, and OWASP attack pattern matching
|
||||
|
||||
Validates normal requests score LOW below 0.5, SQL
|
||||
injection in query strings scores HIGH with SQL_INJECTION
|
||||
rule, XSS payloads trigger XSS rule, path traversal
|
||||
triggers PATH_TRAVERSAL, command injection triggers
|
||||
COMMAND_INJECTION at HIGH severity, scanner UAs fire
|
||||
SCANNER_UA, high request rates fire RATE_ANOMALY, multiple
|
||||
rules aggregate to higher scores, scores are clamped to
|
||||
[0, 1], severity thresholds align with architecture
|
||||
(LOW < 0.5, MEDIUM >= 0.5, HIGH >= 0.7), component_scores
|
||||
match matched_rules, FILE_INCLUSION detects PHP stream
|
||||
wrappers, and DOUBLE_ENCODING detects %25-prefixed
|
||||
sequences
|
||||
|
||||
Connects to:
|
||||
core/detection/rules - RuleEngine
|
||||
core/ingestion/parsers - ParsedLogEntry
|
||||
"""
|
||||
|
||||
from datetime import datetime, UTC
|
||||
|
|
|
|||
|
|
@ -2,7 +2,23 @@
|
|||
©AngelaMos | 2026
|
||||
test_ensemble.py
|
||||
|
||||
Tests ensemble score normalization, weighted fusion, ML/rule blending, and severity classification.
|
||||
Tests ensemble score normalization, weighted fusion, ML/rule
|
||||
blending, and severity classification functions
|
||||
|
||||
TestScoreNormalization validates AE error below threshold
|
||||
maps below 0.5, 3x threshold caps at 1.0, zero error maps
|
||||
to 0.0, negative IF score maps above 0.5, positive below
|
||||
0.5, and zero maps to 0.5. TestEnsembleFusion validates
|
||||
weighted average computation, all-zero scores fuse to 0.0,
|
||||
all-one scores fuse to 1.0, and partial model support.
|
||||
TestBlendScores validates ML/rule blending at various
|
||||
weights and clamping. TestClassifySeverity validates HIGH
|
||||
at >= 0.7, MEDIUM at [0.5, 0.7), LOW below 0.5
|
||||
|
||||
Connects to:
|
||||
core/detection/ensemble - normalize_ae_score,
|
||||
normalize_if_score, fuse_scores,
|
||||
blend_scores, classify_severity
|
||||
"""
|
||||
|
||||
from app.core.detection.ensemble import (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,19 @@
|
|||
©AngelaMos | 2026
|
||||
test_experiment.py
|
||||
|
||||
Tests the VigilExperiment MLflow wrapper: run lifecycle, param/metric logging, and status tagging.
|
||||
Tests the VigilExperiment MLflow context manager for run
|
||||
lifecycle, parameter/metric logging, and status tagging
|
||||
|
||||
Uses a tmp_path MLflow tracking URI for isolation.
|
||||
Validates run ID is set on context entry and None before,
|
||||
log_params writes string values, log_metrics stores floats,
|
||||
log_artifact uploads files to the artifact list,
|
||||
python_version and platform system metadata tags are auto-
|
||||
logged, successful exit tags status='completed', and
|
||||
exception exit tags status='failed' with the error message
|
||||
|
||||
Connects to:
|
||||
ml/experiment - VigilExperiment
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
|
|
|||
|
|
@ -2,7 +2,22 @@
|
|||
©AngelaMos | 2026
|
||||
test_export_onnx.py
|
||||
|
||||
Tests ONNX export for the autoencoder, random forest, and isolation forest models.
|
||||
Tests ONNX export and inference parity for the autoencoder,
|
||||
random forest, and isolation forest models
|
||||
|
||||
TestAutoencoderExport validates file creation, PyTorch-to-
|
||||
ONNX output match within 1e-5 tolerance, and dynamic batch
|
||||
dimension (1, 16, 64). TestRandomForestExport validates
|
||||
file creation and ONNX inference returning class predictions
|
||||
and probabilities. TestIsolationForestExport validates file
|
||||
creation and ONNX anomaly scores matching sklearn
|
||||
decision_function within 1e-4 tolerance
|
||||
|
||||
Connects to:
|
||||
ml/export_onnx - export_autoencoder,
|
||||
export_random_forest,
|
||||
export_isolation_forest
|
||||
ml/autoencoder - ThreatAutoencoder for AE export
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
|
|
|||
|
|
@ -2,7 +2,31 @@
|
|||
©AngelaMos | 2026
|
||||
test_features.py
|
||||
|
||||
Tests per-request feature extraction, Redis sliding-window aggregation, and feature encoding.
|
||||
Tests the 23 per-request feature extractor, Redis sliding-
|
||||
window aggregator (12 windowed features), and 35-dim
|
||||
feature encoder
|
||||
|
||||
Validates all 23 feature keys are returned, path_depth
|
||||
counts segments, path_entropy distinguishes random vs
|
||||
simple paths, query param count and length, percent-
|
||||
encoding and double-encoding detection, status class
|
||||
grouping, temporal features (hour, day, weekend), bot
|
||||
and scanner UA detection, attack pattern detection (SQLi,
|
||||
XSS, traversal), special char ratio, private IP, file
|
||||
extension, and country code passthrough. WindowAggregator
|
||||
tests use fakeredis to validate single/multi-request
|
||||
counts, error rate calculation, unique paths/UAs, TTL
|
||||
setting, and window boundary exclusion. Encoder tests
|
||||
validate 35-element output, method/status ordinal mapping,
|
||||
boolean-to-float, numerical passthrough, and unknown
|
||||
categorical fallback
|
||||
|
||||
Connects to:
|
||||
core/features/extractor - extract_request_features
|
||||
core/features/aggregator - WindowAggregator
|
||||
core/features/encoder - encode_for_inference
|
||||
core/features/mappings - FEATURE_ORDER, METHOD_MAP,
|
||||
STATUS_CLASS_MAP
|
||||
"""
|
||||
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -2,7 +2,19 @@
|
|||
©AngelaMos | 2026
|
||||
test_geoip.py
|
||||
|
||||
Tests the GeoIP lookup service including private IP handling and missing database fallback.
|
||||
Tests the GeoIPService MaxMind lookup including private IP
|
||||
handling, error cases, and missing database fallback
|
||||
|
||||
Validates GeoResult field storage, successful lookup
|
||||
returning country/city/lat/lon, private and loopback IPs
|
||||
returning None without hitting the reader, AddressNotFound
|
||||
Error returning None, None reader returning None, missing
|
||||
city name handled gracefully, non-existent .mmdb path sets
|
||||
reader to None, and valid .mmdb path opens the reader via
|
||||
mock
|
||||
|
||||
Connects to:
|
||||
core/enrichment/geoip - GeoIPService, GeoResult
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
|
|
|||
|
|
@ -2,7 +2,23 @@
|
|||
©AngelaMos | 2026
|
||||
test_inference.py
|
||||
|
||||
Tests the InferenceEngine: model loading, predict output shapes, score ranges, and missing-model handling.
|
||||
Tests the ONNX InferenceEngine for model loading, batch
|
||||
prediction, score ranges, and error handling
|
||||
|
||||
Uses a model_dir fixture with all 3 exported ONNX models,
|
||||
scaler.json, and threshold.json. Validates is_loaded=True
|
||||
with all models, is_loaded=False for nonexistent and
|
||||
partial directories, predict returns None when not loaded,
|
||||
predict returns ae/rf/if score dicts, AE scores are non-
|
||||
negative, RF probabilities are in [0, 1], single-sample
|
||||
prediction works, threshold loads from JSON, and partial
|
||||
model sets (AE only) report not loaded
|
||||
|
||||
Connects to:
|
||||
core/detection/inference - InferenceEngine
|
||||
ml/export_onnx - model export for fixture
|
||||
ml/scaler - FeatureScaler for fixture
|
||||
ml/autoencoder - ThreatAutoencoder for fixture
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -2,7 +2,24 @@
|
|||
©AngelaMos | 2026
|
||||
test_integration.py
|
||||
|
||||
End-to-end tests covering the full path from log file write through tailer, pipeline, and database storage.
|
||||
End-to-end tests covering the full path from log file
|
||||
write through tailer, pipeline, and database storage
|
||||
|
||||
integration_env fixture creates a temp log file, in-memory
|
||||
SQLite, fake Redis, AlertDispatcher, RuleEngine, Pipeline,
|
||||
and LogTailer wired together. Tests write nginx-format log
|
||||
lines (normal, SQLi, XSS, path traversal) to the file and
|
||||
poll the database for stored ThreatEvent rows. Validates
|
||||
that MEDIUM+ threats are persisted, LOW severity requests
|
||||
are not stored, and stored events have correct severity,
|
||||
score, matched_rules, feature_vector length, and source_ip
|
||||
|
||||
Connects to:
|
||||
core/ingestion/tailer - LogTailer
|
||||
core/ingestion/pipeline - Pipeline
|
||||
core/alerts/dispatcher - AlertDispatcher
|
||||
core/detection/rules - RuleEngine
|
||||
models/threat_event - ThreatEvent
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_metadata.py
|
||||
|
||||
Tests SHA-256 model version hashing and async metadata
|
||||
persistence to the database
|
||||
|
||||
TestComputeModelVersion verifies 12-char hex output,
|
||||
deterministic hashing (same file = same version), and
|
||||
distinct versions for different files. TestSaveModel
|
||||
Metadata uses an in-memory SQLite session and fake ONNX
|
||||
artifacts to validate 3-row creation (one per model type),
|
||||
is_active flag on new rows, correct model_type values
|
||||
(autoencoder, random_forest, isolation_forest), previous
|
||||
active row deactivation on re-save, and inactive row
|
||||
preservation (6 total rows after two saves)
|
||||
|
||||
Connects to:
|
||||
ml/metadata - compute_model_version,
|
||||
save_model_metadata
|
||||
models/model_metadata - ModelMetadata ORM model
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_ml_integration.py
|
||||
|
||||
Tests the ML inference engine wired into the ingestion
|
||||
pipeline in hybrid detection mode
|
||||
|
||||
Uses a trained_model_dir fixture with ONNX models to build
|
||||
a pipeline with InferenceEngine. Validates hybrid detection
|
||||
mode is set when ML models are present, final_score is in
|
||||
[0, 1], rules-only mode falls back to rule score as final
|
||||
score, attack lines score higher than benign in hybrid
|
||||
mode, and rule_result is preserved alongside ML scores
|
||||
|
||||
Connects to:
|
||||
core/detection/inference - InferenceEngine
|
||||
core/detection/rules - RuleEngine
|
||||
core/ingestion/pipeline - Pipeline, ScoredRequest
|
||||
ml/export_onnx - model export for fixture
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_orchestrator.py
|
||||
|
||||
Tests the TrainingOrchestrator pipeline from data splitting
|
||||
through model export, validation, and MLflow logging
|
||||
|
||||
Verifies all 5 output files are produced (ae.onnx, rf.onnx,
|
||||
if.onnx, scaler.json, threshold.json), TrainingResult
|
||||
dataclass structure, scaler.json keys (center, scale,
|
||||
n_features), threshold.json float value, per-model metrics
|
||||
presence (ae_threshold, rf f1, if n_samples), ensemble
|
||||
validation metrics, MLflow run ID capture (32-char hex),
|
||||
and passed_gates boolean type
|
||||
|
||||
Connects to:
|
||||
ml/orchestrator - TrainingOrchestrator, TrainingResult
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -2,7 +2,19 @@
|
|||
©AngelaMos | 2026
|
||||
test_parsers.py
|
||||
|
||||
Tests nginx combined log line parsing via parse_combined.
|
||||
Tests nginx combined-format log line parsing via the
|
||||
parse_combined function
|
||||
|
||||
Validates full field extraction (IP, timestamp, method,
|
||||
path, query string, status code, response size, referer,
|
||||
user agent, raw line), IPv4 and IPv6 address handling,
|
||||
dash-referer normalization to empty string, multi-parameter
|
||||
query strings with special characters, malformed and empty
|
||||
line None returns, dash response size normalization to
|
||||
zero, and full-length IPv6 address parsing
|
||||
|
||||
Connects to:
|
||||
core/ingestion/parsers - parse_combined, ParsedLogEntry
|
||||
"""
|
||||
|
||||
from datetime import datetime, UTC
|
||||
|
|
|
|||
|
|
@ -2,7 +2,21 @@
|
|||
©AngelaMos | 2026
|
||||
test_pipeline.py
|
||||
|
||||
Tests the async ingestion pipeline: parsing, feature extraction, rule scoring, and shutdown.
|
||||
Tests the async ingestion pipeline across all 4 stages:
|
||||
parsing, feature extraction, rule scoring, and dispatch
|
||||
|
||||
Uses a fakeredis-backed Pipeline with a results collector
|
||||
callback. Validates that valid log lines flow end-to-end
|
||||
producing a ScoredRequest with correct IP, method, 35-dim
|
||||
feature vector, and LOW severity. Confirms malformed lines
|
||||
are dropped without crashing, backpressure works with
|
||||
maxsize=1 queues, stop() drains remaining items with all
|
||||
tasks completing cleanly, and SQLi payloads score HIGH with
|
||||
SQL_INJECTION rule match
|
||||
|
||||
Connects to:
|
||||
core/ingestion/pipeline - Pipeline, ScoredRequest
|
||||
core/detection/rules - RuleEngine
|
||||
"""
|
||||
|
||||
import fakeredis.aioredis
|
||||
|
|
|
|||
|
|
@ -2,7 +2,21 @@
|
|||
©AngelaMos | 2026
|
||||
test_scaler.py
|
||||
|
||||
Tests the FeatureScaler: fitting, transform correctness, JSON serialization, and round-trip loading.
|
||||
Tests the FeatureScaler IQR-based normalization for
|
||||
fitting, transform correctness, JSON round-trip, and error
|
||||
handling
|
||||
|
||||
Validates n_features is stored after fit, transform
|
||||
preserves shape and float32 dtype, median of scaled
|
||||
features is near zero, inverse_transform recovers original
|
||||
values within 1e-5, save_json creates a valid JSON file
|
||||
with center/scale/n_features keys, load_json round-trip
|
||||
produces identical transform output within 1e-6, transform
|
||||
before fit raises RuntimeError, and fit_transform
|
||||
convenience method works
|
||||
|
||||
Connects to:
|
||||
ml/scaler - FeatureScaler
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_splitting.py
|
||||
|
||||
Tests stratified train/val/test splitting with SMOTE
|
||||
oversampling for imbalanced datasets
|
||||
|
||||
Validates TrainingSplit dataclass return, 70/15/15 split
|
||||
proportions within tolerance, stratified class distribution
|
||||
preservation in val/test sets, SMOTE minority ratio near
|
||||
target strategy (0.3), val/test sizes unaffected by SMOTE,
|
||||
X_normal_train containing only class-0 rows, small dataset
|
||||
(50 samples) success, single-class ValueError, and SMOTE
|
||||
skip when minority count is below k_neighbors threshold
|
||||
|
||||
Connects to:
|
||||
ml/splitting - prepare_training_data, TrainingSplit
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_synthetic.py
|
||||
|
||||
Tests synthetic HTTP traffic generators and mixed dataset
|
||||
assembly for ML training
|
||||
|
||||
TestGenerators validates all 7 per-type generators (SQLi,
|
||||
XSS, traversal, Log4Shell, SSRF, scanner, normal) return
|
||||
correct counts, contain expected payload patterns (OR/UNION
|
||||
for SQLi, script/alert for XSS, ../ for traversal), return
|
||||
ParsedLogEntry instances, and pass through feature
|
||||
extraction and encoding to 35-dim vectors. TestMixedDataset
|
||||
verifies correct X shape (n, 35), dual-label y, matching
|
||||
label counts, and finite feature values
|
||||
|
||||
Connects to:
|
||||
ml/synthetic - all generate_* functions,
|
||||
generate_mixed_dataset
|
||||
core/features/extractor - extract_request_features
|
||||
core/features/encoder - encode_for_inference
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
|
|
|||
|
|
@ -2,7 +2,24 @@
|
|||
©AngelaMos | 2026
|
||||
test_training.py
|
||||
|
||||
Tests training pipelines for the autoencoder, random forest, and isolation forest models.
|
||||
Tests training functions for the autoencoder, random forest,
|
||||
and isolation forest models
|
||||
|
||||
TestAutoencoderTraining validates train_autoencoder returns
|
||||
model/threshold/scaler/history, threshold is positive,
|
||||
history has correct epoch count, higher percentile yields
|
||||
higher threshold, and returned model is in eval mode.
|
||||
TestRandomForestTraining validates model/metrics return,
|
||||
predict_proba availability, required metric keys (f1,
|
||||
pr_auc, accuracy, precision, recall), probability range,
|
||||
and metric value range. TestIsolationForestTraining
|
||||
validates model return, score_samples availability,
|
||||
n_samples metric, and normal/outlier score separation
|
||||
|
||||
Connects to:
|
||||
ml/train_autoencoder - train_autoencoder
|
||||
ml/train_classifiers - train_random_forest,
|
||||
train_isolation_forest
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_training_e2e.py
|
||||
|
||||
End-to-end training integration test from synthetic data
|
||||
generation through ONNX inference and score fusion
|
||||
|
||||
test_full_training_produces_loadable_models generates a
|
||||
500-normal/200-attack synthetic dataset, runs the full
|
||||
TrainingOrchestrator pipeline with 3 epochs, verifies all
|
||||
5 output files (ae.onnx, rf.onnx, if.onnx, scaler.json,
|
||||
threshold.json), loads models via InferenceEngine, runs
|
||||
batch prediction, normalizes and fuses per-model scores,
|
||||
blends with rule scores, and asserts all values are in
|
||||
[0, 1]. Validates passed_gates is a boolean
|
||||
|
||||
Connects to:
|
||||
ml/orchestrator - TrainingOrchestrator
|
||||
ml/synthetic - generate_mixed_dataset
|
||||
core/detection/ensemble - normalize, fuse, blend
|
||||
core/detection/inference - InferenceEngine
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
test_validation.py
|
||||
|
||||
Tests post-training ensemble validation with quality gates
|
||||
|
||||
Uses a trained_model_dir fixture with all 3 ONNX models,
|
||||
scaler, and threshold, plus a separable_test_data fixture
|
||||
with well-separated normal/attack clusters. Validates
|
||||
ValidationResult structure, metric ranges (precision,
|
||||
recall, f1, pr_auc, roc_auc all in [0, 1]), 2x2 confusion
|
||||
matrix shape, gate_details keys (pr_auc, f1), gate pass
|
||||
with low thresholds, gate fail with high thresholds, and
|
||||
custom ensemble weight acceptance
|
||||
|
||||
Connects to:
|
||||
ml/validation - validate_ensemble, ValidationResult
|
||||
ml/export_onnx - model export for fixture setup
|
||||
ml/scaler - FeatureScaler for fixture setup
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ requires-dist = [
|
|||
{ name = "pydantic-settings", specifier = ">=2.12.0" },
|
||||
{ name = "pylint", marker = "extra == 'dev'", specifier = ">=4.0.4" },
|
||||
{ name = "pylint-pydantic", marker = "extra == 'dev'", specifier = ">=0.4.1" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.3.0" },
|
||||
{ name = "redis", extras = ["hiredis"], specifier = ">=7.1.1" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.0" },
|
||||
|
|
@ -1775,35 +1775,35 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.1.1"
|
||||
version = "12.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2073,7 +2073,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.2"
|
||||
version = "9.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
|
|
@ -2082,9 +2082,9 @@ dependencies = [
|
|||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
# ©AngelaMos | 2026
|
||||
# Production Docker Compose
|
||||
# compose.yml
|
||||
#
|
||||
# Production Docker Compose stack for AngelusVigil
|
||||
#
|
||||
# Orchestrates 5 services on the vigil_network bridge:
|
||||
# postgres (18-alpine with healthcheck and persistent
|
||||
# volume), redis (7.4-alpine with custom redis.conf),
|
||||
# backend (FastAPI with asyncpg, Redis, nginx log tail,
|
||||
# GeoIP, and model data volumes), frontend (Vite
|
||||
# production build served via nginx on the host port),
|
||||
# and geoip-updater (MaxMind weekly refresh). Joins the
|
||||
# external certgames_net network and mounts the external
|
||||
# certgames_nginx_logs volume for real-time log access.
|
||||
# Connects to infra/docker/fastapi.prod,
|
||||
# infra/docker/vite.prod, infra/redis/redis.conf,
|
||||
# infra/nginx/vigil.conf
|
||||
|
||||
services:
|
||||
postgres:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
# ©AngelaMos | 2026
|
||||
# Dockerfile
|
||||
#
|
||||
# Container image for the dev-log FastAPI target application
|
||||
#
|
||||
# Based on python:3.14-slim with uv copied from the official
|
||||
# astral-sh image. Installs fastapi and uvicorn system-wide,
|
||||
# copies app.py, and runs uvicorn on port 8000. Sits behind
|
||||
# the nginx reverse proxy defined in compose.yml
|
||||
|
||||
FROM python:3.14-slim
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,25 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
app.py
|
||||
|
||||
Minimal FastAPI target application for generating nginx
|
||||
access logs during development
|
||||
|
||||
Exposes realistic REST endpoints that the simulate.py
|
||||
traffic generator hits through the nginx reverse proxy:
|
||||
/ (HTML landing), /health, /api/users (list and by ID),
|
||||
/api/login (POST returning a fake JWT), /api/search with
|
||||
query parameter, /api/products (list and by ID),
|
||||
/api/checkout (POST), /admin and /admin/dashboard (403
|
||||
forbidden), and /static/{path} (404). Designed to produce
|
||||
diverse nginx combined-format log lines for testing the
|
||||
ingestion pipeline and rule engine
|
||||
|
||||
Connects to:
|
||||
dev-log/nginx.conf - proxied behind nginx
|
||||
dev-log/simulate.py - traffic generator targets these
|
||||
endpoints
|
||||
dev-log/compose.yml - containerized as vigil-devlog-app
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
# ©AngelaMos | 2026
|
||||
# compose.yml
|
||||
#
|
||||
# Docker Compose stack for the dev-log traffic generation
|
||||
# environment
|
||||
#
|
||||
# Runs two services on a bridge network: app (FastAPI target
|
||||
# built from the local Dockerfile with a /health check) and
|
||||
# nginx (alpine image reverse-proxying port 58319 to the
|
||||
# app, writing combined-format access logs to a named volume
|
||||
# vigil_dev_nginx_logs). The nginx container clears stale
|
||||
# log files on startup for clean sessions
|
||||
|
||||
services:
|
||||
app:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
# ©AngelaMos | 2026
|
||||
# nginx.conf
|
||||
#
|
||||
# Nginx reverse proxy configuration for the dev-log traffic
|
||||
# generation environment
|
||||
#
|
||||
# Listens on port 80, proxies all requests to the upstream
|
||||
# FastAPI app on port 8000, and writes combined-format
|
||||
# access logs to /var/log/nginx/access.log. Sets X-Real-IP,
|
||||
# X-Forwarded-For, and X-Forwarded-Proto headers for the
|
||||
# backend. The log output is mounted as a named volume in
|
||||
# compose.yml for consumption by the ingestion pipeline
|
||||
|
||||
events {
|
||||
worker_connections 64;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,26 @@
|
|||
"""
|
||||
©AngelaMos | 2026
|
||||
simulate.py
|
||||
|
||||
HTTP traffic simulator for generating realistic attack and
|
||||
normal log patterns against the dev-log target application
|
||||
|
||||
Provides 10 traffic modes via argparse: normal (benign
|
||||
browsing with GET/POST mix), sqli (12 SQL injection
|
||||
payloads), xss (10 script/event handler vectors),
|
||||
traversal (10 dot-dot-slash and encoding variants), cmdi
|
||||
(7 shell command injection payloads), log4shell (4 JNDI
|
||||
lookup variants), ssrf (5 cloud metadata and internal
|
||||
service targets), scanner (20 recon paths with 11 scanner
|
||||
user-agents), flood (rapid-fire requests), and mixed
|
||||
(50/10/40 normal/scanner/attack split). Uses urllib for
|
||||
HTTP requests with configurable count, delay, and target
|
||||
URL. Checks /health reachability before starting
|
||||
|
||||
Connects to:
|
||||
dev-log/app.py - target endpoints
|
||||
dev-log/nginx.conf - requests proxied through nginx
|
||||
to generate access.log entries
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
|
|
|||
|
|
@ -1,5 +1,18 @@
|
|||
# ©AngelaMos | 2026
|
||||
# dev.compose.yml
|
||||
#
|
||||
# Development Docker Compose stack with exposed ports and
|
||||
# hot reload
|
||||
#
|
||||
# Orchestrates 4 services on the vigil_dev bridge: postgres
|
||||
# (18-alpine on host port 16969 with default devpassword),
|
||||
# redis (7.4-alpine on host port 26969 with appendonly),
|
||||
# backend (FastAPI dev build on host port 36969 with debug
|
||||
# enabled, quiet gitpython, and SKIP_AUTO_TRAIN toggle),
|
||||
# and frontend (Vite dev server on host port 46969 with
|
||||
# source bind-mount for HMR and API proxy to the backend).
|
||||
# Connects to infra/docker/fastapi.dev,
|
||||
# infra/docker/vite.dev
|
||||
|
||||
services:
|
||||
postgres:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.90.20",
|
||||
"axios": "^1.13.4",
|
||||
"axios": "^1.15.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-error-boundary": "^6.1.0",
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ importers:
|
|||
specifier: ^5.90.20
|
||||
version: 5.90.21(react@19.2.4)
|
||||
axios:
|
||||
specifier: ^1.13.4
|
||||
version: 1.13.6
|
||||
specifier: ^1.15.0
|
||||
version: 1.15.0
|
||||
react:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4
|
||||
|
|
@ -192,28 +192,24 @@ packages:
|
|||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@biomejs/cli-linux-arm64@2.4.4':
|
||||
resolution: {integrity: sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@2.4.4':
|
||||
resolution: {integrity: sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@biomejs/cli-linux-x64@2.4.4':
|
||||
resolution: {integrity: sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@biomejs/cli-win32-arm64@2.4.4':
|
||||
resolution: {integrity: sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q==}
|
||||
|
|
@ -357,42 +353,36 @@ packages:
|
|||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
|
||||
|
|
@ -451,28 +441,24 @@ packages:
|
|||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==}
|
||||
|
|
@ -587,8 +573,8 @@ packages:
|
|||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
axios@1.13.6:
|
||||
resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==}
|
||||
axios@1.15.0:
|
||||
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
|
||||
|
||||
babel-runtime@5.8.38:
|
||||
resolution: {integrity: sha512-KpgoA8VE/pMmNCrnEeeXqFG24TIH11Z3ZaimIhJWsin8EbfZy3WzFKUTIan10ZIDgRVvi9EkLbruJElJC9dRlg==}
|
||||
|
|
@ -768,8 +754,8 @@ packages:
|
|||
flatted@3.3.3:
|
||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
||||
|
||||
follow-redirects@1.15.11:
|
||||
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||
follow-redirects@1.16.0:
|
||||
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
|
|
@ -977,28 +963,24 @@ packages:
|
|||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.31.1:
|
||||
resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.31.1:
|
||||
resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.31.1:
|
||||
resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.31.1:
|
||||
resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==}
|
||||
|
|
@ -1124,8 +1106,9 @@ packages:
|
|||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
proxy-from-env@2.1.0:
|
||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
qified@0.6.0:
|
||||
resolution: {integrity: sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==}
|
||||
|
|
@ -1199,6 +1182,7 @@ packages:
|
|||
rolldown-vite@7.2.5:
|
||||
resolution: {integrity: sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
deprecated: Use 7.3.1 for migration purposes. For the most recent updates, migrate to Vite 8 once you're ready.
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^20.19.0 || >=22.12.0
|
||||
|
|
@ -1891,11 +1875,11 @@ snapshots:
|
|||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios@1.13.6:
|
||||
axios@1.15.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.11
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.5
|
||||
proxy-from-env: 1.1.0
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
|
|
@ -2055,7 +2039,7 @@ snapshots:
|
|||
|
||||
flatted@3.3.3: {}
|
||||
|
||||
follow-redirects@1.15.11: {}
|
||||
follow-redirects@1.16.0: {}
|
||||
|
||||
form-data@4.0.5:
|
||||
dependencies:
|
||||
|
|
@ -2329,7 +2313,7 @@ snapshots:
|
|||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
qified@0.6.0:
|
||||
dependencies:
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue