
Piattaforma di threat intelligence self-hosted — aggregazione di feed, triage con AI, copertura MITRE ATT&CK e detection engineering integrata con Sentinel. Funziona in modalità standalone o completamente integrata con Azure.
Una piattaforma di threat intelligence self-hosted che aggrega feed RSS da oltre 60 vendor di sicurezza, esegue il triage tramite AI, correla i risultati con il tuo inventario di asset RunZero e mostra avvisi azionabili tramite una dashboard web in dark mode.
Progettata per funzionare in modo autonomo con zero dipendenze dal cloud, oppure completamente integrata in un ambiente Azure/Entra/Sentinel — scegli il tier che corrisponde a ciò che hai a disposizione.
| Tier | Script | Triage AI | Auth | Storage | Cosa ottieni |
|---|---|---|---|---|---|
| Basic | scripts/setup-basic.sh | Off | Local API key | Local Postgres (Docker) | Aggregazione feed, estrazione IOC, matrice MITRE, dashboard — nessuna AI, nessun cloud, niente a cui registrarsi |
| Basic + API | scripts/setup-basic-api.sh | Anthropic (direct) | Local API key | Local Postgres (Docker) | Tutto quanto sopra, più triage AI di severity/TTP/summary |
| Azure + API | scripts/setup-azure.ps1 | Azure AI Foundry | Microsoft Entra ID SSO | Il tuo Postgres (Azure DB for PostgreSQL, ecc.) | Deployment completo su Azure Container Apps, SSO con ruoli per utente. (L'integrazione della pipeline detections.ai arriverà in una release futura — vedi sotto.) |
Tutti e tre eseguono esattamente lo stesso codice applicativo — l'unica cosa che cambia è quali variabili d'ambiente sono impostate. Vedi Variabili d'ambiente per il riferimento completo.```bash
./scripts/setup-basic.sh
./scripts/setup-basic-api.sh
./scripts/setup-azure.ps1
I due script bash avviano un container Postgres locale, applicano lo schema e generano `backend/.env` / `frontend/.env.local` per te — poi stampano i due comandi per avviare effettivamente l'app (`pip install` + avvio backend, `npm install` + avvio del dev server frontend). `setup-azure.ps1` è un sottile wrapper attorno a `infra/provision.ps1`, il vero runbook di deployment su Azure Container Apps.
---
## Funzionalità
- **Aggregazione feed** — esegue il polling di oltre 60 feed RSS di sicurezza Tier 1/2/3 secondo una pianificazione; deduplica e filtra automaticamente i contenuti promozionali
- **Triage AI** — classifica ogni voce con severità (Critical/High/Medium/Low/Informational), TTP MITRE ATT&CK e un riassunto in linguaggio semplice. Modulare per provider: API Anthropic diretta o Azure AI Foundry, commutabili tramite una singola variabile d'ambiente senza perdita di funzionalità in entrambi i casi
- **Estrazione IOC** — estrae automaticamente IP, domini, URL, hash di file e CVE da ogni voce
- **Integrazione RunZero** — sincronizza il tuo inventario asset e correla la threat intel con gli asset attivi; abbina su CVE, nomi di software, versioni OS e indirizzi IP. Tre sotto-tab sotto `RUNZERO`: **Matches** (voci correlate al tuo inventario, filtrabili per severità/data/confidenza/KEV), **Exposure** (posizione confermata/possibile a livello di organizzazione con tracciamento della remediation) e **Metrics** (andamento nel tempo di intake vs. remediation)
- **Your Stack** — definisci il software/OS nel tuo ambiente; ri-punteggia tutte le voci per rilevanza
- **Registro IOC** — registro ricercabile di tutti gli indicatori estratti con riferimenti incrociati alle voci ed esportazione STIX/CSV
- **Matrice MITRE ATT&CK** — heatmap della copertura TTP rispetto alla threat intel acquisita
- **Dashboard di salute dei feed** — stato del polling per feed, tracciamento dei fallimenti consecutivi e volume di articoli su 7 giorni
- **Detections** — una superficie di revisione a 9 tab (vedi sotto) che copre tutto ciò che è registrato come detection, che sia generato dall'AI, importato dai tuoi file o sincronizzato da un workspace Sentinel live
- **Auth modulare** — SSO Microsoft Entra ID con accesso basato su ruoli, oppure una singola API key locale condivisa con zero dipendenze da Azure. Rilevata automaticamente dal frontend; vedi [Modalità di autenticazione](#auth-modes)
### Due funzionalità legate alle detection
Questo repo in realtà distribuisce due cose correlate ma utilizzabili in modo indipendente sotto l'ombrello "detections":
1. **La tab `DETECTIONS`** — una superficie di revisione autonoma, suddivisa in nove sotto-tab:
- **All Detections** — il catalogo completo delle analytics registrate, filtrabile per tecnica/disposizione/stato di revisione, ciascuna espandibile alla sua descrizione e al KQL completo.
- **Defender Custom Detections** — lo stesso catalogo, limitato alle detection destinate alle regole di custom detection di Microsoft Defender for Endpoint anziché alle regole di analytics di Sentinel.
- **Alignment Reviews** — ogni volta che un'analytics di detection viene registrata contro una tecnica MITRE, un controllo AI confronta la sua copertura effettiva con la descrizione che MITRE stesso dà di quella tecnica. Quando diverge o copre solo parzialmente la tecnica, finisce qui come elemento di revisione umana con il ragionamento dell'AI, una correzione KQL suggerita e il risultato di validazione di quella stessa correzione (gate statico + backtest) — mai un suggerimento cieco.
- **Disposition Alerts** — una coda di rilevamento del degrado: un'analytics approvata la cui telemetria decade o la cui regola sottostante inizia a dare errori viene segnalata qui per una nuova revisione, nominata dalla sua stessa detection anziché solo dalla tecnica MITRE condivisa.
- **Generated Hunts** — le detection sono raggruppate in hunt (una per file importato oggi; una per articolo TI/detections.ai di origine una volta che quell'integrazione sarà disponibile), in linea con la funzionalità Hunts di Microsoft Sentinel. Una hunt può essere sincronizzata in un workspace Sentinel reale come oggetto `Microsoft.SecurityInsights/hunts` più le sue query di ricerca salvate costituenti (vincolata da `SENTINEL_HUNTING_SYNC_ENABLED` e da una `mode` — off/manual/auto — configurabile per team in Settings > API Settings; mai un auto-push silenzioso a meno che tu non lo attivi esplicitamente).
- **Sentinel Hunts** — l'inventario live di ciò che è effettivamente distribuito nella funzionalità Hunting del tuo workspace Sentinel, prelevato direttamente da ARM anziché dalla cronologia di sincronizzazione di questa app; include suggerimenti di test/tuning per query che puoi applicare o ignorare sul posto.
- **Sentinel Analytics Rules** — la stessa idea per le Analytics Rules di Microsoft Sentinel (`Microsoft.SecurityInsights/alertRules`) — un tipo di risorsa Sentinel distinto da Hunting, poiché queste sono ciò che effettivamente genera incidenti/alert secondo una pianificazione — con lo stesso flusso di applicazione/ignoramento dei suggerimenti di tuning.
- **Local Detections** — vedi [Esecuzione senza Sentinel o un provider AI](#running-without-sentinel-or-an-ai-provider-local-detections-import) sotto.
- **Audit Log** (solo admin) — un registro trasversale di ogni controllo che questa app ha effettivamente eseguito: risultati di gate/control-probe delle detection generate dall'AI, tentativi di sincronizzazione delle hunt Sentinel e esecuzioni di test di hunt-query/analytics-rule di Sentinel, combinati in un unico elenco paginato e filtrabile — che copre deliberatamente ciò che nessuna singola tab di revisione fa da sola.
Funziona interamente all'interno del backend principale, nessun deployment aggiuntivo necessario per la superficie di revisione stessa. Il suo design API segue deliberatamente le convenzioni di detections.ai di seguito, anche se è completamente autonomo.
2. **Orchestratore della pipeline detections.ai — in arrivo.** detections.ai ha un'API pubblica in sviluppo per la generazione di detection assistita dall'AI, e questo repo ha un'integrazione reale costruita per essa (`backend/detection_pipeline/orchestrator.py`) che prende threat intel sottoposta a triage, la verifica rispetto alla copertura di detection esistente e genera bozze di KQL per il tuo workspace Sentinel come job pianificato. Questa integrazione supporterà quell'API una volta disponibile, e non fa ancora parte di questa release pubblica. Nel frattempo, **non ne hai bisogno per usare affatto la tab Detections** — [Local Detections Import](#running-without-sentinel-or-an-ai-provider-local-detections-import) sotto copre lo stesso obiettivo di "portare detection reali in questa app" per configurazioni oggi senza generazione AI e senza Sentinel.
### Esecuzione senza Sentinel o un provider AI: Local Detections Import
Dato il nome dell'app e la sua proposta principale, la domanda singola più comune da parte di chi la self-hosta sul tier **Basic** sarà probabilmente *"Non ho Sentinel né un provider AI configurato — posso comunque ottenere qualcosa dalle tab Detections/Hunts?"* La risposta è sì: punta l'app a una cartella dei tuoi file di regole di detection (scritti a mano, esportati da un tenant Sentinel/Defender reale, o prelevati da un repo pubblico di regole Sigma/Sentinel) e li cataloga, li tagga con MITRE e li valida staticamente — nessuna connessione Sentinel e nessuna chiave `DETECTIONS_AI_API_KEY`/Anthropic richiesta per nulla di tutto ciò.
- **Formati supportati, dal primo giorno:** file grezzi `.kql`/`.txt`/`.yar`/`.spl` o con qualsiasi estensione, ciascuno opzionalmente abbinato a un sidecar `.json`/`.yaml` (`{"file": "myrule.kql", "title": "...", "description": "...", "technique_id": "T1059.001"}`) per metadati che l'export di Microsoft stesso non richiede di dichiarare separatamente; YARA; Suricata; Sigma YAML (a documento singolo o multiplo); Splunk SPL; e il JSON nativo esportato da Microsoft di Analytics Rule/Hunting Query (solo le regole di tipo `Scheduled` portano una query KQL grezza che questa app può valutare — ogni altro tipo viene riconosciuto e segnalato, non saltato silenziosamente).
- **Cosa viene effettivamente eseguito su un file importato:** validazione statica (lo stesso motore di durabilità/findings usato dal percorso di generazione AI) per il contenuto KQL; anche un controllo di allineamento MITRE, se *hai* un provider AI configurato (un asse indipendente da Sentinel — puoi avere l'uno, entrambi o nessuno dei due); tutto ciò che dipende da Sentinel (backtesting, telemetria probe, tracciamento della disposizione) resta fuori ambito e viene mostrato come "no Sentinel connection configured" anziché come una cella vuota fuorviante.
- **Dove compare:** il contenuto importato diventa una normale riga hunt/detection — stesse tabelle, stesso flusso di revisione, stessa visualizzazione della tecnica MITRE di qualsiasi cosa generi la pipeline AI — quindi appare anche nelle viste regolari `ALL DETECTIONS`/`GENERATED HUNTS`, non solo nella sua tab dedicata. La sotto-tab dedicata **Local Detections** (sotto `DETECTIONS`, solo admin per avviare un'importazione) è dove la punti a una cartella e osservi il progresso/risultati per file.
- **Setup:** imposta `LOCAL_IMPORT_DIR` su un percorso assoluto nel filesystem del backend (un volume montato, in un deployment containerizzato) — tutto ciò che viene importato deve risiedere sotto quella radice; l'UI ti consente di scegliere un sotto-percorso al di sotto di essa, mai una posizione arbitraria del filesystem. Vedi [Variabili d'ambiente](#environment-variables).
- **Provalo subito:** `examples/local-detections-samples/` include una piccola cartella pronta per l'importazione — due regole KQL valide (una abbinata a un sidecar `.json` per mostrare quel meccanismo), una regola deliberatamente non valida (per vedere il banner di invalidità segnalata) e un file non riconosciuto (per vedere il banner di importazione fallita). Punta `LOCAL_IMPORT_DIR` su di essa per vedere tutti e tre gli stati di risultato alla tua primissima importazione, senza dover scrivere regole.
**Local Detections** — un'esecuzione di importazione completata: il banner di riepilogo evidenzia i file che sono stati catalogati ma segnalati come non validi dall'analisi statica (qui, una regola che genera alert su un singolo hash hardcoded) proprio accanto a quelli importati correttamente, e ogni file diventa una normale riga hunt/detection sotto

---
## Screenshot
Tutti gli screenshot seguenti usano dati sintetici (nomi di organizzazioni fittizi, IP di esempio RFC 5737, domini `.example`) generati per la documentazione — nessuna threat intel reale o dato di clienti.
**Feed** — sfoglia e filtra le voci di threat intel sottoposte a triage con severità, tag, IOC e TTP

<br>
**Dashboard** — ripartizione della severità a colpo d'occhio e principali tecniche MITRE ATT&CK

<br>
**MITRE ATT&CK** — heatmap della matrice completa della copertura delle tecniche rispetto alla intel acquisita

<br>
**Your Stack** — definisci il tuo ambiente; le voci del feed vengono ri-punteggiate per rilevanza

<br>
**IOCs** — registro ricercabile di tutti gli indicatori estratti con esportazione STIX/CSV

<br>
**Integrations** — panoramica dei connettori per Sentinel, Defender e RunZero: stato configurato/abilitato e scorciatoie verso la tab dedicata di ciascuno

<br>
**RunZero** — correlazione degli asset, tracciamento dell'esposizione a livello di organizzazione e metriche di remediation, tutto proveniente dal tuo inventario RunZero

<br>
**Exposure** — organizzazioni classificate per numero di corrispondenze di minacce; clicca su qualsiasi scheda per vedere le voci corrispondenti

<br>
**Detections** — il catalogo completo delle analytics registrate (generate dall'AI e importate localmente allo stesso modo), ciascuna con il suo stato di gate statico/backtest/revisione e la tecnica MITRE

<br>
**Settings** — controlli di triage AI, monitoraggio della salute dei feed, punteggi di affidabilità delle fonti e gestione utenti

---
## Architettura```
┌─────────────────────────────────────────┐
│ Next.js 16 frontend (port 3000) │
│ Tailwind CSS · dark theme │
└──────────────┬──────────────────────────┘
│ REST API (Bearer token)
┌──────────────▼──────────────────────────┐
│ FastAPI backend (port 8000) │
│ APScheduler · slowapi rate limiting │
└──┬──────────┬──────────┬────────────┬───┘
│ │ │ │
Postgres AI provider RunZero API detections.ai
(modular: (asset sync) (coming soon --
Anthropic or see Features below)
Azure AI Foundry)
Backend (backend/) — Python 3.12 + FastAPI. Postgres per tutto lo storage (SQLite e Azure Blob Storage sono stati completamente dismessi). Il provider AI e il metodo di autenticazione sono entrambi selezionati tramite variabili d'ambiente, non hardcoded — vedi sotto.
Frontend (frontend/) — Next.js 16, JavaScript puro, Tailwind CSS. Rileva automaticamente la modalità di autenticazione dal backend al momento del caricamento.
Infra (infra/) — Template Azure Bicep per Container Apps, Key Vault e Container Registry (apps.bicep + platform.bicep + app-stack.bicep, distribuiti tramite provision.ps1). Rilevante solo per il tier Azure + API.
AZURE_AD_TENANT_ID impostato → Modalità Entra: SSO Microsoft Entra ID, ruoli per utente (il primo accesso diventa admin, tutti gli altri predefiniti come viewer).
AZURE_AD_TENANT_ID non impostato → Modalità locale: una singola LOCAL_API_KEY condivisa concede accesso admin a chiunque la possieda. Nessuna gestione utenti, nessuna dipendenza da Azure. Il frontend chiama GET /api/auth/mode al caricamento e visualizza automaticamente la schermata di login corrispondente — nulla da configurare lato frontend.
Entrambe le modalità emettono successivamente lo stesso tipo di JWT firmato dall'app, quindi ogni altra route (require_auth/require_admin) funziona in modo identico indipendentemente da quale modalità ha emesso il token.
Esegui scripts/setup-basic.sh o scripts/setup-basic-api.sh (vedi Livelli di deployment) — gestiscono Postgres e la generazione di .env per te. Poi:```bash
cd backend && pip install -r requirements.txt && uvicorn main:app --reload --port 8000
cd frontend && npm install && npm run dev
### Configurazione manuale```bash
cd backend
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp env.example .env # fill in required values — see Environment Variables below
uvicorn main:app --reload --port 8000
Per testare il server, è possibile utilizzare il seguente comando:
python3 server.py
Il server sarà in ascolto sulla porta 8080 per impostazione predefinita. È possibile modificare la porta modificando la variabile PORT nel file server.py.
Per testare il server, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client, è necessario modificare il file client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client, è possibile utilizzare il seguente comando:
python3 client.py
Per testare il client, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di comando e controllo, è necessario modificare il file c2_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di comando e controllo, è possibile utilizzare il seguente comando:
python3 c2_server.py
Per testare il server di comando e controllo, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di comando e controllo è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di comando e controllo, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di comando e controllo, è necessario modificare il file c2_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di comando e controllo, è possibile utilizzare il seguente comando:
python3 c2_client.py
Per testare il client di comando e controllo, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di comando e controllo è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di comando e controllo, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di inoltro, è necessario modificare il file relay_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di inoltro, è possibile utilizzare il seguente comando:
python3 relay_server.py
Per testare il server di inoltro, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di inoltro è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di inoltro, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di inoltro, è necessario modificare il file relay_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di inoltro, è possibile utilizzare il seguente comando:
python3 relay_client.py
Per testare il client di inoltro, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di inoltro è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di inoltro, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server proxy, è necessario modificare il file proxy_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server proxy, è possibile utilizzare il seguente comando:
python3 proxy_server.py
Per testare il server proxy, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server proxy è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server proxy, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client proxy, è necessario modificare il file proxy_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client proxy, è possibile utilizzare il seguente comando:
python3 proxy_client.py
Per testare il client proxy, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client proxy è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client proxy, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di tunneling, è necessario modificare il file tunnel_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di tunneling, è possibile utilizzare il seguente comando:
python3 tunnel_server.py
Per testare il server di tunneling, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di tunneling è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di tunneling, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di tunneling, è necessario modificare il file tunnel_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di tunneling, è possibile utilizzare il seguente comando:
python3 tunnel_client.py
Per testare il client di tunneling, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di tunneling è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di tunneling, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di reverse shell, è necessario modificare il file reverse_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di reverse shell, è possibile utilizzare il seguente comando:
python3 reverse_shell_server.py
Per testare il server di reverse shell, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di reverse shell è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di reverse shell, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di reverse shell, è necessario modificare il file reverse_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di reverse shell, è possibile utilizzare il seguente comando:
python3 reverse_shell_client.py
Per testare il client di reverse shell, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di reverse shell è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di reverse shell, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di bind shell, è necessario modificare il file bind_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di bind shell, è possibile utilizzare il seguente comando:
python3 bind_shell_server.py
Per testare il server di bind shell, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di bind shell è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di bind shell, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di bind shell, è necessario modificare il file bind_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di bind shell, è possibile utilizzare il seguente comando:
python3 bind_shell_client.py
Per testare il client di bind shell, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di bind shell è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di bind shell, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_client.py
Per testare il client di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il client di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il client di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il server di shell web, è necessario modificare il file web_shell_server.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il server di shell web, è possibile utilizzare il seguente comando:
python3 web_shell_server.py
Per testare il server di shell web, è possibile utilizzare il seguente comando:
curl http://localhost:8080
Se il server di shell web è in esecuzione, si dovrebbe ricevere una risposta.
Per arrestare il server di shell web, è possibile utilizzare la combinazione di tasti Ctrl+C.
Per configurare il client di shell web, è necessario modificare il file web_shell_client.py. Le seguenti variabili devono essere configurate:
SERVER_IP: l'indirizzo IP del serverSERVER_PORT: la porta del serverPer eseguire il client di shell web, è possibile utilizzare il seguente comando:
cd frontend
npm install
cp env.local.example .env.local # set NEXT_PUBLIC_API_URL=http://localhost:8000
npm run dev
```
### Docker Compose (entrambi i servizi)```bash
cp backend/env.example backend/.env # fill in required values
docker compose up --build
```
Frontend → http://localhost:3000
Backend API docs → http://localhost:8000/docs
---
## Variabili d'ambiente
Copia `backend/env.example` in `backend/.env` e compila. Raggruppate in base al tier che le richiede:
**Sempre richieste:**
| Variabile | Descrizione |
|----------|-------------|
| `PG_DSN` | Stringa di connessione Postgres |
| `JWT_SECRET_KEY` | Segreto per la firma dei token di sessione dell'app (`python -c "import secrets; print(secrets.token_hex(32))"`) |
**Auth — scegli una modalità:**
| Variabile | Descrizione |
|----------|-------------|
| `LOCAL_API_KEY` | Modalità locale: chiave condivisa che concede accesso admin. Lascia `AZURE_AD_TENANT_ID` non impostata per attivare questa modalità |
| `AZURE_AD_TENANT_ID` | Modalità Entra: tenant ID per SSO. Impostarla attiva la modalità Entra |
| `AZURE_AD_CLIENT_ID` | Modalità Entra: client ID della registrazione dell'app |
| `AZURE_AD_CLIENT_SECRET` | Modalità Entra: segreto della registrazione dell'app (solo frontend) |
| `NEXTAUTH_SECRET` | Modalità Entra: segreto di cifratura della sessione NextAuth (solo frontend) |
**AI triage — opzionale, scegli un provider (ometti entrambi per eseguire con il triage disabilitato):**
| Variabile | Descrizione |
|----------|-------------|
| `AI_PROVIDER` | `anthropic` (predefinito) o `azure` |
| `ANTHROPIC_API_KEY` | Chiave API Anthropic diretta |
| `AZURE_FOUNDRY_ENDPOINT` | Endpoint Azure AI Foundry, es. `https://<resource>.services.ai.azure.com/anthropic` |
| `AZURE_FOUNDRY_API_KEY` | Chiave API Azure AI Foundry |
| `AZURE_FOUNDRY_DEPLOYMENT` | Nome del deployment Foundry (predefinito `claude-haiku-4-5`) |
| `AZURE_FOUNDRY_API_VERSION` | Versione API Foundry (predefinita `2025-05-01`) |
**Opzionali:**
| Variabile | Descrizione |
|----------|-------------|
| `RUNZERO_API_TOKEN` | Abilita la sincronizzazione e la correlazione degli asset RunZero |
| `ALLOWED_ORIGINS` | Allowlist CORS separata da virgole (predefinita `http://localhost:3000`) |
| `ENABLE_SCHEDULER` | Imposta `false` per disabilitare il poller dei feed in background (predefinito `true`) |
| `ARCHIVE_AFTER_DAYS` | Soglia di archiviazione automatica in giorni (predefinita `90`) |
| `PG_POOL_MIN` / `PG_POOL_MAX` / `PG_POOL_TIMEOUT` | Ottimizzazione del pool di connessioni Postgres (predefiniti `1` / `10` / `30`) |
| `LOCAL_IMPORT_DIR` | Abilita [Local Detections Import](#running-without-sentinel-or-an-ai-provider-local-detections-import) — percorso assoluto sul filesystem del backend a cui ogni import è confinato. Se non impostata, la funzionalità è completamente disabilitata (la sua scheda mostra un messaggio "not configured") |
**Frontend** (`frontend/.env.local` o `frontend/env.local.example`):
| Variabile | Descrizione |
|----------|-------------|
| `NEXT_PUBLIC_API_URL` | URL del backend come visto dal browser. Incorporato nel bundle JS al momento della build. Lascia **non impostata** per instradare le chiamate API attraverso il proxy same-origin integrato (`frontend/pages/api/[...proxy].js`) — richiesto ogni volta che il backend non ha ingress pubblico (es. il Container App solo interno del tier Azure + API) |
| `BACKEND_URL` | URL del backend come visto dal server Next.js stesso. Usato dallo scambio di login di NextAuth e, quando `NEXT_PUBLIC_API_URL` non è impostata, dal proxy same-origin che inoltra lato server ogni richiesta browser a `/api/*` |
**Orchestrator detections.ai — in arrivo** (non ancora parte di questa release pubblica; documentato qui per quando sarà rilasciato. Tier Azure + API, deployable separato — vedi `backend/detection_pipeline/orchestrator.py`):
| Variabile | Descrizione |
|----------|-------------|
| `DETECTIONS_AI_API_KEY` | Richiesta per eseguire l'orchestrator |
| `SENTINEL_WORKSPACE_ID` | Customer ID (GUID) del workspace Log Analytics, per il backtesting. Opzionale |
| `PIPELINE_BATCH_SIZE` | Voci per esecuzione (predefinito `5`) |
| `PIPELINE_DRY_RUN` | `true` per acquisire e registrare senza chiamare l'API |
| `PIPELINE_LANGUAGE` | Linguaggio delle query di detection (predefinito `kql`) |
**Sentinel Hunts sync** (opzionale, disattivata per impostazione predefinita — vedi Settings > API Settings per la modalità on/off/manual/auto):
| Variabile | Descrizione |
|----------|-------------|
| `SENTINEL_HUNTING_SYNC_ENABLED` | `true` per consentire qualsiasi tentativo di sincronizzazione delle hunt. Non impostata/false è un puro no-op — zero chiamate ARM |
| `AZURE_SUBSCRIPTION_ID` | Sottoscrizione contenente il workspace Sentinel |
| `AZURE_RESOURCE_GROUP` | Resource group contenente il workspace Sentinel |
| `SENTINEL_WORKSPACE_NAME` | Il **nome** del workspace, non il suo customer ID — un valore diverso da `SENTINEL_WORKSPACE_ID` sopra, che invece è usato dal client data-plane per il backtesting |
---
## Azure Deployment
L'IaC reale e attuale è `infra/apps.bicep` + `infra/platform.bicep` + `infra/app-stack.bicep`, distribuito tramite `infra/provision.ps1` (o il thin wrapper `scripts/setup-azure.ps1`). Effettua il provisioning di Container Apps, segreti supportati da Key Vault e managed identities — Postgres stesso non è provisionato da questo repo; punta `PG_DSN` (memorizzato come segreto Key Vault `pg-dsn`) verso qualsiasi server Postgres raggiungibile.```powershell
./scripts/setup-azure.ps1
# or directly:
cd infra
cp migration.psd1.example migration.psd1 # fill in your resource group, apps, etc.
./provision.ps1
```
`provision.ps1` è idempotente — sicuro da rieseguire dopo aver modificato il manifest. Consulta il suo commento di intestazione per l'intera procedura passo-passo (piattaforma → stack applicativo → segreti → Easy Auth → importazione immagine → app → controlli successivi).
L'orchestratore detections.ai (un Container Apps Job pianificato guidato da un blocco `Orchestrator` in `migration.psd1` — vedi `migration.psd1.example` per la struttura, e memorizza la tua chiave come segreto Key Vault `DETECTIONSAIAPIKEY`) non fa ancora parte di questa release pubblica — vedi [Due funzionalità relative a detections](#two-detections-related-features) sopra.
---
## Struttura del progetto```
├── backend/
│ ├── main.py # FastAPI app, all endpoints
│ ├── db.py # Postgres queries
│ ├── pgcompat.py # connection pool + SQLite-style placeholder translation
│ ├── feed_manager.py # RSS polling, AI triage (provider-modular), scheduler
│ ├── enrichment.py # IOC extraction, KEV cache, stack rematch
│ ├── runzero_sync.py # RunZero asset sync and correlation engine
│ ├── dedup.py # CVE deduplication logic
│ ├── auth.py # Entra ID SSO + local API-key auth, app JWT sign/verify
│ ├── ioc_export.py # STIX 2.1 and CSV export
│ ├── stack_presets.py # Pre-built tech stack templates
│ ├── detection_pipeline/ # detections.ai orchestrator, MITRE alignment-check,
│ │ # Sentinel hunts/analytics-rules sync + tuning,
│ │ # audit log, local_import.py (Local Detections Import)
│ └── tests/ # pytest test suite, incl. fixtures/local_import/
├── frontend/
│ ├── pages/
│ │ ├── index.js # Main app shell + tab routing
│ │ └── login.js # Entra ID or local API-key login, auto-detected
│ ├── lib/
│ │ ├── authMode.js # GET /api/auth/mode, cached per page load
│ │ ├── authFetch.js # Bearer auth + 401-retry wrapper
│ │ └── authSession.js # token storage, JWT decode/expiry helpers
│ └── components/
│ ├── layout/ # TopBar, Sidebar, TabBar, TopFilterBar, TimeRangeToggle
│ ├── feed/ # FeedList, FeedCard
│ ├── integrations/ # IntegrationsPanel, ExposurePanel, RunZeroPanel,
│ │ # RunZeroMatchesPanel, RunZeroMetricsPanel
│ ├── detections/ # DetectionsPanel (tab shell) + one component per
│ │ # sub-tab: DetectionsCatalogPanel, AlignmentReviewPanel,
│ │ # DispositionAlertsPanel, HuntsPanel, SentinelHuntsPanel,
│ │ # SentinelAnalyticsRulesPanel, LocalDetectionsPanel,
│ │ # AuditPanel, plus shared TuningSuggestionBadge
│ ├── settings/ # SettingsPanel, CadencePicker, SeverityCards
│ └── mitre/ # MitreMatrix
├── infra/ # Azure Bicep templates + provision.ps1
├── scripts/ # Tiered setup scripts (see Deployment tiers)
└── docker-compose.yml
```
---
## Sorgenti Feed
63 feed su tre livelli:
- **Livello 1** — CISA, Cisco Talos, Fortinet Threat Signal, ESET WeLiveSecurity, Microsoft Security Blog, SentinelOne Labs, Google Project Zero, Zero Day Initiative, Check Point Research, Talos Intelligence Blog, The DFIR Report, Oracle
- **Livello 2** — Recorded Future, Malpedia, SANS ISC, Securelist, Unit42, Proofpoint TI, Malwarebytes TI, Wiz Blog, Datadog Security Labs, ReversingLabs, Sekoia, Cyble, ANY.RUN Blog e altri
- **Livello 3** — BleepingComputer, Krebs on Security, Schneier on Security, The Hacker News, Dark Reading, CrowdStrike Blog, Snyk, Semgrep e altri
---
## Sicurezza
- Tutti gli endpoint API richiedono `Authorization: Bearer <token>`
- Confronto dei token a tempo costante (`secrets.compare_digest`) per la chiave di autenticazione locale
- SQL parametrizzato ovunque — nessuna interpolazione di stringhe nelle query
- CORS limitato a una allowlist esplicita di origini
- Input LLM sanificati prima delle chiamate al provider AI; output validati prima della memorizzazione
- I container vengono eseguiti come non-root con tutte le capability Linux rimosse
- Nessun segreto incorporato nelle immagini — caricati a runtime da `.env` / Azure Key Vault
---
## Licenza
MIT — vedi [LICENSE](https://github.com/ethan-andrews/threatintel-aggregator/blob/main/LICENSE).