Version 2.1.0 | Last Updated: March 2026
AIris Security is a full-stack vulnerability assessment platform built around four scanner integrations, ML-assisted risk analysis, and a ReportLab-based PDF generator. The design follows a service-oriented pattern: a Next.js frontend communicates with the FastAPI backend via REST; scan execution and ML analysis run in backend background flow.
┌────────────────────────────────────────────────────────────────────┐
│ USER INTERFACE LAYER │
│ (Next.js 16.1.6) │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ Auth pages │ │ Scan page │ │ Results / Report pages │ │
│ │ login / │ │ target + │ │ findings · ML analysis │ │
│ │ register │ │ live logs │ │ charts · PDF download │ │
│ └─────────────┘ └──────────────┘ └──────────────────────────┘ │
└──────────────────────────┬─────────────────────────────────────────┘
│ REST / Axios (polling every 3 s)
┌──────────────────────────▼─────────────────────────────────────────┐
│ APPLICATION LAYER │
│ (FastAPI 0.110.1 + Uvicorn) │
│ ┌──────────────┐ ┌──────────────────┐ ┌───────────────────┐ │
│ │ Auth API │ │ Scan API │ │ Report API │ │
│ │ /api/auth/* │ │ /api/scan/* │ │ /api/scan/ │ │
│ │ JWT, bcrypt │ │ start·status· │ │ report/{id}/pdf │ │
│ └──────────────┘ │ results·health │ └───────────────────┘ │
│ └──────────────────┘ │
└──────┬─────────────────┬───────────────────┬────────────────────────┘
│ │ │
┌──────▼──────┐ ┌───────▼────────┐ ┌───────▼──────────────────────┐
│ Database │ │ Scanner Layer │ │ ML Engine │
│ (MongoDB) │ │ (subprocess + │ │ (scikit-learn + NLTK) │
│ Motor │ │ executor) │ │ │
│ │ │ │ │ ┌──────────────────────┐ │
│ • users │ │ • Nmap │ │ │ XGBoost │ │
│ • scans │ │ • Nikto │ │ │ attack predictor │ │
│ • reports │ │ • SSLScan │ │ ├──────────────────────┤ │
│ │ │ • DirSearch │ │ │ NLP payload │ │
└─────────────┘ └────────────────┘ │ │ classifier (TF-IDF) │ │
│ ├──────────────────────┤ │
│ │ Hybrid risk scoring │ │
│ │ + scanner boosts │ │
│ └──────────────────────┘ │
└──────────────────────────────┘
| Library | Version | Role |
|---|---|---|
| Next.js | 16.1.6 | React framework + routing |
| React | 18.2.0 | UI rendering |
| TailwindCSS | 3.4.19 | Utility-first styling |
| Framer Motion | 12.29.0 | Animations |
| Axios | 1.6.x | HTTP client |
pages/
├── index.js Landing page with feature overview
├── login.js JWT login form
├── register.js Account creation
├── scan.js Main scanning interface (target input, live logs, results)
├── history.js Per-user scan history
├── results/
│ └── index.js Redirects to report view
└── report/
└── [id].js Full results: ML analysis, findings table, PDF download
components/
├── PredictionCard.jsx Active ML analysis card in report view
├── LogTerminal.jsx Placeholder file (currently empty)
├── RiskMeter.jsx Placeholder file (currently empty)
└── VulnTable.jsx Placeholder file (currently empty)
// Poll every 3 seconds until status === "completed"
const pollInterval = setInterval(async () => {
const status = await getScanStatus(scanId);
setScanProgress(status.progress); // 0–100 %
setScanLogs(status.logs); // string[]
if (status.status === "completed") {
router.push(`/report/${scanId}`);
clearInterval(pollInterval);
}
}, 3000);| Library | Version | Role |
|---|---|---|
| FastAPI | 0.110.1 | REST API framework |
| Uvicorn | 0.25.0 | ASGI server |
| Motor | 3.3.1 | Async MongoDB driver |
| PyJWT | 2.10.1 | JWT token handling |
| bcrypt | 4.1.2 | Password hashing |
| ReportLab | 4.0.7 | PDF generation |
| matplotlib | 3.x | Chart generation (embedded in PDF) |
| scikit-learn | 1.7.2 | ML inference |
backend/
├── server.py FastAPI app + Windows event-loop policy
└── app/
├── core/
│ ├── config.py Environment config (pydantic Settings)
│ └── security.py JWT encode/decode, password hashing
├── db/
│ └── mongodb.py Motor client + collection helpers
├── models/
│ ├── user.py User document model
│ ├── scan.py Scan document model
│ └── report.py Report document model
├── schemas/ Pydantic request/response schemas
├── api/
│ ├── auth.py POST /api/auth/register, /login; GET /api/auth/me
│ └── report.py GET /api/report/{id}
├── routes/
│ └── scan.py POST /api/scan/start
│ GET /api/scan/status/{id}
│ GET /api/scan/results/{id}
│ GET /api/scan/health
│ GET /api/scan/scanners
├── scanners/
│ ├── __init__.py Auto-detection registry
│ ├── base.py Abstract BaseScanner
│ ├── nmap_scanner.py Nmap XML parser
│ ├── nikto_scanner.py Nikto text parser
│ ├── sslscan_scanner.py SSLScan TLS/cipher/cert parser
│ └── dirsearch_scanner.py DirSearch plain-format parser
└── services/
├── scan_service.py Scan lifecycle management
├── scanner_orchestrator.py Legacy/simplified orchestrator
├── ml_service.py Hybrid ML risk scoring
├── pdf_report.py PDF builder (reportlab + matplotlib)
├── aggregator_service.py Finding aggregation
├── vulnerability_aggregator.py Deduplication
└── auth_service.py User management
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/auth/register |
No | Create user account |
POST |
/api/auth/login |
No | Return JWT token |
GET |
/api/auth/me |
JWT | Return authenticated user profile |
GET |
/api/scan/health |
No | System status + scanner list |
GET |
/api/scan/scanners |
No | Available scanner names |
GET |
/api/scan/history |
JWT | User scan history |
POST |
/api/scan/start |
JWT | Initiate comprehensive scan |
GET |
/api/scan/status/{id} |
JWT | Progress + live logs |
GET |
/api/scan/results/{id} |
JWT | Findings + ML analysis |
GET |
/api/scan/report/{id}/pdf |
JWT | Download PDF report |
GET |
/api/report/{id} |
JWT | Return stored report payload |
At startup app/scanners/__init__.py instantiates each scanner class and checks its is_available property. Only scanners whose binary/script is found are added to AVAILABLE_SCANNERS:
_ALL_SCANNER_CLASSES = {
"nmap": NmapScanner,
"nikto": NiktoScanner,
"sslscan": SSLScanner,
"dirsearch": DirSearchScanner,
}
AVAILABLE_SCANNERS: dict = {}
for _name, _cls in _ALL_SCANNER_CLASSES.items():
try:
_instance = _cls()
if hasattr(_instance, "is_available") and not _instance.is_available:
continue
AVAILABLE_SCANNERS[_name] = _cls
except RuntimeError as _e:
print(f"[SCANNER] {_name} unavailable: {_e}")Each scanner searches for its tool in this order:
backend/security_tools/tool_paths.txt— explicit path overrides- Common Windows install paths (e.g.
C:\Program Files (x86)\Nmap\nmap.exe) - System
PATH(shutil.which)
class BaseScannerInterface:
async def scan(self, target: str) -> Dict[str, Any]: ...Nmap
{
"tool": "nmap", "status": "completed", "target": "...",
"findings": [...], # standardised finding dicts
"raw_output": "..."
}Nikto
{
"tool": "nikto", "status": "completed", "target": "...",
"findings": [...],
"raw_output": "..."
}SSLScan
{
"tool": "sslscan", "status": "completed", "target": "...",
"tls_versions": ["TLSv1.0"], # deprecated protocols found enabled
"weak_ciphers": ["RC4-SHA"], # weak cipher strings
"certificate_info": {
"subject": "...", "issuer": "...",
"valid_from": "...", "valid_until": "...",
"days_until_expiry": 42, "self_signed": False
},
"findings": [...],
"raw_output": "..."
}DirSearch
{
"tool": "dirsearch", "status": "completed", "target": "...",
"exposed_paths": ["[200] /admin/", "[403] /.git/"],
"findings": [...],
"summary": "DirSearch completed for ...",
"raw_output": "..."
}findings + scanner_results
│
▼
ml_service.analyze_scan(findings, scanner_results)
│
├── Build scan_input for ml/src/inference.py
│ payload + scanner blocks (nmap, nikto, sslscan, dirsearch)
│
├── predict_attack(scan_input)
│ payload classifier + attack predictor + graph risk helper
│
├── Return mapped backend shape
│ risk_score, attack_type, confidence,
│ payload_prediction, ml_powered
│
└── On failure: fallback output with neutral defaults
| File | Location | Description |
|---|---|---|
payload_classifier.joblib |
ml/models/ |
TF-IDF + LogReg payload classifier |
attack_predictor.joblib |
ml/models/ |
Multi-class attack predictor |
attack_label_encoder.joblib |
ml/models/ |
Label encoder for attack types |
Note: legacy backend RF/scaler placeholders exist in service code but active inference path uses ml/src/inference.py model artifacts.
ReportLab handles layout; matplotlib generates charts which are embedded as in-memory PNG images — no temp files.
- Scan Info — target, scan type, timestamps, scanner list
- ML Analysis — risk gauge chart, expanded ML table (attack type, hybrid/RF/NLP scores, confidence), predicted attack vectors, CVE context, remediation callout box (green, keyed to
attack_type) - Findings — severity pie chart, per-severity grouped tables with green-highlighted
Mitigation:row sourced fromREMEDIATION_MAP - Raw Output — full scanner stdout on a new page
REMEDIATION_MAP in pdf_report.py contains 48 entries keyed by attack type and finding type. _lookup_remediation(key) normalises underscores, hyphens, and case, then does exact → substring → reverse-substring matching.
// users collection
{
"_id": ObjectId,
"email": "user@example.com",
"hashed_password": "$2b$12$...",
"created_at": ISODate
}
// scans collection
{
"scan_id": "scan_20260303_120000_all",
"user_email": "user@example.com",
"target": "scanme.nmap.org",
"status": "completed", // initializing|scanning|analyzing|completed|error
"progress": 100,
"scanners": ["nmap", "nikto", "sslscan", "dirsearch"],
"started_at": ISODate,
"completed_at": ISODate,
"findings": [...],
"ml_analysis": { "risk_score": 62, "attack_type": "XSS", ... },
"raw_output": "...",
"logs": [...]
}1. User submits target on /scan page
└─ POST /api/scan/start { target }
│
▼
2. Backend validates target, generates scan_id, stores initial record in MongoDB
└─ Starts background asyncio task: run_all_scanners()
└─ Returns { scan_id, status: "started" }
│
▼
3. Frontend polls GET /api/scan/status/{scan_id} every 3 s
└─ Updates progress bar and inline log panel
│
▼
4. Background task executes scanners sequentially
├─ Nmap → open_ports, findings, raw
├─ Nikto → findings, raw
├─ SSLScan → tls_versions, weak_ciphers, cert_info, findings, raw
└─ DirSearch → exposed_paths, findings, raw
│
▼
5. analyze_scan() calls `predict_attack(scan_input)`
└─ Returns risk_score, attack_type, confidence, payload_prediction
│
▼
6. Scan record updated: status="completed", findings, ml_analysis, raw_output
│
▼
7. Frontend detects "completed" and routes to `/report/{scan_id}`
└─ Report page renders PredictionCard, findings table, raw output, PDF action
│
▼
8. User clicks Download PDF
└─ GET /api/scan/report/{id}/pdf
└─ PDFReportGenerator.generate_scan_report(scan_data) → bytes
└─ Browser downloads scan_report_{id}_{date}.pdf
Register → bcrypt hash password (12 rounds) → store in MongoDB
Login → verify bcrypt → issue JWT (default 24h expiry, HS256)
Requests → Axios Authorization: Bearer <token> → FastAPI Depends(verify_token)
- Target validation rejects obviously malformed inputs
- Timeouts on every scanner subprocess prevent runaway processes
- All subprocess calls use argument lists (not
shell=True) to prevent injection
| Component | Typical Duration |
|---|---|
| Nmap scan | 20–40 s |
| Nikto scan | 5–10 min |
| SSLScan | 30–90 s |
| DirSearch | 1–5 min |
| ML inference | < 1 s |
| PDF generation | 1–3 s |
| Layer | Technology | Version |
|---|---|---|
| Frontend | Next.js | 16.1.6 |
| Frontend | TailwindCSS | 3.4.19 |
| Frontend | Framer Motion | 12.29.0 |
| Backend | FastAPI | 0.110.1 |
| Backend | Uvicorn | 0.25.0 |
| Database | MongoDB | 4.4+ |
| DB Driver | Motor | 3.3.1 |
| Auth | PyJWT + bcrypt | 2.10.1 / 4.1.2 |
| ML | scikit-learn | 1.7.2 |
| Charts | matplotlib | 3.x |
| ReportLab | 4.0.7 | |
| Scanners | Nmap · Nikto · SSLScan · DirSearch | Latest |
Document Version: 2.1.0 · Last Updated: March 2026