Compare commits

..
214 Commits
Author SHA1 Message Date
DevEnv nis2-agileandClaude Opus 4.8 4ef1faf05f [DOCS] Contesto 2026-08-25: fix Documenti Istituzionali (NO_ORG) + Organigramma freeze/MutationObserver
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-26 18:30:43 +02:00
DevEnv nis2-agileandClaude Opus 4.8 396783cd41 [FIX] Documenti Istituzionali (#499): le 5 voci standard non comparivano mai (NO_ORG)
Root cause: InstitutionalDocsController::list() usava requireAuth()+getCurrentOrgId(),
ma currentOrgId e' popolato solo da requireOrgAccess() → getCurrentOrgId() = null →
l'endpoint rispondeva sempre NO_ORG (400) → loadDocs riceveva standards=[] → la card
mostrava solo "Aggiungi voce" (screenshot Simon). L'endpoint NON aveva mai funzionato a runtime.
- Backend: list() usa resolveOrgId() (risolve X-Organization-Id senza lanciare) e restituisce
  SEMPRE le 5 voci standard; i documenti salvati solo se un'org e' selezionata.
- Frontend (dashboard.js): render condiviso + STD_FALLBACK → le 5 voci si mostrano sempre,
  anche se l'API fallisce. Buster dashboard.js 20260825.
- Backend attivato via reload php-fpm host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-26 18:14:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 69f3894c18 [FIX] RACI + Competenze: stesso anti-loop MutationObserver+createIcons dell'organigramma
Prevenzione proattiva dello stesso freeze latente trovato in organigramma: le pagine
raci.html e competenze.html richiamavano createIcons() su ogni mutazione DOM. Debounce+disconnect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-25 18:15:21 +02:00
DevEnv nis2-agileandClaude Opus 4.8 9eeef784d4 [DOCS] Help online: sezione 'Documenti istituzionali' (5 voci standard + descrizione obbligatoria, #499 follow-up)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-25 18:14:25 +02:00
DevEnv nis2-agileandClaude Opus 4.8 1f9d837c70 [FIX] Organigramma: MutationObserver+createIcons anti-loop (debounce+disconnect)
Secondo possibile innesco del freeze: lo script inline richiamava lucide.createIcons()
su OGNI mutazione DOM (MutationObserver subtree). Poiche' createIcons muta il DOM
(<i>->svg), poteva auto-ritriggerarsi. Ora: debounce 120ms + disconnect durante il
render + flag applying per ignorare le mutazioni auto-prodotte. HTML network-first -> live al reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-25 18:02:09 +02:00
DevEnv nis2-agileandClaude Opus 4.8 bfda75b292 [FIX] Organigramma: guardia anti-ciclo (client+server) — "Nuovo Ruolo" bloccava il sito
Segnalazione Simon: in Struttura interna/Organigramma il clic su "Nuovo Ruolo"
blocca l'intero sito. Causa: né il tree builder server (OrgRoleController::buildTree,
$attach) né il render client (organigramma.js orgNodeHtml) avevano una guardia
anti-ciclo su parent_role_id → con una gerarchia ciclica la ricorsione va in loop
infinito (hang del backend / freeze del render).
- organigramma.js orgNodeHtml(node, seen): salta gli id già visti (rompe il ciclo);
  orgRender passa un Set condiviso. Live via bind-mount (buster 20260806).
- OrgRoleController::buildTree: $attach porta la catena antenati e non ridiscende su
  un id già visto. ⚠️ Richiede reload php-fpm host (opcache) per andare live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-25 18:00:08 +02:00
DevEnv nis2-agileandClaude Opus 4.8 0bf17db50a [DOCS] Piano Guida a percorsi guidati NIS2 (dal metodo TRPG): mapping su asset esistenti + 3 trappole + fasi
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-10 14:11:52 +02:00
DevEnv nis2-agileandClaude Opus 4.8 cb6106d08c [DOCS] Contesto 2026-08-03: rework Rischi (matrice 4 fasce/toggle, trasversali 12 col) v1.27.0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 12:25:50 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f421f4ecdb [DOCS] Rischi rework: help online + manuale + KB (matrice 4 fasce/toggle, trasversali 12 col)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 10:44:58 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4809d8e895 [FEAT] Pagina Rischi (Simon): matrice a 4 fasce + toggle inerente/residuo; trasversali 12 col in fondo
Richieste Simon sulla finestra Rischi:
1) Matrice 5×5: eliminata la fascia 10-14; fasce = 1-2 basso (verde), 3-8 medio
   (arancione), 9-12 alto (rosso tenue), 15-25 critico (rosso). Unificate con le
   bande Alert. (matrixBand + CSS band-low/med/high/crit; rimosse level-1..10).
2) La matrice riepiloga i giudizi INERENTI o RESIDUI con un tasto di selezione
   (setMatrixView; matrixPoints legge likelihood/impact o residual_*).
3) Rischi trasversali:
   3.1 sezione spostata in FONDO (matrice dai requisiti risalita subito dopo la
       tabella derivata);
   3.2 tabella trasversali convertita alle STESSE 12 colonne (inerente+residuo+
       alert+esito), editing inline P/I via api.updateRisk (onTransversalScoreChange);
   3.3 tasti "AI Suggerisci" e "Nuovo Rischio" spostati in testa alla tabella;
   3.4 seconda matrice 5×5 dei trasversali con lo stesso toggle inerente/residuo;
   3.5 tasto Elimina per ogni rischio trasversale (confirmDeleteRisk).
- Solo frontend/statico. version 1.27.0 + SW cache bump.
- Verificato puppeteer (996002): 4 bande, 2 toggle funzionanti, 12 col, 6 delete, 0 errori.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 10:39:05 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b8c7d3c9b9 [DOCS] Contesto 2026-08-01: #501 p3 all-hazard comprimibile + ticket RESOLVED
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 18:22:43 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ed0b16eee2 [FEAT] Rischi #501 p3: sezione all-hazard mantenuta ma "Rischi trasversali" comprimibile
Compromesso approvato (Simon/Cristiano) al posto della cancellazione richiesta al
punto 3 del #501 — validato nis2-expert (all-hazards = obbligo art.21.2 Direttiva
-> art.24 D.Lgs.138/2024; rimuoverla = lacuna di conformità).
- risks.html: la sezione diventa un <details> comprimibile. Header sempre visibile
  con titolo "Rischi trasversali (approccio multirischio / all-hazards) - art. 24
  D.Lgs. 138/2024" + contatore. Apri-se-vuota (updateAllhazardHeader): vuota -> aperta
  (presidiata in audit), con voci -> chiusa (meno ingombro). Nota riformulata che la
  distingue dai "rischi di non conformità".
- i18n.js custom_heading/custom_help (IT+EN) + help.js allineati.
- Manuale + KB RISCHI_MODELLO.md aggiornati. Cache-buster help/i18n 20260731.
- Solo frontend/statico.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 10:49:10 +02:00
DevEnv nis2-agileandClaude Opus 4.8 8736d6bb4f [DOCS] Contesto 2026-07-31: correzioni Simon 12-col LIVE + proposta #501 p3 (validata, no codice)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 08:13:09 +02:00
DevEnv nis2-agileandClaude Opus 4.8 8799620fba [FIX] Rischi 12 col: titoli gruppo "Rischio Inerente/Residuo" + soglie alert 9-12 (feedback Simon)
Riscontro Simon sulla tabella Gestione Rischi:
- Titoli di gruppo sui tripletti Probabilità/Impatto/Valutazione: da
  "VALUTAZIONE RISCHIO INERENTE/RESIDUO" a "Rischio Inerente"/"Rischio Residuo"
  (risks.html thead + i18n.js grp_inherent/grp_residual IT+EN).
- Ritirata eccezione #3: banda Alert riportata a spec Simon 15-25/9-12/3-8/1-2.
  Simon ha ragione: con P,I in 1-5 i prodotti 7/11/13/14 non esistono → nessun
  buco. alertBand resta >=9 (=9-12 in pratica); aggiornati commento, help.js,
  manuale, KB (9-14 -> 9-12).
- Cache-buster help.js/i18n.js -> 20260730b (i18n va bustato o riscrive il titolo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 07:36:43 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7149e762f8 [DOCS] Contesto sessione 2026-07-30: Gestione Rischi 12 colonne (Modulo Azioni fase 1) v1.26.0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 13:03:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 371e98c3c7 [RELEASE] v1.26.0 — Gestione Rischi 12 colonne (inerente+residuo). SW cache bump.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 10:59:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ea57928226 [DOCS] Rischi 12 colonne: KB ARIA (inerente/residuo + eccezione conformità) + manuale d'uso
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 10:54:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 8a0bc5ca39 [FEAT] Gestione Rischi: tabella 12 colonne (inerente+residuo) — integrazione prototipo "Modulo Azioni" (Simon)
Integra la spec di Simon per la pagina Rischi con le correzioni normative
(parere nis2-expert + iso-27001-expert) applicate come eccezioni motivate:
- 12 colonne: Codice (link → Misure e Requisiti), Descrizione (RISCHIO DEFAULT =
  cfg_nis2_rischi.risk_descr), VALUTAZIONE RISCHIO INERENTE (P/I/PxI), Alert,
  Rischio Inerente, VALUTAZIONE RISCHIO RESIDUO (P/I/PxI), Esito residuo, Rischio Residuo.
- Editing INLINE di Probabilità/Impatto (1-5), salvataggio automatico.
- Alert a bande (15-25 Prioritaria / 9-14 Critica / 3-8 Necessaria / 1-2 Suggerita)
  → popup azioni + "crea azione" (tabella requisito_actions, seme Modulo Azioni).
- Eccezione #1: il rischio RESIDUO NON aggiorna in automatico lo STATO DI CONFORMITÀ del
  requisito (piani distinti). Col.11 "Esito residuo" = flag Rivalutare/Adeguato; conformità esplicita.
- Eccezione #3: banda 13-14 inclusa in "Critica" (9-14). "Danno €" → "Impatto" (1-5).
  Soglie = metodo interno (ISO 27001 §6.1.2), non obbligo (help/UI lo dichiarano).

Backend: mig 067; derivedList (+risk_descr +residuo), setState (+residuo), requisitoActions list/create.
help.js + i18n IT/EN. Cache-buster api/i18n/help 20260730a. Prossima mig=068.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 10:49:52 +02:00
DevEnv nis2-agileandClaude Opus 4.8 cf224480cc [FEAT] Sessione + release: refresh token silenzioso + avviso "nuova versione"
Evita la "sessione scaduta" e i deploy che restano invisibili all'utente.
- api.js: refresh PROATTIVO dell'access token ~2 min prima della scadenza
  (scheduleTokenRefresh su exp del JWT, riprogrammato ad ogni setTokens) +
  ensureFreshToken() per rinnovo immediato al ritorno sulla scheda. Con refresh
  a 7gg l'utente resta loggato senza mai vedere il login.
- common.js: watch versione (poll version.json ogni 5 min + su visibilitychange)
  → banner "È disponibile una nuova versione — Aggiorna" (reload) quando cambia;
  nudge service worker (registration.update); su visibilitychange chiama anche
  api.ensureFreshToken(). Nessun hard-refresh manuale.
- Cache-buster api/common ?v=20260729b. version 1.25.12. Solo frontend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 16:07:57 +02:00
DevEnv nis2-agileandClaude Opus 4.8 faadb62da6 [FEAT] Ruolo consulente: visibilità firm-scoped (org-switcher + Cruscotto Studio)
Un utente con role='consultant' e consulting_firm_id vede solo i CLIENTI del
proprio studio (organizations.consulting_firm_id = suo firm) UNION le proprie
membership dirette — non tutte le org (quello resta super_admin), non solo le
membership. Applicato in OrganizationController::list (selettore azienda) e
ConsultantController::portfolio (cruscotto). Additivo: super_admin e altri ruoli
invariati. Verificato: utente-test consultant/firm1 vede 2 aziende (cliente studio
996003 + membership 996001), non tutte. version 1.25.11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 15:53:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 a8516efafc [FIX] Gestione Rischi #501: tabella requisiti a scorrimento verticale + filtro per classe soggetto
- Punto 1: la tabella 'Rischi dai requisiti del framework di sicurezza' e ora
  un riquadro a scorrimento verticale (max-height 60vh, header sticky) invece di
  allungare la pagina.
- Punto 2: elenca SOLO i requisiti applicabili alla classe del soggetto
  (importante -> Allegato 1, essenziale -> Allegato 2, Det. ACN 164179/2025);
  le righe non pertinenti non sono piu mostrate (prima solo attenuate).
- help.js allineato + cache-buster help.js?v=20260729b su 35 pagine; version.json 1.25.10.
- Punto 3 (rimozione sezione all-hazard) NON applicato: contraddice la decisione
  documentata nel #500 (copertura multirischio art.24 D.Lgs.138/2024) -> escalato.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 15:11:36 +02:00
DevEnv nis2-agileandClaude Opus 4.8 15010f7dc3 [FEAT] Cruscotto Studio (frontend): companies.html vista d'insieme + menu
companies.html diventa il cruscotto dello studio consulente: barra KPI (aziende,
compliance media, scadenze in ritardo/in arrivo), griglia aziende (compliance/
rischi/incidenti + badge ritardo, clic = entra nell'azienda) e sezione "Scadenze
generali" aggregata su TUTTE le aziende ordinata per data. Una sola chiamata
api.consultantPortfolio(). Voce menu V3 "Le mie aziende" (icona briefcase) gata a
consultant/super_admin. help.js + i18n (nav.studio + companies.*) aggiornati.
Cache-buster api/topnav-v3/i18n/help ?v=20260729. version 1.25.9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 14:49:37 +02:00
DevEnv nis2-agileandClaude Opus 4.8 2bd97a6bd8 [FEAT] Cruscotto Studio (backend): ConsultantController::portfolio
GET /api/consultant/portfolio — vista d'insieme per consulente/studio: per ogni
azienda visibile all'utente (super_admin=tutte, altri=proprie membership) ritorna
compliance score, rischi/incidenti aperti, scadenze in ritardo/in arrivo; +
elenco SCADENZE AGGREGATE su tutte le aziende (incidenti CSIRT, policy, trattamenti
rischio, formazione) ordinate per data; + KPI d'insieme. Read-only, additivo.
Rotta registrata in index.php. Verificato: 5 aziende, 20 scadenze, compliance media 74%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 14:33:08 +02:00
DevEnv nis2-agileandClaude Opus 4.8 924963444e [FEAT] Rischi: suggerimento probabilità/impatto dallo stato di conformità
Nella modale "Valuta" di un rischio-requisito non ancora valutato, Probabilità e
Impatto vengono pre-compilati in base allo stato (conforme→basso 1/2, parziale→
medio 3/3, non conforme→alto 4/4) con nota esplicita "Valori suggeriti … è solo
un suggerimento, modificabili". Se già valutato, usa i valori salvati. Idea
pre-avallata dal nis2-expert (precompilare l'asse dallo stato = rischio residuo).
Solo frontend (risks.html); nessun valore imposto (art.24 non prescrive metodo).
version 1.25.8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 08:39:40 +02:00
DevEnv nis2-agileandClaude Opus 4.8 03796978c5 [DOCS] KB: modello pagina Rischi (rischi=requisiti, framing) + ingest
Doc SYSTEM per ARIA: pagina Rischi = requisiti del framework (Det. ACN 164179/2025),
framing 'rischio di non conformita', codice RSK interno (non ACN), matrice=rappresentazione
(art.24), rischi aggiuntivi all-hazard per admin. + scripts/ingest-risks-model.php.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:40:24 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f6d2076684 [FEAT] Rischi #500 opzione B: matrice dai rischi-requisito (probabilità×impatto)
Ogni rischio derivato da un requisito puo' ora essere valutato con Probabilita' e
Impatto (1-5) e compare nella matrice 5x5.
- DB: mig 066 (org_requisito_state += likelihood/impact) + runner idempotente.
- Backend: FrameworkController::setState accetta likelihood/impact; RiskController
  ::derivedList ritorna likelihood/impact/score. Applicata su prod + reload.
- Frontend risks.html: colonne Probabilita'/Impatto/Rischio(P×I) + tasto "Valuta"
  (modale, riservato a super_admin/org_admin/compliance_manager); matrice calcolata
  client-side dai rischi derivati valutati (esclusi i non_applicabile, caveat
  nis2-expert) + rischi custom. Framing normativo invariato.
- help.js/i18n aggiornati; version 1.25.7; buster help/i18n ?v=20260727b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:32:59 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f1156a81bb [FIX] Rischi #500: ripristino tasti Nuovo/AI LIMITATI ai profili admin (decisione utente)
Il supervisore autonomo (b5f936b) aveva RIMOSSO del tutto i tasti 'Nuovo Rischio'
e 'AI Suggerisci' applicando il ticket alla lettera. Decisione utente (Cristiano):
NON rimuoverli ma limitarli ai profili super_admin/org_admin — l'inserimento libero
serve a coprire i rischi all-hazard non legati a un singolo requisito (art.24
D.Lgs.138/2024, confermato nis2-expert). Ripristinato il gating (gateAdminActions):
i tasti compaiono solo agli admin, nascosti agli altri. version 1.25.6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 15:20:44 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b5f936bba2 [FEAT] Rischi #500 punto 4: rimossi tasti 'Nuovo Rischio' e 'AI Suggerisci' (elenco = requisiti Lex&ISO, sola lettura)
I rischi in Gestione Rischi corrispondono ai requisiti del framework (Determinazione ACN 164179/2025);
niente inserimento manuale/AI. Reverte l'admin-gating: la spec del richiedente e il piano originale
prevedevano la rimozione dei tasti. La sezione 'Rischi aggiuntivi' resta come elenco in sola lettura.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:13:48 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4e7e75582a [FEAT] Rischi #500 fase 2 (frontend): elenco rischi = requisiti Lex&ISO
La pagina Gestione Rischi mostra come tabella primaria (read-only) i requisiti
del framework Lex&ISO come rischi, codice RSK-<misura>Com.N via /api/risks/derived,
filtrati per classe (importante All.1 / essenziale All.2). Framing normativo
(validato nis2-expert): ogni voce = "rischio derivante dal mancato/parziale
soddisfacimento del requisito", mai "requisito = rischio"; RSK e "92" NON come
codici/numeri ufficiali ACN; matrice = strumento di rappresentazione (art.24).
- Rischi CUSTOM mantenuti in sezione "Rischi aggiuntivi (all-hazard)".
- Tasti "Nuovo Rischio"/"AI Suggerisci" limitati a super_admin/org_admin (non
  rimossi): l'inserimento libero serve al multirischio (art.24 D.Lgs.138/2024).
- api.js derivedRisks(); help.js + i18n aggiornati; version 1.25.4; SW v1.25.4.
- Cache-buster api/help/i18n ?v=20260727r. Backend/DB non toccati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 14:15:03 +02:00
DevEnv nis2-agileandClaude Opus 4.8 571e151305 [FEAT] Rischi #500 fase 2 backend: endpoint /api/risks/derived (rischi dai requisiti LEX&ISO, codifica RSK-<misura>Com.N)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:41:11 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7c711b727a [FEAT] Rischi: matrice sempre visibile, rimosse viste FAIR e KRI (ticket #500 fase 1)
Ticket #500 (Simon Fattori) - semplificazione pagina Gestione Rischi:
- matrice 5x5 ora sempre visibile sotto la tabella (rimosso bottone 'Matrice' e il view-toggle)
- rimosse le viste 'Quantitativo (FAIR)' e 'KRI' con relativi bottoni, blocchi HTML e JS
- help.js allineato (rimosse sezioni FAIR/KRI), cache-buster help.js -> 20260727a
- version.json -> 1.25.2
Rimandato a fase 2 (richiede modello dati): rischi derivati dai requisiti LEX&ISO
+ codifica RSK-<misura>Com.N + rimozione bottoni 'Nuovo Rischio'/'AI Suggerisci'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 12:15:22 +02:00
DevEnv nis2-agileandClaude Opus 4.8 896663d24c [FEAT] Dashboard: card Documenti istituzionali funzionante + UI (ticket #499)
- loadDocs: 5 voci standard sempre presenti + voci custom + 'Aggiungi voce'
- modale add/edit con descrizione OBBLIGATORIA (richiesta punto 1), link doc opzionale
- delete voci custom; controlli visibili a org_admin/compliance_manager/super_admin
- CSS .doc-* in v3.css; cache-buster dashboard.js/v3.css su dashboard.html; version 1.25.1

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:15:26 +02:00
DevEnv nis2-agileandClaude Opus 4.8 1b65321ffb [FEAT] Documenti istituzionali dashboard: backend + mig 065 (ticket #499)
Card 'Documenti istituzionali' resa funzionale (era stato vuoto onesto).
- mig 065: tabella institutional_documents (per-org, descrizione obbligatoria)
- InstitutionalDocsController: list/save/delete (org-scoped, org_admin+compliance_manager)
- 5 voci standard sempre presenti: Codice Etico, Mission, Vision, Statuto, Piano Sanzionatorio
- registrato in index.php controllerMap/actionMap

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:13:13 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ecb17e59f0 [DOCS] CONTEXT: mail a Massimo (rientro + installa app) + follow-up silent re-auth
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 14:35:45 +02:00
DevEnv nis2-agileandClaude Opus 4.8 006d06533f [DOCS] CONTEXT: v1.25.0 + fix bug sessione scaduta (segnalazione Massimo)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:12:10 +02:00
DevEnv nis2-agileandClaude Opus 4.8 41476c88f2 [FIX] api.js: sessione scaduta → login invece di pagina bloccata "--"
Bug segnalato da un tester "sono tornato su nis2 e non funziona più" (utente/
aziende "--", inventario bloccato su Caricamento, niente FAB segnalazioni).
Causa: un 401 su /auth/refresh (refresh token scaduto/invalido) rientrava nel
handler di refresh (la condizione non escludeva l'endpoint auth) → ricorsione:
doRefreshToken() non ritornava mai, quindi logout()/redirect a login non
scattava e la pagina restava rotta. Riprodotto con puppeteer (access scaduto +
refresh invalido → dashboard "--" identica allo screenshot).

Fix: escludi /auth/refresh e /auth/login dall'auto-refresh; inoltre un 401 senza
refresh token disponibile fa logout() → login. Ora un utente che torna dopo la
scadenza del token viene mandato a login (ri-accede) invece di vedere "--".
Bug PRE-ESISTENTE in api.js (non introdotto dal V3), critico ora col push tester.
Cache-buster api.js ?v=20260627u su tutte le pagine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:09:50 +02:00
DevEnv nis2-agileandClaude Opus 4.8 64e10b152b [RELEASE] v1.25.0 — UI V3: version.json + shell service worker aggiornati
- version.json 1.24.15 → 1.25.0 (MINOR: nuova interfaccia V3) con changelog
  utente; allinea footer/changelog e la versione nel bug-reporter alla UI reale.
- sw.js: cache 'nis2-shell-v1.23.0' → 'v1.25.0' + PRECACHE portato allo shell V3
  (v3.css/topnav-v3.js/inter/lucide al posto di style.css/common-bi/bootstrap-italia).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:26:36 +02:00
DevEnv nis2-agileandClaude Opus 4.8 e15e1b8645 [DOCS] CONTEXT_LAST_SESSION: V3 test completo + 2 bug fixati + polish + coda ticket vuota
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 23:40:58 +02:00
DevEnv nis2-agileandClaude Opus 4.8 a71d1a096d [FIX] V3 polish: spotlight ARIA su top-nav + CSP font segnala-anonimo
- aria-guide "Mostrami dov'è": ora aggancia le voci del menu IN ALTO V3
  (data-nav aggiunto in topnav-v3.js su voci dirette + mega-link). Se la voce è
  in un mega-menu, lo apre (.aria-guide-open) e ci punta lo spotlight; coach
  "menu in alto". Fallback navigazione invariato. (Prima cercava .sidebar-nav V2.)
- segnala-anonimo.html: CSP font-src 'self' → 'self' data: (sbloccato il font
  data: embeddato, niente più violazione CSP).
- Cache-buster: topnav-v3.js/common.js ?v=20260627t, aria-guide.js ?v=20260627t.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:57:21 +02:00
DevEnv nis2-agileandClaude Opus 4.8 95e77a668a [FIX] i18n V3: chiavi mr.title/normative.title mancanti (si vedeva la chiave)
La flotta V3 aveva messo data-i18n="mr.title"/"normative.title" sugli <h1> di
misure-requisiti e normative, ma le chiavi non erano in i18n.js → I18n.t()
ritorna la chiave → il titolo mostrava "mr.title"/"normative.title". Aggiunte
le 2 chiavi (it+en). Cache-buster i18n.js ?v=20260627p su tutte le pagine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:36:39 +02:00
DevEnv nis2-agileandClaude Opus 4.8 a223923fd6 [FIX] ARIA data-aware via chat: AiController risolve l'org per org_data_ok
ask() chiamava requireAuth() ma non requireOrgAccess(), quindi getCurrentOrgId()
restava null → org_data_ok sempre falso → ARIA rispondeva "non ho accesso ai dati"
anche con membership valida. Ora l'org si risolve da X-Organization-Id → param
org_id → currentOrgId → org primaria dell'utente; lo spoofing resta neutralizzato
dal controllo membership esistente. Verificato: ARIA ora cita i numeri reali
(rischi/incidenti/asset...) via /api/ai/ask. Bug pre-esistente (Fase B 24/6),
emerso nel test di rilascio V3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:18:47 +02:00
DevEnv nis2-agileandClaude Opus 4.8 9971fbc065 [DOCS] CONTEXT_LAST_SESSION: UI V3 live + allineamento help/i18n/KB/ARIA + mail tester
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:48:25 +02:00
DevEnv nis2-agileandClaude Opus 4.8 58b4bdbe5e [FIX] UI V3 — allineamento help/i18n/KB/ARIA al menu top-nav
Dopo il revamp V3, allineati i layer che descrivevano ancora la sidebar V2:
- topnav-v3.js: aggiunte le pagine orfane in nav (Matrice RACI → Struttura
  interna, Scadenziario → Monitoraggio, Compliance Journey → Guida) + cablato
  data-i18n su gruppi e voci del mega-menu (traduzione IT/EN come in V2).
- i18n.js: nuove chiavi nav V3 (gruppi mega-menu + connettori/whistleblowing/
  normative/cross-analysis/kb/architettura/simulazione/integrazioni/workflow/
  audit ACN/registra incidente).
- AIService.php navigationMapBlock: riscritta sul menu IN ALTO V3 (9 gruppi +
  pulsante "Registra incidente"), sync con topnav-v3.js NAV_GROUPS; ARIA non
  parla più di "barra laterale".
- help.js: corrette le 3 menzioni "sidebar" → barra/menu in alto.
- KB nis2_kb: nuovo docs/kb/V3_UI_NAVIGAZIONE.md + scripts/ingest-v3-ui-doc.php
  (ingerito host-side: 3 chunk SYSTEM, rimossi i chunk V2; ARIA RAG ora guida
  sul top-nav).
- Cache-buster ?v=20260627n sui 3 JS modificati su tutte le pagine.

Deploy: JS/HTML statici live via bind-mount; php-fpm reload (host + nis2-app)
per AIService.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:38:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 a4150b5389 [FEAT] UI V3 — flotta: chrome top-nav + design system su 32 pagine app
Migrazione UI dal V2 (Bootstrap Italia, sidebar) al V3 (top-nav mega-menu
navy/oro, Inter+Lucide self-host) fedele ai mockup docs/mockup Ui/.
- 32 pagine app convertite (incl. admin/*): head Inter+v3.css, chrome
  topnav-v3, contenuti in card V3, dati REALI preservati (id/data-* intatti),
  i18n/help/FAB reali invariati, nessun CDN esterno.
- v3.css: aggiunti widget condivisi (modal/toast/form/btn-variant/spinner/
  progress/tag/badge) + utility (.btn base, .text-muted, .sr-only) + alias
  variabili V2->token V3, così modali/form/toast di common.js funzionano
  ovunque senza style.css.
- maintenance.html: finestra estesa a ~12:15.
- docs/UI_V3_FLEET_BRIEF.md: ricetta operativa della flotta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:57:35 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b0fc190704 [FEAT] UI V3 — foundation (v3.css + topnav-v3.js + Inter/Lucide self-host) + dashboard pilota fedele al mockup
Fase 0 della migrazione UI V3 (sidebar V2 → top-nav navy/oro, font Inter, icone Lucide).
- public/css/v3.css: design system portato fedelmente dal mockup index.html
  (token :root, chrome appbar+mega-menu, card/stat/calendario/anag/doc/fab, @media ≤900/≤560),
  + aggiunte minime V3+ per stati loading/empty/error, mobile nav toggle e touch≥44px (AGID/WCAG).
- public/vendor/inter/: Inter self-host (variable woff2 latin+latin-ext) + inter.css + README (NO Google CDN).
- public/vendor/lucide/lucide.min.js: Lucide UMD self-host (NO unpkg CDN).
- public/js/topnav-v3.js: appbar + mega-menu data-driven dalla mappa IA (§6, 9 gruppi),
  aria-current sulla voce attiva, riusa gli helper di common.js (utente/logout/versione/org-switcher,
  branding, impersonate) e i 3 FAB reali (ARIA + bug-reporter + notifiche). window.loadSidebar=loadTopnavV3.
- public/dashboard.html: convertita al V3 fedele al mockup, cablata sui DATI REALI via api.js
  (overview/compliance-score/upcoming-deadlines/recent-activity/organizations-current);
  monitor giornaliero/settimanale alimentati dagli eventi reali; documenti istituzionali = stato vuoto onesto.
- public/js/dashboard.js (nuovo): wiring dati reali + render calendario/scadenze/attività/anagrafica;
  stati loading/empty/error; zero dati finti del mockup.

node --check OK su topnav-v3.js e dashboard.js. Solo UI: nessun PHP/DB/api.js toccato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:59:08 +02:00
DevEnv nis2-agileandClaude Opus 4.8 2a5d90f8bb [OPS] Manutenzione flag-gated: .htaccess (503→maintenance.html) + pagina + bypass token
Meccanismo riusabile: presenza di ../.maintenance (gitignored) → ogni richiesta 503 +
maintenance.html (mostra la finestra). Bypass ?mnt=<token>/cookie. Accendi: touch
.maintenance · Spegni: rm .maintenance. Attivato ora per il revamp UI V3 (~1h).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:42:29 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b2fa1a163e [DOCS] UI V3 mockup: estratti index/assessment/calendario + zip (fonte visiva flotta)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:18:30 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f9baf39599 [DOCS] UI V3 revamp — spec operativa per flotta di agenti (da mockup docs/mockup Ui)
Analisi dei 3 mockup (top-nav mega-menu, tema navy/oro, Inter, Lucide) → spec per
migrare l'UI dal V2 (Bootstrap Italia/sidebar) al V3: design system (token/chrome/
componenti), architettura migrazione (riusa api.js/auth/help/i18n/FAB, sostituisce
solo chrome+css), work-breakdown a fasi (F0 foundation+dashboard pilota; F1 flotta
1 agente/gruppo IA in worktree; F2 verifica), mappa IA, guardrail, acceptance per
pagina, decisioni utente aperte (§7). Mockup = fonte visiva, dati reali non mock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:18:09 +02:00
DevEnv nis2-agileandClaude Opus 4.8 eb69a4f22e [FEAT] Discovery agent AWS (EC2/RDS/S3/SG → assets-ingest)
Connettore di discovery cloud AWS parallelo all'agente di rete: elenca
EC2/RDS/S3 via AWS CLI (zero dipendenze npm) + flussi SG ingress aperti
verso 0.0.0.0/0, mappa in asset NIS2 e POSTa a /api/services/assets-ingest
(source=aws). Config via env (RESOURCES/AWS_REGION/MAX_ITEMS/DRY_RUN),
gestione errori robusta (aws assente o comando fallito → messaggio chiaro).
README aggiornato con sezione AWS + permessi IAM read-only minimi.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:15:55 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b787c327c1 [FEAT] Canale anonimo Vocea: pagina pubblica pre-login (scheletro) + risposta a Vocea
segnala-anonimo.html: pagina PUBBLICA (no checkAuth, no sidebar, no common.js/api.js/
pwa.js, no analytics) che ospiterà l'iframe del canale Vocea, conforme alle 6 regole
anonimato StarGateWB: Referrer-Policy no-referrer (meta + iframe), CSP frame-src
app.vocea.cloud, slug del canale da ?canale=<slug> (nessun id del segnalante nell'URL),
postMessage con check event.origin + envelope {source:'vocea'} e NESSUNA persistenza
del recovery code (solo autosize). Dormiente finché non c'è lo slug/MS live.
+ docs/OUTGOING_TO_VOCEA_...: conferma origin frame-ancestors https://nis2.agile.software.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 08:29:03 +02:00
DevEnv nis2-agileandClaude Opus 4.8 1fe5b7ae61 [DOCS] StarGateWB/Vocea: direttive anonimato segnalante per embed widget + gap NIS2
Ricevute da Vocea (27/6) le 6 regole per preservare l'anonimato del segnalante
nell'embed del widget. Gap analysis NIS2: la pagina app «Segnalazioni»
(whistleblowing.html) è dietro login → NON usabile come canale anonimo; serve una
pagina PUBBLICA pre-login. Più: no-log apertura, no id nell'URL iframe, check
event.origin, Referrer-Policy no-referrer, CSP frame-src app.vocea.cloud.
Origin da dare a Vocea per frame-ancestors: https://nis2.agile.software.
Da applicare all'attivazione Vocea (oggi scaffold dormiente).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 08:22:23 +02:00
DevEnv nis2-agileandClaude Opus 4.8 01b016da50 [FEAT] Connettori Discovery — UI gestione per org (Fase 4)
Pagina connettori-discovery.html + connettori-discovery.js (DiscoveryUI): lista
connettori, crea (mostra la chiave API una sola volta + comando agente), dettaglio
con storico run, rigenera chiave, elimina. Solo org_admin.
- Voce sidebar V2 "Connettori Discovery" in Operativo (common-bi.js, data-nav auto).
- api.js: metodi list/create/get/update/delete/rotateDiscoveryKey.
- ARIA navigationMapBlock: aggiunta la voce (coerenza assistente↔menu).
- help.js: _pageMap + guida contestuale "Connettori Discovery" (multi-connettore,
  come funziona, sicurezza scope ingest:assets).
Smoke: pagina 200, /api/discovery-connectors/list 401 (auth). Cache-buster
common-bi/help/api -> 20260625d.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:03:31 +02:00
DevEnv nis2-agileandClaude Opus 4.8 3d0bec1f7f [FEAT] Discovery agent di riferimento (scan rete → auto-popola NIS2) — Fase 3
scripts/discovery-agent.mjs (Node 18+, zero dipendenze): scansiona CIDR/IP via
TCP-connect (no root), reverse-DNS, deduce tipo asset + criticità dalle porte aperte,
costruisce asset (external_ref=net:<ip>) + flussi di rete e li POSTa a
/api/services/assets-ingest col X-API-Key del connettore. Concorrenza, MAX_HOSTS,
DRY_RUN, isPrivate (RFC1918+loopback+link-local). + README_discovery_agent.md (uso,
variabili, cron, multi-connettore, estensione cloud, sicurezza).
Validato: node --check + DRY_RUN reale (listener temporaneo) → rileva porta aperta,
costruisce asset+flusso corretti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:45:13 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b8ac793c8f [FEAT] Connettori discovery rete/cloud per-org → auto-popola Inventario (backend, mig 064)
Più connettori per azienda; ogni connettore ha una api_key dedicata (scope ingest:assets)
che l'agente esterno usa per mappare e auto-popolare NIS2.
- mig 064: discovery_connectors (per-org, multi) + discovery_runs (storico) + network_flows (ID.AM-03).
- DiscoveryConnectorController (org_admin): CRUD connettori + emissione/rotazione api_key
  (mostrata 1 volta) + dettaglio con ultimi run.
- ServicesController::ingestAssets esteso: accetta anche "flows" (upsert network_flows,
  dedup external_ref) e, se la chiave appartiene a un connettore, registra discovery_run
  + aggiorna last_run del connettore. Auto-scoring rilevanza NIS2 già in bulkUpsert.
- AssetController::bulkUpsert: ora salva anche "dependencies" → popola la Mappa Dipendenze.
- Router: /api/discovery-connectors (list/create/{id}/update/delete/rotateKey).
Smoke E2E (HTTP 201): 2 asset scorati + 1 flusso + run tracciato + dipendenze persistite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:38:55 +02:00
DevEnv nis2-agileandClaude Opus 4.8 cc7c6e4cf2 [FIX] ARIA: regola anti-invenzione record — stop confabulazione su reclami/ticket
Bug (segnalato "risponde mele per pere"): chiedendo ad ARIA "analizza la segnalazione
di Massimo Tagliavini e com'è stata gestita", ARIA FABBRICAVA un record falso (segnalante
"CISO", data "25 gennaio 2025", citazione inventata, gravità/stato/processo) — i ticket
dei singoli utenti vivono in AgileHub, non nei dati di ARIA → confabulava.
Fix: regola ANTI-INVENZIONE vincolante (priorità massima) nel system prompt di askWithRag:
non inventare MAI record specifici (segnalazioni/reclami/ticket/incidenti/persone/date/
citazioni/esiti) non presenti nei DATI ORG o nel contesto; i reclami/ticket dei singoli
utenti sono un sistema separato non accessibile → dichiararlo e indicare dove consultarli,
senza inventare. Smoke: ora ARIA NEGA correttamente l'accesso, nessun dettaglio fabbricato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:05:37 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ab3e88299e [FEAT] Inventario: ordinamento lista per colonna (#442)
Click su un'intestazione (Nome/Tipo/Categoria/Criticita/Rilevanza NIS2/Owner/Stato)
ordina l'inventario; secondo click inverte (freccia ▲/▼, aria-sort).
Criticita e Rilevanza ordinano per valore reale, non alfabetico.
Client-side sull'inventario gia' caricato (no paginazione su assets), si combina
coi filtri, accessibile da tastiera. Help aggiornato + cache-buster help.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 10:41:34 +02:00
DevEnv nis2-agileandClaude Opus 4.8 87fc5bf959 [FEAT] Inventario: esportazione CSV in formato import-compatibile (#441)
Nuovo pulsante "Esporta" in assets.html: scarica l'intero inventario
(tutte le pagine) in CSV con le stesse colonne riconosciute
dall'import (name, asset_type, criticality, data_classification,
internet_facing, dependencies_count, regulated, external_ref, vendor,
location). I criteri data_classification/internet_facing/
dependencies_count/regulated sono ricostruiti da relevance_criteria con
mappatura reversibile (round-trip stabile, verificato 100/100 casi).
Parser import reso quote-aware (RFC-style) per round-trip lossless di
nomi con virgola; retro-compatibile con CSV non quotato.
help.js + cache-buster (?v=20260625b) su tutte le pagine. version 1.24.14.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:13:22 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b33e0d051e [FIX] ARIA polish post #439/#440: help Inventario allineato + regola anti-invenzione
Completa i 2 punti rimandati dal supervisore:
- help.js sezione Inventario: documentate le nuove funzioni che il supervisore ha
  aggiunto (commit 9c36604) → "Mappa Dipendenze" con il campo Dipendenze per popolarla;
  "Elimina bene" (cestino, org_admin) distinto dallo stato "dismesso".
- AIService::navigationMapBlock: 2 regole nuove → (5) NON citare nomi file .html/URL
  interni; (6) NON inventare pulsanti/funzioni/passi inesistenti, usare solo le funzioni
  della guida/contesto della pagina. Elimina l'allucinazione "rischi.html" e simili.
Smoke: ARIA cita Elimina + Mappa Dipendenze, zero nomi-file, "Gestione > Rischi".
Cache-buster help.js -> 20260625a. php-fpm reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:56:25 +02:00
DevEnv nis2-agileandClaude Opus 4.8 9c36604b2b [FEAT] Inventario: Mappa Dipendenze + eliminazione beni (#440, #439)
- Nuovo pulsante "Mappa Dipendenze" in toolbar: vista delle relazioni
  tra i beni (costruita client-side da allAssets, endpoint backend gia'
  esistente dependencyMap).
- Campo "Dipendenze" (multi-select) nel form bene: consente di compilare
  la mappa che ARIA indicava ma che non aveva UI. create/update salvano
  gia' il campo dependencies.
- Pulsante "Elimina" bene nella lista e nel dettaglio (DELETE /assets/{id}
  gia' esistente, richiede org_admin).
- Dettaglio bene: dipendenze mostrate per nome anziche' per ID grezzo.
- version.json -> 1.24.13.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 08:43:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 3bd2f09a45 [DOCS] CONTEXT_LAST_SESSION 2026-06-24: Vocea scaffold + segnalazioni Massimo + Agile=studio + upgrade ARIA (A→D) + avviso tester
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:40:06 +02:00
DevEnv nis2-agileandClaude Opus 4.8 a982797fa2 [FEAT] ARIA voce naturale (proxy TTS nexus-voice-ms) + toggle voce — Fase C
Porting voce TRPG/AllTax (ElevenLabs "Sarah") sull'ARIA chat NIS2:
- AiController::tts() + route POST /api/ai/tts: proxy same-origin a nexus-voice-ms
  (config VOICE_MS_URL=172.21.0.1:4215, voce TTS_VOICE_ID=EXAVITQu4vr4xnSDxMaL,
  eleven_turbo_v2_5). requireAuth, cache MP3 sha256 (sys_get_temp_dir/nis2-tts-cache),
  validazioni (text<=800, voice_id allowlist), stream audio/mpeg, 502 se upstream non-audio.
- common.js: speak(answer) dopo ogni risposta ARIA (voce discreta vol .55, markdown
  strip), toggle 🔊/🔇 nell'header (muto persistito localStorage nis2_aria_voice),
  autoplay best-effort.
Smoke: voice-ms da nis2-app HTTP 200 audio/mpeg 19KB; /api/ai/tts 401 senza auth.
Reachability OK (no allowlist VOX necessaria). Cache-buster common.js -> 20260624g.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:58:05 +02:00
DevEnv nis2-agileandClaude Opus 4.8 fe7d0555c6 [FEAT] ARIA data-aware: snapshot dati org dell'utente nel prompt (Fase B)
Richiesta: "AI deve sapere i contenuti dei dati a cui ha accesso l'utente".
- AIService::orgDataSnapshotBlock($orgId): aggregati LIVE dell'org (compliance score,
  rischi aperti per livello + top 5, incidenti aperti, asset attivi/rilevanti NIS2,
  fornitori critici scoperti, NC aperte, formazione, policy per stato). Query con
  colonne reali (riuso DashboardController), ognuna in try/catch (degrada, non rompe).
- Iniettato in askWithRag SOLO se org_data_ok.
- SICUREZZA multi-tenant: AiController verifica membership in user_organizations
  (super_admin bypassa) → org_data_ok; scope rigoroso organization_id → niente leak
  cross-org da X-Organization-Id falsificato. PRIVACY: niente nome/fatturato (anonimizz.).
Smoke: con membership ARIA cita score 80% + 14 rischi (org 996003); senza membership
nega l'accesso ai dati. php-fpm reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:44:16 +02:00
DevEnv nis2-agileandClaude Opus 4.8 42529043d3 [FEAT] ARIA puntatore guida "Mostrami dov'è" — spotlight verso il menu (Fase D)
Porting del pattern TRPG (CHANGE_aria_voice_pointer.md) sulla sidebar V2:
- public/js/aria-guide.js (nuovo): DESTINATIONS (26 voci menu → keyword IT),
  matchDestination(question), pointTo(nav,label) = overlay + spotlight pulsante +
  puntatore animato + coach-mark "Vai →"; Esc/click per chiudere; fallback navigate
  se la voce non è visibile; rispetta prefers-reduced-motion; self-contained, no inline-CSS.
- common-bi.js: data-nav="<href senza .html>" sulle voci sidebar (aggancio puntatore).
- common.js: inietta aria-guide.js dopo login; dopo ogni risposta ARIA, se la domanda
  combacia con una destinazione mostra il bottone "📍 Mostrami dov'è: <voce>".
Frontend-only, additivo. Cache-buster common.js/common-bi.js -> 20260624f.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:39:47 +02:00
DevEnv nis2-agileandClaude Opus 4.8 bafc305503 [FEAT] ARIA allineata all'Help contestuale della pagina + pageId canonico (Fase A)
Lamentela: "AI non allineata con help online e contesto". Causa: ARIA e Help
erano due fonti separate (Help=help.js, ARIA=KB) → divergevano.
Fix (fonte unica = help.js, zero duplicazione):
- help.js: getContextText(pageId) (testo piano dell'help della pagina) + detectPageId().
- common.js: invia page_id (canonico) + page_help (stesso testo del pannello "?") a /api/ai/ask.
- AiController: passa page_id/page_help in userContext.
- AIService::askWithRag: inietta "GUIDA CONTESTUALE DELLA PAGINA" nel system prompt
  (ARIA si allinea all'help mostrato all'utente, non lo contraddice) + usa page_id
  per il trigger relevanceGrid (più robusto del title).
Smoke: ARIA cita "registro dei rischi" dall'help + "Gestione > Rischi". Cache-buster
common.js/help.js -> 20260624e (39 pagine).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:30:10 +02:00
DevEnv nis2-agileandClaude Opus 4.8 3b9f30a890 [FEAT] Agile Technology = studio di consulenza che gestisce sé stessa (consolidamento)
Opzione A (decisione utente 24/6): nessun duplicato, nessuna rinomina. Seeder
idempotente+transazionale build_agile_consulente.php:
- consulting_firm 1: anagrafica REALE (P.IVA 07776161213, sede legale Roma Piazza
  di Campitelli 2 00186 RM, PEC agile.software@pec.it) — erano dati placeholder.
- org 996003 (Nuova Agile Technology srl, P.IVA 07776161213): consulting_firm_id=1
  → prima azienda auto-gestita dallo studio.
- firm_org_assignments: studio 1 ↔ org 996003 ↔ 4 admin.
- users.consulting_firm_id=1 per Benassati(4, consulente), Silvia Garretto(103,
  legale rappr = presidenza@), Tagliavini(326), Fattori(330).
Manuale/procedure/policy (12 doc published) già su 996003, non toccati. Rollback nel file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:09:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 e5f7a7d3a6 [FIX] Widget Segnalazioni: cap descrizione 1900 char + contatore, evita errore 'Data too long operative_summary' (ticket #430)
La colonna operative_summary lato ticket-ms e' VARCHAR(2000): descrizioni
lunghe causavano l'errore SQL grezzo a video. Aggiunto maxlength + contatore
live + guard al submit nel widget locale public/js/bug-reporter.js.
Cache-buster: bug-reporter.js?v=20260624 in common.js + common.js?v=20260624
su tutte le pagine. Causa radice (dimensione colonna) inoltrata ad AgileHub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:11:19 +02:00
DevEnv nis2-agileandClaude Opus 4.8 08d4ac0cc4 [FIX] ARIA: inietta griglia esatta combo Rilevanza NIS2 nel prompt (ticket #429)
ARIA (askWithRag) conosceva solo il nome pagina + KB/fonti, non la griglia
reale delle tendine della finestra «Valuta Rilevanza NIS2» (AssetScoringService::GRID):
dava risposte generiche non corrispondenti alla finestra. Aggiunto
relevanceGridBlock() (6 criteri, opzioni, punteggi, desc dalla fonte di verita')
iniettato nel system prompt quando l'utente e nell'Inventario o chiede della
rilevanza. Additivo, nessun impatto su altri flussi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:09:24 +02:00
DevEnv nis2-agileandClaude Opus 4.8 cf23be9c77 [FEAT] Inventario Rilevanza NIS2: significato opzioni combo + dettaglio punteggio per criterio (ticket #431, #432)
- AssetScoringService::GRID: aggiunto campo 'desc' a tutte le 35 opzioni dei 6 criteri (significato operativo, additivo)
- assets.html: ogni combo mostra il significato dell'opzione selezionata + nuovo box 'Dettaglio punteggio per criterio' (live, anche alla riapertura di una valutazione salvata)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 22:43:43 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ea5966eaee [FIX] ARIA: mappa navigazione reale + contesto pagina (ticket #424)
ARIA citava voci di menu inesistenti (es. 'A4 - Analisi e Valutazione
del Rischio' / 'Fase 4.1 - Inventario') non corrispondenti alla barra
laterale, e non sapeva da quale pagina scrivesse l'utente.

- AIService::askWithRag: iniettata la mappa REALE della sidebar
  (navigationMapBlock, sincronizzata con common-bi.js) con regole che
  vietano di inventare menu/fasi/codici; aggiunto contesto pagina utente.
- AiController::ask: accetta e passa il campo 'page' dal frontend.
- common.js (ARIA FAB): invia la pagina corrente in /api/ai/ask.
- Cache-buster common.js 20260623->20260623h su tutte le pagine.

NB: questo commit include anche il fix #419 gia' applicato e live in
common.js (email bug-reporter recuperata via api.getMe quando assente
nelle claims JWT), finora non committato — incluso come backup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:12:36 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d45bd472cd [FIX] Vocea scaffold: rotta tenantInfo non versionata + portalUrl reale + namespace vault
Correzioni dallo smoke live su api.vocea.cloud (23/6, VIGILE/Vocea):
- tenantInfo(): /tenants/{slug}/info è sotto /api/wb (NON /api/wb/v1) → request() ora
  accetta baseOverride; aggiunto $apiBase (base senza /vN). I 3 /integrations/* restano /v1.
- portalUrl(): path reale /{locale}/segnala/{slug} (era /wb/{slug}); aggiunto
  portalStatusUrl() /{locale}/stato/{slug}; channelStatus espone portal_status_url.
- Vault namespace corretto: tier1__nis2-app__vocea (l'app reale è nis2-app, non nis2-agile).

Scaffold resta DORMIENTE (VOCEA_ENABLED=false). NB: VIGILE segnala che le route
/api/wb/v1/integrations sono ancora 404 (deploy MS incompleto) → non attivare finché
Vocea non completa il deploy e l'API key non è nel vault.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:50:50 +02:00
DevEnv nis2-agileandClaude Opus 4.8 637e1ec510 [FEAT] Inventario: rinomina/rimuovi sottoclassi org dalla modifica bene (ticket #426)
Aggiunti endpoint PUT/DELETE /api/assets/subclasses/{id} (org-scoped, le
sottoclassi di sistema restano protette; FK ON DELETE SET NULL scollega i
beni senza cancellarli) + link UI «rinomina»/«rimuovi» accanto a «aggiungi».
Cache-buster api.js + bump version 1.24.8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:42:02 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7529c59a75 [FIX] Inventario: combo Rilevanza NIS2 ripopolate alla riapertura (ticket #427)
La valutazione rilevanza salva relevance_criteria come mappa piatta
{criterio: optionValue} (AssetController::score), ma showScoringModal
leggeva prev[key].value (forma a oggetto) -> sempre undefined -> nessuna
combo pre-selezionata alla riapertura. Letto ora il valore-stringa diretto,
con fallback retro-compatibile alla forma a oggetto {value:...}.

Fix solo frontend (JS inline in assets.html), nessuna migrazione.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 18:36:33 +02:00
DevEnv nis2-agileandClaude Opus 4.8 bd1e95542c [FIX] ARIA chat: indicatore 'sta scrivendo' + input multi-riga (Shift+Invio)
Risolve #418/#420 (ARIA sembrava non rispondere / serviva riaprire il pannello):
l'attesa della risposta /api/ai/ask (RAG+Claude, alcuni secondi) non mostrava
alcun indicatore -> ora compare 'ARIA sta scrivendo...' con input disabilitato
(anti doppio-invio). Indicatore rispetta prefers-reduced-motion (AGID/WCAG).

Risolve #425: il campo di scrittura era <input> a riga singola -> ora <textarea>
con Invio=invia, Shift+Invio=a capo, auto-crescita e aria-label.

common.js e' statico (no PHP/USR2): bump cache-buster ?v=20260623 su 39 pagine
(public + admin) + version.json 1.24.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 18:13:38 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4e2e56ec0b [FEAT] Vocea whistleblowing — scaffold integrazione (dormiente) + handoff AgileHub
Scaffold per sostituire il modulo whistleblowing interno (plaintext) con Vocea
(nexus-whistleblowing-ms), prodotto zero-knowledge della suite. Modalita C
(integrazione verticale, nessuna crypto re-implementata). DORMIENTE: VOCEA_ENABLED
=false di default -> zero impatto sul modulo legacy.

- docs/sql/063_vocea_channel.sql + application/cli/migrate_063_vocea_channel.php
  (idempotente): organizations += vocea_tenant_slug/channel_status/enrolled_at.
  NON ancora applicata al DB.
- application/services/VoceaService.php (nuovo): client API integratori (X-API-Key
  su api.vocea.cloud) per submit/status/reports/keyholders + portalUrl (embed
  wb-link.js). Guard enabled()/isEnabledForOrg(): se OFF/non provisioned ritorna
  DISABLED/NOT_PROVISIONED senza chiamate di rete.
- config.php: costanti VOCEA_ENABLED/BASE_URL/API_KEY/PORTAL_URL (default off).
- WhistleblowingController: branch dormiente in list() (delega a Vocea se attivo) +
  endpoint GET channelStatus + helper voceaOrg/listViaVocea. Modulo legacy invariato.
- public/index.php: route GET:channelStatus.
- docs handoff: richiesta a VIGILE (OUTGOING) + risposta VIGILE (INCOMING).

Attivazione a MS live: applicare migrate_063 + VOCEA_ENABLED=true + VOCEA_API_KEY
(vault tier1__nis2-agile__vocea__*) + slug canale su organizations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:07:49 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ef9a7282c5 [DOCS] Contesto: sedi Nuova Agile (anagrafica completa) + mail a Simon con richieste ACN aperte (92 vs 87 + date Determinazioni)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:26:29 +02:00
DevEnv nis2-agileandClaude Opus 4.8 be44e33b81 [CLIENT] Nuova Agile: sedi (legale Roma + operativa Modena) + scope SGSI allineato
Anagrafica: sede legale Piazza di Campitelli 2, 00186 Roma (address/city); sede operativa Strada Scaglia Est 15, 41121 Modena (mission). Scope/contesto SGSI resi precisi: sedi registrate ma modello work-from-anywhere. Anagrafica Nuova Agile ora completa (P.IVA + sedi + classificazione + DPO).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:20:05 +02:00
DevEnv nis2-agileandClaude Opus 4.8 128c19ac79 [CLIENT] Nuova Agile: P.IVA + nomina DPO esterno (Cristiano Benassati) — TUTTE le 5 NC chiuse + contesto sessione
P.IVA 07776161213; DPO esterno Cristiano Benassati (user 4) in organigramma/SGSI/Manuale + membership. NCR-9005 chiusa -> 0 NC aperte. Determinazione NIS2 (p10) gia' committata in 51dc757; qui anche p10 script + p11 + aggiornamento CONTEXT_LAST_SESSION.md (blocco G: task #8/#9/#10 + determinazione + chiusura NC).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:02:55 +02:00
DevEnv nis2-agileandClaude Opus 4.8 51dc757352 [CLIENT] Nuova Agile: Determinazione di applicabilita NIS2 formalizzata + chiusura NC maggiore
Dati Direzione: fatturato 0,9M€(2026)/1,4M€(2027), ~12 persone → SOTTO-SOGLIA. Cloud provider (Allegato I) ma soggetto al cap dimensionale → NON obbligata per legge; la Direzione DELIBERA l'adozione VOLONTARIA integrale delle misure da soggetto 'importante'. Org: turnover+voluntary_compliance=1+designation_basis=voluntary. Manuale sez.11 'Caveat' → 'Determinazione di applicabilita NIS2' (+snapshot). NCR-9001 (maggiore) CHIUSA + CAPA-1010 completata. DPO: esterno deliberato (nominativo in corso) → NCR-9005 resta in correzione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:51:19 +02:00
DevEnv nis2-agileandClaude Opus 4.8 05a1436d8c [I18N] Modulo Fornitori bilingue IT/EN (#8b) — v1.24.5
- supplier-portal.html (portale esterno OTP): i18n self-contained (dizionario inline + toggle IT/EN, ~79 stringhe), flusso OTP/token/ID/API invariato; domande questionario restano dati (non tradotte).
- supply-chain.html (interno): i18n estesa (5->25 data-i18n + 97 I18n.t).
- i18n.js: +82 chiavi sp.* (38->120). Cache-buster i18n.js su supply-chain.html. node --check OK, pagine 200, parita IT/EN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:32:18 +02:00
DevEnv nis2-agileandClaude Opus 4.8 14c4c2a7d2 [STD] Version-provenance sulle segnalazioni (feedback-version-provenance) — mig.062, v1.24.4
feedback_reports += client_surface/client_version/client_build; FeedbackController/Service li salvano; feedback.js li invia (surface web-v2 + version/build da version.json). Standard hub_standards id=30. Smoke verde (provenance salvata, test pulito). NB: il widget feedback.js è oggi dormiente (rimosso dall'iniezione 14/6) → plumbing pronto per la riattivazione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:14:13 +02:00
DevEnv nis2-agileandClaude Opus 4.8 3b1d909e6e [KB] Re-ingest guida UI V2 corretta in nis2_kb (sostituisce i chunk stale -bi.html del 12/6)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:06:56 +02:00
DevEnv nis2-agileandClaude Opus 4.8 26d0b8569c [DOCS] Contesto ultima sessione 2026-06-20 (UI V2 completata + cliente Nuova Agile end-to-end + gestione documentale ISMS + consolidamento 36->12)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 16:55:43 +02:00
DevEnv nis2-agileandClaude Opus 4.8 27bbed3ec9 [CLIENT] Nuova Agile: allineati i riferimenti incrociati post-consolidamento
Dopo la fusione 36->12, corretti i rinvii nei 12 documenti pubblicati: 'Politica per la Sicurezza delle Informazioni' -> 'Politica Generale...'; rinvii a 'Procedura di Controllo dei Documenti' -> 'Procedura di Governance del SGSI'; rinvio all'Istruzione Hardening (assorbita) -> regole di hardening nella Politica Uso Accettabile/Dispositivi/Remoto; riscritta la sez.8 'Struttura documentale' del Manuale sui 12 documenti. Aggiornati anche gli snapshot v2.0. Auto-referenze interne (minuscole) lasciate invariate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 15:42:40 +02:00
DevEnv nis2-agileandClaude Opus 4.8 c05520c7da [CLIENT] Nuova Agile: consolidamento documentale lean (36 -> 12) + archiviazione originali
Approccio ISO 27001 agile/lean: i 36 documenti v1.0 sono fusi per tema in 12 documenti consolidati v2.0 (Manuale, 6 politiche, 5 procedure), pubblicati. I 36 originali sono ARCHIVIATI (storico preservato, non cancellati). Idempotenza ignora gli archiviati (fix collisione titolo procedura incidenti).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 15:33:41 +02:00
DevEnv nis2-agileandClaude Opus 4.8 6ca78f9669 [CLIENT] Nuova Agile: recepito modello SGSI agile / work-from-anywhere (5 doc pubblicati + scope + 4 rischi)
ISO 27001 lean per azienda cloud-native senza sede: Linea Guida modello agile + Politiche Lavoro da remoto/Dispositivi aziendali/Posta aziendale + Procedura custodia e backup chiavi cloud (key escrow controllato). Scope/contesto SGSI aggiornati (nessuna sede, work-from-anywhere, responsabilita condivisa col cloud). Rischi R-011..R-014 (reti non fidate, furto dispositivo, perdita chiavi cloud, commistione privato/lavoro). Documenti PUBBLICATI v1.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:57:32 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7b8a604739 [DOCS+KB] Gestione documentale: help (Permessi documentali) + i18n matrice IT/EN + ingest KB AI (v1.24.3)
- help.js: sezione 'Permessi documentali (ciclo di vita SGSI)' nel help Impostazioni
- settings.html: matrice permessi bilingue IT/EN (azioni/ruoli/hint) + cache-buster help.js
- KB AI: docs/kb/GESTIONE_DOCUMENTALE_SGSI.md + application/cli/ingest_doc_management_kb.php (ingerito in nis2_kb scope SYSTEM, ricercabilita verificata)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:40:28 +02:00
DevEnv nis2-agileandClaude Opus 4.8 e245e76671 [FEAT] Permessi documentali profilabili per ruolo (Impostazioni > Permessi documentali) — v1.24.2
I diritti sul ciclo di vita dei documenti SGSI sono ora configurabili per ruolo (matrice Ruolo x Azione, org-scoped) invece che cablati.
- mig.061: tabella isms_doc_permissions (org x capability x role x allowed)
- IsmsModelController: requireDocCapability() sostituisce i requireOrgRole cablati nelle 7 transizioni (edit/submit/approve/publish/reject/archive/new_version); default = comportamento precedente; super_admin sempre ok; endpoint GET/PUT /api/isms/docPermissions (canEdit)
- settings.html: tab 'Permessi documentali' con matrice checkbox (lettura per i membri, salvataggio Amministratore/Direzione)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:25:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d29195b2c1 [FEAT] ISMS documenti: download unico set (Word) + editor in-app contenuto bozze (v1.24.1)
- exportAllDocumentsWord: GET /api/isms/documentsWordAll -> .doc unico (copertina+indice+page-break) di tutto il set non archiviato
- getDocument: GET /api/isms/documents/{id} (full doc per editor)
- updateDocument: guard NOT_EDITABLE su documenti non-bozza/revisione + ruoli estesi a board_member
- UI isms.js: pulsante 'Scarica tutto (Word)', 'Modifica' (editor contenteditable + toolbar + toggle HTML) per bozze/revisione
- help.js: editor in-app + download unico

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:11:57 +02:00
DevEnv nis2-agileandClaude Opus 4.8 68694a0f38 [FEAT] Gestione documentale ISMS — ciclo di vita ISO 27001 cl.7.5 + export Word (v1.24.0)
Ogni documento del Modello SGSI ha un ciclo di vita completo Bozza->In revisione->Approvato->Pubblicato->Archiviato con tracciamento (revisore/approvatore/pubblicatore, date, entrata in vigore, prossimo riesame) e storico versioni (snapshot). Download Word .doc modificabile per documento.
- mig.060: stati published/archived + 10 colonne tracciamento + tabella isms_document_versions
- IsmsModelController: submit/approve/publish/reject/archive/newVersion/versions/exportWord + listDocuments arricchito
- index.php: 8 route documents/{id}/(submit|approve|publish|reject|archive|newVersion|versions|word)
- isms.js: UI badge stato + pulsanti transizione + download Word + storico (IT/EN inline); cache-buster
- help.js: sezione 'Ciclo di vita dei documenti (cl.7.5)'
- 31 documenti di Nuova Agile pubblicati v1.0 (build_nuova_agile_p6_publish)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 13:40:21 +02:00
DevEnv nis2-agileandClaude Opus 4.8 94e3a4293e [CLIENT] Nuova Agile Technology srl — bonifica post-audit AUD-001 (2o ciclo operativo + chiusura NC)
Evidenze operative + chiusura findings: 10 trattamenti rischio, 11 assegnazioni formazione completate, 15 esecuzioni controlli periodici, SoA avanzata, DPO interim al RSGSI, 5 CAPA. Chiuse 3 NC operative (rischi/formazione/controlli); restano aperte le 2 di governance (determinazione NIS2 = delibera Direzione; nomina DPO formale). Gap analysis completata 80%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 11:16:06 +02:00
DevEnv nis2-agileandClaude Opus 4.8 67e5e9f4fd [CLIENT] Nuova Agile Technology srl — simulazione audit interno combinato ISO 27001+NIS2 (AUD-001)
Audit registrato live nel modulo Audit interni: 21 voci di checklist (clausole ISO 27001 4-10 + controlli Annex A/SoA + misure NIS2 art.23/24/25), 5 Non Conformità aperte (1 maggiore: determinazione applicabilità NIS2 non formalizzata; 4 minori: trattamento rischi, formazione, controlli non eseguiti, DPO non nominato). Verdetto: SGSI documentato ma non ancora operativo, non pronto per Stage 2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 10:47:35 +02:00
DevEnv nis2-agileandClaude Opus 4.8 1c2eced7a4 [CLIENT] Nuova Agile Technology srl (org 996003) — build completo SGSI/NIS2
Provisioning idempotente di un'azienda cliente cloud-only (Aruba/Hetzner/AI, no server in sede, PC portatili):
- Fase 1: org + classificazione NIS2 (important, sub_threshold_candidate/preliminary) + 3 membri esistenti collegati + organigramma 8 ruoli
- Fase 2: 12 asset (NIS2-rilevanti) + 4 fornitori + 10 rischi + gap analysis (80 risposte, 45%)
- Fase 3: ISMS model + SoA 111 controlli (27001/27017/27018) + 31 documenti (Manuale/10 politiche/13 procedure/7 istruzioni)
- Fase 4: 15 piani di controllo periodici + 5 corsi formazione
Documenti AI-redatti con regole fonti-certe (D.Lgs.138/2024 art.23/24/25; 24h/72h/1 mese). Sorgenti in _docs_na/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 10:19:23 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f9b2c9d6d7 [KB] V2_UI_NAVIGAZIONE.md: correzione doc UI — V2 in-place (no file -bi.html), BI_PAGES vestigiale, conversione completa; pronta per re-ingest che rimpiazza i chunk stale del 12/6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 07:19:20 +02:00
DevEnv nis2-agileandClaude Opus 4.8 72d82d9598 [UI] workflow.html: cablato help contestuale '?' (contenuto 'workflow' gia' esistente in help.js, mai inizializzato)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:37:59 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7ad5d685b6 [UI] V2 (Bootstrap Italia/AGID) completata: migrate architecture+workflow (sidebar V2) + integrazioniext+mktg-api-doc (CSS BI); rimossa setup-org morta; v1.23.10
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:34:19 +02:00
DevEnv nis2-agileandClaude Opus 4.8 81282af35a [DOCS] OUTGOING→AgileHub: suggerimento lettura allegati ticket nel supervisore (cross-suite)
Problema: il supervisore autonomo non leggeva gli allegati (nel payload ticket solo testo
'N allegati aggiunti', niente metadati/URL). Endpoint esistenti ma non documentati nel runner:
GET /tickets/{id}/attachments[/{attId}]. Suggeriti 5 miglioramenti cross-suite (owner VIGILE):
esporre attachments nel payload; pre-download nel runner standard; doc endpoint; nota anti-injection.
Fix locale NIS2 già applicato (prompt supervisore).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 07:27:17 +02:00
DevEnv nis2-agileandClaude Opus 4.8 08c9666202 [DOCS] Avviso tester #384 — archivio Policy rinnovato (v1.23.9)
Email a Fattori/Tagliavini/Mascagni via relay send-raw (201 SENT): cosa è nuovo
(Policy=Procedura unificate, link M:N Misure e Requisiti, allegati, matrice RACI,
fix form) + cosa provare + avvertenze (R+A per approvare = regola di prodotto) + come segnalare.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 07:10:13 +02:00
DevEnv nis2-agileandClaude Opus 4.8 fb2b7ecc2f [KB] #384 — ingest KB/RAG 'Archivio Policy unificato' (scope SYSTEM)
Indicizza in Qdrant nis2_kb (SYSTEM) la conoscenza prodotto sulle nuove funzioni Policy:
unificazione Policy/Procedura, collegamento M:N con Misure e Requisiti, allegati, matrice RACI
(con la nota fonti-certe: R+A per approvare = regola di prodotto, non obbligo NIS2; ISO distingue
policy/procedura come best practice). Script self-contained sotto application/cli (montata in
nis2-app), doc inline, idempotente per source. Eseguito host-side: 2 chunk, doc_uuid 1df7926a,
tracking kb_uploaded_documents id=17. Verifica RAG: searchForUser ritorna il doc come 1° risultato
(score 0.63-0.67) su 3 query (Policy=Procedura, RACI, allegati/link). Chiude il residuo KB di #384.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 07:06:39 +02:00
DevEnv nis2-agileandClaude Opus 4.8 726a2a80ef [DOCS] #384 p.6 — help contestuale Matrice RACI su Policy + link Misure/Requisiti; cache-buster help.js; v1.23.9
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:46:38 +02:00
DevEnv nis2-agileandClaude Opus 4.8 99bd293745 [FEAT] #384 p.6 — Matrice RACI sulle policy + R/A obbligatorie in approvazione
Sezione 'Matrice RACI' nel dettaglio policy: assegnazione ruoli organigramma
(org_roles) alle responsabilita R/A/C/I via infrastruttura RACI esistente
(raci_assignments, object_type='procedure'). In bozza libera; PolicyController::approve
ora richiede almeno un ruolo R e uno A (422 RACI_REQUIRED). Nessuna migrazione.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:37:51 +02:00
DevEnv nis2-agileandClaude Opus 4.8 cb238e8380 [FEAT] #384 p.2.2/3 — colonne multi-valore + link attivi bidirezionali Policy<->Misure e Requisiti
Misure e Requisiti: colonna Policy (default) mostra tutte le policy collegate al
requisito come codici Policy.NN cliccabili -> aprono la policy (policies.html?policy=ID).
Policy: colonna Requisito NIS2 mostra tutti i requisiti collegati (codifica MISURA+
REQUISITO, es. 'DE.CM-01 comma 1.') come link -> evidenziano il requisito
(misure-requisiti.html?requisito=ID). Backend: FrameworkController::catalog espone
policies[] per requisito e PolicyController::list espone requisiti[] per policy
(org-scoped, tabella requisito_policy mig.059, try/catch con degrado al dato di default).
Solo lettura/visualizzazione: nessuna modifica dati, nessuna migrazione.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:25:16 +02:00
DevEnv nis2-agileandClaude Opus 4.8 02f3e69227 [FEAT] #384 p.2.2/3 fondazione — modello relazionale REQUISITO<->POLICY (mig.059)
Migrazione 059 additiva/idempotente/reversibile:
- policies.proc_code VARCHAR(16) NULL: ripristina la chiave di join verso il
  catalogo canonico (cfg_nis2_procedure/cfg_nis2_requisiti), che andava persa
  quando le 42 procedure di default venivano copiate nell'elenco Policy (mig.048).
- tabella requisito_policy: M:N org-scoped requisito<->policy (un requisito puo'
  avere piu' policy e una policy piu' requisiti) — base per p.2.2 e p.3.
- backfill sicuro: proc_code valorizzato su 42 policy (match esatto titolo) +
  116 associazioni di default popolate (INSERT IGNORE, WHERE proc_code IS NULL).
- PolicyController::ensureFrameworkProcedures: i nuovi seed conservano proc_code
  e popolano requisito_policy (try/catch dedicato, non blocca il seed).

Nessun impatto utente in questo passo (nessuna superficie legge ancora i nuovi
campi). UI multi-valore + link bidirezionali (2.2/3) nei prossimi cicli. v1.23.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:12:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 8a8045904b [FEAT] #384 p.5 — allegati file su Policy (sezione Allegati nel dettaglio) + allowlist server-side upload evidenze
- policies.html: sezione 'Allegati' nel dettaglio policy (Bootstrap Italia card),
  upload (entity_type=policy) via /api/audit/evidence/upload, lista con download/autore/data
- AuditController::uploadEvidence: allowlist estensioni server-side (anti stored-XSS same-origin)
- version.json -> 1.23.5

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:00:01 +02:00
DevEnv nis2-agileandClaude Opus 4.8 6f3ca4b4bd [FEAT] #384 p.2.1 — ricodifica display Proc.->Policy in Misure e Requisiti
I codici delle policy/procedure di default si presentano con prefisso
"Policy." (es. Proc.01 -> Policy.01) tramite helper policyCode().
Solo etichetta UI: proc_code canonico invariato come chiave di join
requisito<->policy (non rompe il futuro link bidirezionale, punto 3).
v1.23.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:45:05 +02:00
DevEnv nis2-agileandClaude Opus 4.8 6496c9655d [FEAT] #384 p.1 — terminologia Policy unificata
Colonna 'Procedura (default)' -> 'Policy (default)' in misure-requisiti.html;
colonna/dettaglio/campo modale 'Art./Articolo NIS2' -> 'Requisito NIS2' in policies.html;
testi descrittivi pagina Misure e Requisiti + guida help.js allineati al lessico 'policy'.
Cache-buster help.js?v= 20260631 -> 20260618p1 su 28 pagine. version.json 1.23.2 -> 1.23.3.
Punti 2/3/5/6 del ticket restano in lavorazione a fasi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:18:25 +02:00
DevEnv nis2-agileandClaude Opus 4.8 5e6e6f6617 [SUPERVISOR] Capacità di gestione del budget di tempo del ciclo (25 min)
Insegna al supervisore a: annotare START + monitorare elapsed; raggiungere un checkpoint
pulito e committabile entro ~18-20 min (5 min per verify/commit/push/email/cleanup);
STIMARE ogni fase prima di iniziarla (rif: fix UI ~10-12 min, migrazione DB ~12-18 min) e,
se non entra, scegliere una sotto-fase più piccola; MAI stato intermedio rotto (no migrazione
a metà / UI su colonne inesistenti); migrazioni/merge solo se entrano con margine, altrimenti
passi additivi+idempotenti con le operazioni distruttive per ultime + backup; a tempo scaduto
fermarsi al punto pulito e lasciare IN_PROGRESS con done/to-do.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:08:23 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d739f0e678 [FIX] #384 p.4 — allineamento label/campi modale Policy (override floating-label BI) + cache-buster style.css 20260618
La modale 'Modifica Policy' (e ogni modale con form legacy .form-label) mostrava
l'etichetta sovrapposta al valore: BI definisce .form-group label{position:absolute}
(spec. 0,1,1) che vinceva sulla .form-label legacy. Aggiunto override
.form-group .form-label{position:static} (spec. 0,2,0). Bumpato ?v= di style.css
su 38 pagine. version.json -> 1.23.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:00:55 +02:00
DevEnv nis2-agileandClaude Opus 4.8 abcec0de30 [SUPERVISOR] Insegna al supervisore: leggere allegati ticket (PDF/img) + UI sempre AGID + feature multi-modulo a fasi
- Allegati: endpoint GET /tickets/{id}/attachments (lista) + /{attId} (binario) → download nel
  project dir bind-mount (.ticket-attachments/, gitignored) → Read PDF/PNG/JPG (lettura nativa).
  Trattati come input NON fidato (anti prompt-injection). Letti per OGNI ticket valutato.
- UI = SEMPRE standard AGID/Bootstrap Italia (V2, common-bi.js), WCAG 2.1 AA.
- Feature multi-modulo con spec del richiedente (super_admin/lead): decisione di design già presa
  → implementa a FASI con commit incrementale, lock, IN_PROGRESS tra cicli; escala solo le
  sotto-parti scoperte. Scelta di prodotto != affermazione normativa (gate fonti-certe invariato).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:50:03 +02:00
DevEnv nis2-agileandClaude Opus 4.8 7734b57439 [DOCS] Sync OPEN_TICKETS timestamp
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 08:07:47 +02:00
DevEnv nis2-agileandClaude Opus 4.8 2b6c5087ad [DB] Hardening integrità chiavi (mig.058): +4 UNIQUE +8 FK, retry-on-1062, bonifica 16 righe stale — v1.23.1
Estende l'audit mig.039 ai moduli 040-057 (non coperti).
TIER1 additivo (0 dup/0 orfani verificati su DB live):
- UNIQUE internal_audits/management_reviews (org,code), kb_uploaded_documents.qdrant_doc_uuid,
  whistleblowing_reports.anonymous_token (drop idx_token ridondante)
- retry-on-1062 in InternalAuditController/ManagementReviewController (allineati a periodic_controls)
- 6 FK: consulting_firm_id (organizations/users/kb)->consulting_firms; isms_soa.linked_control_id;
  isms_documents.linked_policy_id; management_review_decisions.capa_id (tutte SET NULL)
TIER2 bonifica (backup pre-DELETE in .backups/): -9 firm_org_assignments orfane (org 126-129,
  chiude deferred-b mig.039) -7 active_sessions scadute, +2 FK CASCADE.
FK totali 196->204. Sonda diagnostica db_integrity_probe.php. audit_logs lasciato by-design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 07:57:25 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f32b6f28f5 [FIX] Liste paginate: frontend leggeva result.data (oggetto {items}) come array
Causa "Errore di connessione" su Inventario (e liste analoghe): gli endpoint
list usano jsonPaginated → data = {items,total,page,per_page,pages}, ma il
frontend faceva `allAssets = result.data || []` e poi .filter/.forEach/.length
sull'oggetto → TypeError → catch → messaggio d'errore (e stat a "--").

Allineato il consumo a jsonPaginated (come gia' faceva risks.html):
- public/assets.html  — Inventario (loadAssets): estrae data.items + per_page=100
- public/incidents.html — lista incidenti (renderIncidentsTable)
- public/reports.html — visualizzatore Audit log (loadAuditLogs): data.items +
  paginazione da data.pages/page (prima usava result.pagination/total_pages inesistenti)
- public/admin/organizations.html, public/admin/users.html — liste admin
  (data.items + paginazione da data: total/per_page/page)

Tutti gli estrattori sono difensivi (gestiscono anche un eventuale array bare).
Verificato: endpoint reali tornano data={items,...}; nessun consumer rotto
restante (listNCRs/listFeedback non usati da pagine; /feedback/mine resta array).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 12:14:04 +02:00
DevEnv nis2-agileandClaude Opus 4.8 896caa829c [DOCS] Landing: 4 nuovi moduli (SGSI ISO27001, Governance/Stakeholder, Audit&Riesame, Controlli periodici&Calendario) — 12->16
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:31:29 +02:00
DevEnv nis2-agileandClaude Opus 4.8 1758074a06 [DOCS] Contesto sessione: modulo Controlli periodici v1.23.0 (mig.057) + sync ticket
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:26:06 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4fdeae7cee [FEAT] Modulo Controlli periodici (control monitoring §9.1/A.8.16) — v1.23.0
Registro controlli ricorrenti (owner/frequenza/metodo/controllo SoA-NIS2 collegato)
+ registro esecuzioni come EVIDENZA (data/esito/note) con avanzamento automatico
della scadenza; da esito non conforme generazione NC o NC+azione correttiva
(riuso non_conformities/capa_actions, nuova source 'monitoring'); report
scheda+storico stampabile; scadenze nel Calendario unico.

mig.057: periodic_controls + periodic_control_executions (FK CASCADE, UNIQUE
(organization_id, code)). PeriodicControlController (slug periodic-controls) +
controlli-periodici.html + CalendarController.srcPeriodicControls.

Fix da flotta di verifica avversariale (9 finding confermati):
- advance(): correzione overflow strtotime '+1 month' (31/01 -> 28/02, no salto
  mese) via DateTimeImmutable con clamp all'ultimo giorno valido (mensile/
  trimestrale/semestrale/annuale); giornaliero/settimanale/custom invariati.
- create(): retry-on-duplicate del codice CTL-NNN con UNIQUE (org_id, code).
- update(): array_key_exists per azzerare owner_role_id / next_due_date.
- delete(): rimossa cleanup morta su review_schedule (il calendario legge diretto).
- CalendarController.srcReviewSchedule: esclude internal_audit/stakeholder_activity
  (gia' con sorgente diretta) -> fix doppia comparsa nel calendario.
- calendario.html: TYPE_META 'periodic_control' (chip filtro + label + colore).

Additivo. Cache-buster ?v= bump (sidebar) + sw cache nis2-shell-v1.23.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:21:03 +02:00
DevEnv nis2-agileandClaude Opus 4.8 0738504fdd [DOCS] Analisi+design modulo Controlli periodici (control monitoring: frequenza + esecuzioni/evidenza + NC/AC) — documentato con esempi GRC/Eramba
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:19:15 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d479b301d3 [DOCS] Rilascio ISO 3 moduli: avviso tester + contesto (KB+email chiusi, v1.22.0, prossima mig=057)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:52:28 +02:00
DevEnv nis2-agileandClaude Opus 4.8 79ca72fba9 [FEAT] ISO-readiness: Audit interni (§9.2) + Riesame Direzione (§9.3) + Calendario scadenze (mig.055-056, v1.22.0)
Rilascio unico (3 moduli) costruito in flotta parallela + verifica avversariale.

MODULO A — Audit interni (ISO 27001 §9.2, mig.055): internal_audits + internal_audit_items;
codice AUD-NNN; checklist pre-popolata (clausole 4-10 + Annex A applicabili dal SoA);
esiti per riga; apertura non conformità collegata a NCR/CAPA (source polimorfico);
report HTML stampabile. InternalAuditController + internal-audits.html.

MODULO B — Riesame di Direzione (ISO 27001 §9.3, mig.056): management_reviews +
management_review_decisions; codice RD-AAAA-NN; INPUT aggregati automaticamente dai
moduli (gather: NC/CAPA, audit interni, rischi+trattamenti, KPI/score, obiettivi, formazione,
stakeholder, normative, scadenze), congelati nello snapshot all'approvazione; decisioni;
verbale stampabile. ManagementReviewController + management-review.html.

MODULO C — Calendario unico scadenze: aggregatore SOLA LETTURA (nessuna migrazione) di tutte
le scadenze (incidenti/policy/rischi/NC-CAPA/formazione/stakeholder/audit/riesame/review_schedule),
griglia mensile + lista + filtri + deep-link. CalendarController + calendario.html.

Integrazione: router (3 controller+actionMap), api.js, sidebar V2+legacy, help.js (3 sezioni),
i18n (IT+EN), review_schedule ENUM += internal_audit. v1.22.0 + sw cache + cache-buster 20260630.

Verifica flotta (28 agenti, 4 dim + avversariale): 9 finding confermati, TUTTI corretti —
MAJOR gatherRisks (risk_treatments.organization_id inesistente → JOIN risks); nextCode numerico
(no dup >99/anno); footer report audit con etichetta "ISO buona prassi, non obbligo"; help 24→25
clausole; ARIA tab/calendario; focus modali; tasti su celle calendario; rimossi helper api morti.

Smoke prod OK (calendario 18 eventi, audit AUD-001 25 item + report + audit→NCR + audit→calendario,
riesame gather/decisione/approve/report, gatherRisks ora available); org 151 ripulita. Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:48:49 +02:00
DevEnv nis2-agileandClaude Opus 4.8 18a4a0b509 [DOCS] Analisi Modulo C — Calendario unico scadenze (aggregatore read-only) + stima aggiornata A+B+C
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 06:59:29 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d6f445121e [DOCS] Design+stima moduli Audit Interni (9.2) e Riesame Direzione (9.3) — chiusura gap ISO 27001 lato SGSI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 06:55:20 +02:00
DevEnv nis2-agileandClaude Opus 4.8 fc1b424283 [DOCS] Valutazione copertura NIS2 Agile vs audit ISO 27001:2022 (per direzione Agile) + checklist sorgente
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 06:49:48 +02:00
DevEnv nis2-agileandClaude Opus 4.8 3d80aa8c57 [DOCS] Avviso tester (parole chiare): novità C5 Stakeholder/Attività/portale + cosa provare
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 06:18:18 +02:00
DevEnv nis2-agileandClaude Opus 4.8 1ce5fb485f [DOCS] Salvataggio contesto: TLS DB #9 DONE (sign-off VIGILE) + scoperta prod su php-fpm host
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 00:02:45 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d583acf9cd [DOCS] TLS DB nis2_user — DONE (sign-off VIGILE): enforce REQUIRE SSL già attivo dall'11/6
- CLAUDE.md testata: stato TLS DB del prodotto DA FARE → ✅ DONE. Pre-equip PHP-CA fatto
  (app in TLS 1.3) + enforce REQUIRE SSL su nis2_user@'%' già attivo (applicato da VIGILE
  l'11/6 nel cutover container; ssl_type=ANY). Confermato indipendentemente con SHOW CREATE USER.
- Gotcha documentato: in MySQL 8.0 SHOW GRANTS NON mostra REQUIRE SSL (solo SHOW CREATE USER) —
  era la causa del mio falso allarme "non enforced".
- Aggiunto il doc di conferma di VIGILE (INCOMING_FROM_AGILEHUB_2026_06_16_tls_enforce_gia_attivo).

Chiude l'ultimo open-item infra coordinato. Nessuna azione DB residua.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:59:28 +02:00
DevEnv nis2-agileandClaude Opus 4.8 93b21eeb43 [DOCS] OUTGOING→AgileHub/VIGILE: richiesta enforce TLS REQUIRE SSL su nis2_user (pre-equip PHP-CA fatto, de-rischiato)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:37:40 +02:00
DevEnv nis2-agileandClaude Opus 4.8 2552062c48 [DOCS] Contesto: chiusura open-items C5 (email gated #7, EMAIL_MS_URL interno #8, DB pre-equip #9, 92 prevale Simon)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:33:33 +02:00
DevEnv nis2-agileandClaude Opus 4.8 69873e21be [FEAT] Epic C / C5 — chiusura open-items: email gated + fonti + provenienza 92/87
- #7 (go-live ready): send() aggancia l'invio email del questionario/firma-lettura via
  EmailService, GATED dal kill-switch EMAIL_SENDING_ENABLED (oggi=false → ZERO invii;
  pronto al go-live). Magic-link assoluto via APP_URL; invio solo ai target con email
  valida; i link restano sempre disponibili per la condivisione manuale. Smoke prod:
  email_sent=0, email_enabled=false (nessun invio).
- #2 (prevale Simon): nis2_sources.php — aggiunta NOTA DI PROVENIENZA sul 92 vs 87
  (prodotto usa 92 = file referente; ACN ufficiale 87; divergenza nota/accettata).
  Numero NON modificato per decisione confermata.

Note operative (host, fuori repo):
- #8: EMAIL_MS_URL spostato sul path interno http://172.21.0.1:8081/api/emails
  (verificato 400 con X-Internal-Key = funzionante; pre edge-strip). Rollback: URL pubblico.
- #9: pre-equip PHP-CA del DB CONFERMATO (CA presente, app connessa in TLS 1.3); enforce
  ALTER USER nis2_user REQUIRE SSL resta azione DB coordinata con VIGILE (utente @%).

v1.21.3. Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:32:27 +02:00
DevEnv nis2-agileandClaude Opus 4.8 6992bdcf14 [DOCS] Contesto: open-item C5 risolti (upload via setfacl host, fonti ACN) + escalation 92vs87 a Simon
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:16:02 +02:00
DevEnv nis2-agileandClaude Opus 4.8 3051983003 [FIX] Epic C / C5 open-items — upload ripristinato + fonti ACN aggiornate + UI
- UPLOAD ALLEGATI RIPRISTINATO in prod (causa: il sito è servito da php-fpm HOST e
  public/uploads era git:git senza ACL per www-data → move_uploaded_file falliva;
  fix host: setfacl -R -m u:www-data:rwx -m d:u:www-data:rwx public/uploads). Collaudato:
  upload interno+portale .pdf 201, .html→422. Risolve anche evidence_files/visure.
- nis2_sources.php (fonti-certe): Determina 333017/2025 datata 22 settembre 2025 e marcata
  STORICA/superata; aggiunta entry VIGENTE 379887/2025 (efficace dal 31/12/2025); URL ACN
  autorevoli (date-giorno marcate [da confermare sul testo ufficiale], verifica nis2-expert).
- UI attività: create+assign non lascia più attività orfane su errore (no duplicati su retry,
  messaggio chiaro) + assegnazione individuale richiede almeno un destinatario; blocco PWA di
  stakeholder-activities allineato (favicon-16/status-bar/application-name). v1.21.2.

ESCALATION (decisione di Simon, non modificata): nis2_sources.php riga 79 riporta "37 misure /
92 requisiti" per gli importanti (dato file Simon), ma la pagina UFFICIALE ACN indica "37/87".
Da riconciliare con Simon (open item #2). NON cambiato per rispettare la decisione bloccata.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:11:40 +02:00
DevEnv nis2-agileandClaude Opus 4.8 f065d70dda [DOCS] Contesto sessione 2026-06-16 (sera): Epic C COMPLETO — C5 Stakeholder LIVE (C5.1+C5.2+hardening, v1.21.1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:55:25 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b917d2da14 [FIX] Epic C / C5 hardening — esiti flotta di verifica multi-agente (mig.054)
Corretti i finding confermati dalla verifica multi-agente (5 major + minori):

SICUREZZA
- Stored XSS allegati: da blocklist a ALLOWLIST di estensioni innocue (no html/svg/js
  renderizzabili same-origin) in StakeholderPortalController::attachment e
  StakeholderActivityController::storeUpload; nome file randomizzato (random_bytes).
- Magic-link: scadenza (mig.054 stk_activity_targets.token_expires_at; send() imposta
  scadenza attività+30gg o +90gg; resolveTarget() → 410 TOKEN_EXPIRED se scaduto);
  rate-limit per-IP sugli endpoint del portale; comment/attachment bloccati su attività chiusa.

CORRETTEZZA
- send(): NON rigenera token né azzera lo stato dei destinatari già responded/acknowledged
  (prima ne perdeva l'esito); imposta token_expires_at.
- assign individuale: semantica "replace" (rimuove i deselezionati non ancora conclusi) +
  guard su lista vuota (evita 'IN ()').
- update(): conserva assign_mode esistente quando si modifica solo stak_code.

UI/UX/A11Y
- Editor opzioni per domande a scelta singola/multipla (prima degradavano a testo nel portale).
- Etichette stato/tipo localizzate; risposte mostrate inline (no alert()); escAttr nel portale
  (escape virgolette negli attributi); ARIA su modali/tab; voce sidebar anche in common.js.

OPEN ITEM (NON regressione C5, pre-esistente e ambientale): l'upload allegati restituisce
UPLOAD_ERROR in prod — move_uploaded_file/is_uploaded_file fallisce nella topologia
proxy→fastcgi (stesso pattern di evidence_files/AuditController, mai funzionato: la dir
uploads/evidence non esiste). rename/copy come www-data funzionano. Da investigare lato infra.
Il resto di C5 (questionari, firma-lettura, commenti, calendario, portale) è pienamente operativo.

Smoke prod OK: allowlist (.html→422, struttura ok), opzioni scelta nel portale, send no-reset,
replace individuale, token_expires_at presente. Additivo. v1.21.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:52:31 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4f386faae5 [FEAT] Epic C / C5.2b — Portale esterno stakeholder + sotto-dashboard feedback (mig.053)
Completa C5 (Epic C). Gli stakeholder rispondono in self-service tramite magic-link
(token SHA-256 per destinatario, NESSUN account/JWT); il compliance manager vede gli
esiti, i commenti e gli allegati nel dettaglio dell'attività.

- StakeholderPortalController (non-JWT, token-only): access / respond (questionario) /
  acknowledge (firma di avvenuta lettura) / comment / attachment. Submit one-shot (409),
  validazione risposte obbligatorie, anti-IDOR (un token = un solo destinatario),
  auto-completamento attività quando tutti hanno risposto/firmato.
- StakeholderActivityController: feedback (risposte per destinatario), comments
  (GET/POST), attachments (upload interno + lista; riuso evidence_files entity_type=
  'stk_activity', file sotto public/uploads/stk_activity/{org}/).
- mig.053: stk_activity_responses (answers JSON / acknowledged_at), stk_activity_comments
  (interni/esterni). Estende il seeder idempotente.
- Frontend: stk-portal.html (pagina pubblica dependency-free: questionario per tipo di
  domanda o testo+firma, commento, upload); dettaglio attività in stakeholder-activities.html
  con esiti, thread commenti e allegati.

Email disattivate (kill-switch) → i magic-link si condividono manualmente. Smoke prod OK
(access no-auth, respond+required+409, acknowledge+WRONG_TYPE, comment esterno/interno,
bad-token 404, feedback interno, auto-complete; cleanup org 151 pulita). Additivo. v1.21.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:20:14 +02:00
DevEnv nis2-agileandClaude Opus 4.8 a5ff29e0da [FEAT] Epic C / C5.2a — Attività stakeholder: questionari tipo + attività + calendario (mig.052)
Fondazione delle attività verso gli stakeholder (Simon C5 §4):
- Questionari tipo (template): tipo questionario-da-compilare / firma-di-avvenuta-lettura,
  domande (JSON) + collegamento m2m a procedure (policies) E a misure/requisiti del
  framework (cfg_nis2_misure/requisiti).
- Attività: basate (opz.) su un template, assegnabili a un CODICE stakeholder (tutti
  quelli di quel tipo) o a singoli stakeholder; pianificabili con data e scadenza.
- Calendario NIS2: review_schedule esteso (ENUM entity_type += 'stakeholder_activity');
  su create/update con scadenza la riga di calendario viene sincronizzata.
- Invio: genera un magic-link per destinatario (token SHA-256) per il portale esterno
  (C5.2b). Email disattivate (kill-switch) -> i link si condividono manualmente.

mig.052: stk_questionnaire_templates, stk_template_(procedures|misure|requisiti),
stk_activities, stk_activity_targets, stk_activity_procedures + ALTER review_schedule.
StakeholderActivityController + /api/stakeholder-activities/*; stakeholder-activities.html
(2 tab: attività / questionari tipo + dettaglio invio); voce sidebar V2; help/i18n.
Smoke prod OK (template m2m, attività by_code+calendario, assign, send magic-link,
anti-IDOR individual, negativi 422; cleanup org 151 pulita). Additivo. v1.20.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:12:11 +02:00
DevEnv nis2-agileandClaude Opus 4.8 c782aa54fa [FEAT] Epic C / C5.1 — Modulo Stakeholder: registro + matrice di Mendelow (mig.051)
Registro dedicato degli stakeholder (tabella `stakeholders`, NON una colonna su
suppliers: gli interni non sono fornitori; gli esterni si COLLEGANO opzionalmente
a un supplier esistente senza duplicare il dato).

- mig.051: cfg_stakeholder_types (30 di sistema Stak.01-30 + org-added da Stak.31),
  cfg_stakeholder_quadrants (4, range corretti: Q4 potere 0-2), stakeholders,
  stakeholder_procedures (m2m -> policies). Seeder idempotente seed_stakeholders.php.
- StakeholderController: list/create/update/delete + types/addType + quadrants + pickers.
  Quadrante CALCOLATO a read-time dai range config. Anti-IDOR su org_role/supplier/
  policy/tipo; coerenza kind (interni->organigramma, esterni->fornitore); punteggi 0-5.
- api.js: metodi stk*. stakeholders.html riscritta: registro + matrice di Mendelow
  in SVG dependency-free + modali (stakeholder, nuovo tipo). Voce sidebar V2 (common-bi.js).
- Help aggiornato; cache-buster ?v=20260626 su tutte le pagine; version 1.19.0; sw cache v1.19.0.

Ancoraggio GV.SC-02 (obbligo, art. 24 D.Lgs. 138/2024). La matrice di Mendelow e'
etichettata come BUONA PRASSI, non obbligo NIS2 (regola fonti-certe). Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:54:57 +02:00
DevEnv nis2-agileandClaude Opus 4.8 419162594d [DOCS] Handoff C5 (Stakeholder) — testimone per nuovo prompt
Briefing auto-contenuto: spec verbatim Simon C5, fonte docs/simon/C5_EleStakeH.xlsx (30 tipi),
validazione normativa (Mendelow=best practice, refuso Q4=potere 0-2, GV.SC-02), esistente da
riusare (org_roles/policies/cfg_nis2_requisiti/NCR-CAPA/review_schedule/suppliers), fasi C5.1/C5.2,
convenzioni operative critiche (deploy host SSH, migration runner, common-bi.js sidebar+cache-buster,
multi-tenancy, smoke tester), flotta verifica finale + help/trad/KB, open items.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:18:51 +02:00
DevEnv nis2-agileandClaude Opus 4.8 8df8a5d91d [DOCS] Contesto sessione 2026-06-16: Epic C C1-C4 LIVE (mig.046-050, v1.18.4)
C1 (config Misure/Requisiti + consultabile + valutazione per requisito + copia procedure),
C2 (Determinazione), C3 (rimosso Gap ACN, lg231 intatta), C4 (inventario 2 voci + sottoclassi).
Flotta di verifica su C1 (6 finding corretti). 2 bug pre-esistenti risolti (apostrofo JS inventario,
cache-buster common-bi). Resta C5 (stakeholder, a fasi) + flotta finale + help/trad/KB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:16:33 +02:00
DevEnv nis2-agileandClaude Opus 4.8 30f4614b37 [FEAT] Epic C / C4 — inventario a 2 voci + sottoclassi configurabili (mig.050)
Simon C4: nell'inventario DUE sole voci principali; le sottoclassi le crea l'utente.
Riconduzione concordata (no perdita di dettaglio): hardware/network/facility -> ID.AM-01;
software/service/data -> ID.AM-02; personnel -> Organigramma (0 asset personnel presenti).

- Config: cfg_inventory_voci (2: ID.AM-01 Hardware, ID.AM-02 Software/Servizi/Sistemi),
  inventory_subclassi (12 default di sistema org NULL + sottoclassi org-scoped).
- assets.voce_code + subclass_id (FK). 20 asset esistenti MIGRATI (5 ID.AM-01 / 15 ID.AM-02).
- AssetController: list con label voce/sottoclasse; create/update validati (voce in whitelist,
  sottoclasse appartenente a org/default+voce); GET/POST /assets/subclasses; import (bulkUpsert)
  deriva la voce dal tipo legacy. asset_type resta come campo legacy (viste secondarie).
- assets.html: filtro per voce, form con selettore Voce + Sottoclasse (+ '+ aggiungi'),
  lista/dettaglio mostrano 'voce · sottoclasse', stat-chips per voce.
- FIX bug pre-esistente: apostrofo non escapato ('l'inventario') che ROMPEVA tutto il JS
  della pagina Inventario. + cache-buster common-bi.js (era a v=20260618, non bumpato dal fix C3).
Help + KB + i18n. Verifiche prod: subclasses 200, create voce/legacy OK, validazione 422,
JS valido (node --check), migrazione idempotente. version 1.18.4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 19:59:35 +02:00
DevEnv nis2-agileandClaude Opus 4.8 e281aa4970 [FEAT] Epic C / C3 — rimosso modulo 'Analisi GAP ACN' (superato da C1)
Simon C3: l'analisi gap ACN non è un artefatto normativo e va eliminata; la conformità
emerge da C1 (elenco Misure/Requisiti + valutazione per requisito in org_requisito_state).

Ricognizione → migrazione → backup → rimozione (sicuro, 0 perdite):
- Tabelle acn_assessments/acn_assessment_responses VUOTE (0 righe) -> droppate (mig.049,
  guard anti-drop se >0 righe). Ricreabili da 036. Nessun dato utente perso.
- Integrazione lg231: /api/services/gap-analysis legge 'assessments' generale, NON il modulo
  ACN -> intatta. Verificato.
- RaciController ripuntato su cfg_nis2_misure (fonte canonica) al posto di acn_measures.json
  (rimosso); validazione measure_code verificata (/raci/objects?type=measure -> 43 misure).
Rimossi: AcnAssessmentController, public/acn-gap.html, application/data/acn_measures.json,
routing acn-gap, voci sidebar (common.js + common-bi.js), metodi api.js acn*, sezione help 'acn',
chiavi i18n acn.*, mapping demo-selectors. Tour demo step 3 RIPUNTATO su misure-requisiti
(no step rotto, 10 step). Sidebar BI ora mostra 'Misure e Requisiti' (gap di common-bi.js sanato).
Verifiche prod: /acn-gap.html 404, /api/acn-gap/* 404, /misure-requisiti 200, RaciController OK.
Cache-buster ?v=20260624, version 1.18.3, SW v1.18.3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 19:24:29 +02:00
DevEnv nis2-agileandClaude Opus 4.8 45e6c3599c [FIX] Epic C / C1 — correzioni post-verifica flotta (1 major + 5 minor)
Flotta di verifica (14 agenti): sicurezza pulita, fedeltà del dato perfetta (codifica
Simon intatta, 0 scostamenti). Corretti i 6 finding confermati:
- MAJOR: PolicyController::ensureFrameworkProcedures ora in transazione + SELECT...FOR UPDATE
  sulla riga org -> no procedure duplicate su fallimento parziale (rollback) o richieste concorrenti.
- MINOR: importer idempotente committato application/cli/seed_framework.php (DDL + upsert da
  nis2_framework_seed.json) -> re-seed riproducibile/versionato (verificato 2 run = 0 duplicati).
- MINOR: allineato '92 requisiti' importanti (dato autoritativo Simon) in help.js + nis2_sources.php
  (era 87, contraddizione interna); coerente '24 requisiti aggiuntivi essenziali' (116-92).
- MINOR (UI): troncamento anteprima valutazione sul RAW prima di esc() (no entita HTML spezzate).
- MINOR (UI a11y/AGID): modale valutazione con focus iniziale, ripristino focus, focus-trap.
Cache-buster ?v=20260623, version 1.18.2, SW nis2-shell-v1.18.2.
NB residuo da verificare: data esatta Determinazione 333017/2025 (mese senza giorno) in nis2_sources.php.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:40:31 +02:00
DevEnv nis2-agileandClaude Opus 4.8 1f2d48df7c [DOCS] Epic C / C1 operativo — help + KB + cache-buster + v1.18.1
- help.js 'misure-requisiti': sezione 'Valutazione e stato di conformità' (stato per
  requisito, valutazione del rischio che parte vuota, copia procedure di default) + nota.
- KB AI: doc SYSTEM su valutazione conformità + procedure/rischi di default (uuid 92979c05).
- Cache-buster ?v=20260622 (35 pagine), version.json 1.18.1, SW cache nis2-shell-v1.18.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:19:40 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4570ce3ba1 [FEAT] Epic C / C1 operativo — copia procedure di default al 1° accesso (mig.048)
PolicyController::list ora, al primo accesso al modulo Procedure di un'org reale,
copia le 42 procedure di default (cfg_nis2_procedure) in policies come contenuto
editabile/cancellabile (decisione utente: copia per-org al 1° accesso).
- organizations.framework_procedures_seeded_at = idempotenza (no ri-copia).
- categoria policies assegnata da mappa famiglia NIST->ENUM; nis2_article=misura;
  content riporta misure coperte + nota 'personalizzabile e cancellabile'.
- Salta org demo/sandbox (isDemo) per non inquinare il dataset dimostrativo.
- Best-effort: errore nel seed NON blocca la lista (log + return).
Smoke prod: org 151 3->45 policy (42 default, 10 categorie), idempotente al 2° accesso,
golden 151 poi ripristinato (cleanup). Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:18:28 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b34bf3f9f6 [FEAT] Epic C / C1 operativo — valutazione/stato di conformità per requisito (mig.047)
org_requisito_state (org x requisito -> stato + valutazione del rischio + note),
la valutazione 'parte vuota'. È da qui che emerge lo stato di conformità (sostituirà il Gap ACN, C3).
- FrameworkController: catalog ora include lo stato per-org di ogni requisito (risoluzione org
  OPZIONALE con verifica accesso = anti-IDOR); nuovo POST /framework/state (upsert, requireOrgRole
  org_admin/compliance_manager, validazione stato + requisito esistente).
- misure-requisiti.html: colonna Stato/Valutazione (se org) + modale di valutazione (stato,
  valutazione del rischio, note). api.frameworkSetState.
Smoke prod: catalog has_org/org_class OK; upsert non_conforme persistito e riletto; ISOLAMENTO
multi-tenant verificato (org 152 non visibile a org 151). Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:41:16 +02:00
DevEnv nis2-agileandClaude Opus 4.8 92d101b6f4 [FEAT] Epic C / C1 — Elenco Misure/Requisiti consultabile (read-only) + FrameworkController
UI data-driven sulle tabelle di config cfg_nis2_* (mig.046):
- FrameworkController::catalog (GET /api/framework/catalog): 43 misure / 116 requisiti,
  procedura+rischio di default per requisito, flag importanti/essenziali, classe org.
- public/misure-requisiti.html (Bootstrap Italia/AGID): vista per misura / per area politica,
  selettore soggetto (auto/importante/essenziale/tutti), righe che si 'spengono' per classe,
  ricerca, sola lettura (codifica non modificabile). Voce sidebar in sezione Compliance.
- api.js frameworkCatalog(); routing index.php; i18n nav.framework IT/EN.
- help.js: guida 'misure-requisiti' (struttura, importanti/essenziali, riferimenti art.23/24 D.Lgs.).
- KB AI: doc SYSTEM ingestato (uuid 7871c1ea).
- Cache-buster ?v=20260621 (35 pagine), version 1.18.0, SW cache nis2-shell-v1.18.0.
- docs/simon/: file sorgente autoritativi (provenienza seed, codifica Simon non variata).

Smoke prod: /framework/catalog 200 (43/116, 6 misure solo-essenziali, GV.OC-04->Proc.01/Risk.01),
pagina 200, USR2 applicato. Additivo, nessuna modifica a dati esistenti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:29:08 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d37047987f [FEAT] Epic C / C1 — tabelle di configurazione framework NIS2 (mig.046)
Fondazione data-driven (direttiva utente: configurabile via DB, no JSON hardcoded).
Fonte autoritativa: docs/simon/C1_Misure_Requisiti.xlsx + Vista (codifica Simon NON variata).

Tabelle system-level, sola-lettura per l'utente, configurabili da super_admin:
- cfg_nis2_ambiti (18 categorie NIST GV/ID/PR/DE/RS/RC)
- cfg_nis2_misure (43 misure; 6 solo-essenziali; area politica a..p, applies_important/essential)
- cfg_nis2_requisiti (116 requisiti; 92 importanti / 116 essenziali; ord N immutabile; FK misura/proc/risk)
- cfg_nis2_procedure (42 procedure di default Proc.01-42)
- cfg_nis2_rischi (84 rischi di default Risk.01-85)

Seed via application/data/nis2_framework_seed.json (import-only; runtime legge le cfg_*).
Applicato su prod (additivo, reversibile via DROP). Runner temp self-contained gia rimosso.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:07:12 +02:00
DevEnv nis2-agileandClaude Opus 4.8 690af688a0 [DOCS] Contesto sessione 2026-06-15 (notte): A4 COMPLETA 5/5 (RACI 4.3 + Scadenziario 4.4 + Stakeholder 4.5 live, commit b4d47d5->64866be, v1.17.2), help+i18n+KB aggiornati, 2 email ai tester (ringraziamento + punto del progetto); prossimo: feedback E2E tester sui nuovi moduli
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:23:05 +02:00
DevEnv nis2-agileandClaude Opus 4.8 64866be2ce [DOCS] A4 design: Fasi 4.4 (scadenziario) e 4.5 (stakeholder) segnate LIVE — A4 COMPLETA 5/5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:12:24 +02:00
DevEnv nis2-agileandClaude Opus 4.8 c5b00ccd96 [FEAT] A4 Fasi 4.4 + 4.5 (A4 COMPLETA 5/5): scadenziario centralizzato review_schedule (mig.044; ReviewScheduleController list/create/update/delete/complete/sync) + stakeholder estesi clienti/partner GV.SC-02 (mig.045 suppliers.stakeholder_type, SupplyChainController::stakeholderMap). Pagine review-schedule.html + stakeholders.html (Bootstrap Italia/AGID). Workflow build+review adversariale (3 major risolti: 045 da DELIMITER -> ALTER bare runner-safe). help GV.SC-04/05 allineati al titolo canonico ACN. Cache-buster ?v=20260620.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:10:28 +02:00
DevEnv nis2-agileandClaude Opus 4.8 c7d36c910f [DOCS] A4 design: Fase 4.3 RACI segnata LIVE + rettifica nomi reali (capa_actions/non_conformities, object_id polimorfico anti-IDOR)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:46:15 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b4d47d58a2 [FEAT] A4 Fase 4.3 — Matrice RACI + link m2m: raci_assignments + procedure_inventory/procedure_risk/inventory_risk/risk_measure (mig.043), RaciController org-scoped (anti-IDOR, validazione object/measure), raci.html+raci.js (Bootstrap Italia/AGID), routing+sidebar+api+help+i18n. Build+review adversariale via workflow (0 finding critical/major). Cache-buster ?v=20260619.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:44:08 +02:00
DevEnv nis2-agileandClaude Opus 4.8 05dfba6fcd [DOCS] Contesto sessione 2026-06-15 (sera-2): A4 Fase 4.2 Competenze implementata e live (skills/role_skills/user_skills/skill_course_map, gapGrid, openAction NCR+CAPA, help+KB+PWA), review adversariale 0 finding, email tester inviata; prossimo 4.3 RACI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:07:20 +02:00
DevEnv nis2-agileandClaude Opus 4.8 5368b0a61c [FEAT] A4 Fase 4.2 — Competenze (skill/requisiti ruolo/gap) + alert→azione NCR+CAPA
Secondo modulo del modello relazionale A4 (docs/DESIGN_A4_RELATIONAL.md).

Backend:
- Migration 042 (docs/sql/042_competences.sql) + runner aggiornato (scripts/migrate-a4.php).
  4 tabelle additive/idempotenti: skills (org-owned o globali), role_skills
  (competenze richieste dal ruolo, UNIQUE role+skill), user_skills (possedute,
  UNIQUE org+user+skill), skill_course_map. APPLICATA su prod (nis2-db v8.0.45,
  TLSv1.3).
- CompetenceController (route 'competences'): catalog, skills CRUD, roleSkills,
  userSkills, skillCourses (map/unmap), gapGrid (richiesto−posseduto per ruolo
  con titolare), openAction. Multi-tenancy + anti-IDOR + livelli 1-5; competenze
  globali in sola lettura per gli utenti org.
- alert→azione: openAction RIUSA il workflow NCR/CAPA reale (la tabella azioni è
  capa_actions figlia di non_conformities, NON 'corrective_actions' che non
  esiste): crea NCR (source='management_review', source_entity_type='competence_gap',
  source_entity_id=role_skills.id) + capa_actions figlia. Anti-duplicato sul gap.
  Zero ALTER alle tabelle esistenti. Ancoraggio PR.AT-01/02, GV.RR-04, art.24 D.Lgs.

Frontend:
- competenze.html (4 schede: Catalogo, Requisiti per ruolo, Competenze persone,
  Gap & azioni) + js/competenze.js. Bootstrap Italia V2. CTA "Assegna corso"
  (riuso /training/assign) e "Apri non conformità".
- Voce sidebar "Competenze" (common.js + common-bi.js + BI_PAGES) + nav.competences
  i18n IT/EN. api.js: metodi comp* + assignTraining.

Help/KB/PWA:
- help.js: guida contestuale 'competences' (schede, calcolo gap, PR.AT-01/02,
  GV.RR-04, art.24 D.Lgs., disclaimer no-parere-legale) + mappa pagina.
- sw.js: nome cache nis2-shell-v1.17.0 (allineamento PWA).
- Design doc corretto (rettifica capa_actions vs corrective_actions) + avanzamento 4.1/4.2.

Cache-buster: ?v=20260618 dei 5 JS condivisi su 32 HTML. version.json 1.16.0 -> 1.17.0.
Smoke E2E su prod (fpm reale): catalogo, requisito, competenza, gap=2, openAction
(NCR+CAPA), anti-dup 409, mappatura corso — tutti verdi; dati di test ripuliti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:01:32 +02:00
DevEnv nis2-agileandClaude Opus 4.8 47199f1e4e [DOCS] Contesto sessione 2026-06-15 (sera): A4 Fase 4.1 Organigramma implementata e live (org_roles, OrgRoleController, organigramma.html, help+KB), prossimo passo 4.2 skill/gap competenze
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:08:10 +02:00
DevEnv nis2-agileandClaude Opus 4.8 8540b53cb2 [FEAT] A4 Fase 4.1 — Organigramma (org_roles): ruoli/gerarchia + nodo governance Art.23
Primo modulo del modello relazionale A4 (docs/DESIGN_A4_RELATIONAL.md).

Backend:
- Migration 041 (docs/sql/041_org_roles.sql) + runner scripts/migrate-a4.php.
  Tabella org_roles additiva/idempotente: gerarchia self-FK (parent_role_id),
  titolare (holder_user_id), is_governance_body (organi amministrazione/direttivi
  Art.23 D.Lgs.138/2024), description (GV.RR-02). APPLICATA su prod (container
  nis2-db v8.0.45, TLSv1.3, 11 col, 4 FK).
- OrgRoleController: list (flat+tree arricchiti), get, create, update, delete,
  assignableUsers. Multi-tenancy ancorata a getCurrentOrgId(), anti-IDOR
  (id+organization_id), prevenzione cicli nella gerarchia, holder = membro org,
  delete bloccato se ha figli (409). Route registrate in public/index.php.

Frontend:
- public/organigramma.html + js/organigramma.js: vista ad albero (badge governance,
  titolare/vacante), editor crea/modifica/elimina con select padre anti-ciclo,
  "crea struttura di base". Bootstrap Italia V2.
- Voce sidebar "Organigramma" (common.js + common-bi.js) + nav.org_chart i18n IT/EN.
- api.js: metodi orgRole* (wrapper _acn).

Help/KB:
- help.js: guida contestuale 'org' (cosa rappresenta, nodo Art.23, come si usa,
  fonti certe D.Lgs.138/2024 art.23 + GV.RR-02 best practice, disclaimer no-parere-legale).

Cache-buster: bump ?v=20260617 dei 5 JS condivisi su tutte le 32 HTML referenti.
version.json 1.15.2 -> 1.16.0. Smoke E2E su prod (fpm reale): login, CRUD, tree,
anti-ciclo (422), delete-con-figli (409), cleanup tutti verdi.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:05:56 +02:00
DevEnv nis2-agileandClaude Opus 4.8 dcb9a14f0f [DOCS] Help online: sezione Classificazione NIS2 aggiornata ad A2 (autovalutazione preliminare, size-independent art.3 c.5, criteri c.9 b/c/d, D.Lgs. non Direttiva) + bump ?v help.js. KB AI: ingestato doc aggiornamenti A1/A2/A3 (scope SYSTEM)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:45:14 +02:00
DevEnv nis2-agileandClaude Opus 4.8 8c28d5ae97 [DOCS] Contesto sessione 2026-06-15: segnalazioni Fattori (A1/A2/A3 implementate, A4 disegnato), Unica UI (-28 -bi), tester provisioning
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:42:43 +02:00
DevEnv nis2-agileandClaude Opus 4.8 dbda3efde4 [DOCS] A4 design: modello relazionale (organigramma+RACI hub, skill/gap competenze/formazione, m2m procedure/inventario/rischi, scadenziario centralizzato) — ancorato GV.RR/PR.AT/ID.AM/GV.PO/GV.SC/art.23-24, 5 fasi
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:36:46 +02:00
DevEnv nis2-agileandClaude Opus 4.8 3c5184bbae [CLEANUP] Unica UI: rimossi 28 duplicati *-bi.html (orfani post-swap Bootstrap Italia, 0 riferimenti). Le pagine ufficiali (nomi senza -bi) SONO la UI V2 BI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:17:34 +02:00
DevEnv nis2-agileandClaude Opus 4.8 717f9ac396 [FEAT] A2 UI: wizard onboarding esteso (bilancio, autonomia/gruppo, categorie size-independent art.3 c.5, mission GV.OC, codice etico facoltativo, 3 criteri sotto-soglia c.9 b/c/d) + classificazione v2 lato server + disclaimer ACN 'autovalutazione preliminare'. Markup Bootstrap Italia/AGID
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:17:34 +02:00
DevEnv nis2-agileandClaude Opus 4.8 c5fce7bdf7 [FEAT] A2 backend: Nis2ClassificationService v2 (matrice+bilancio, size-independent art.3 c.5, criteri sotto-soglia c.9 b/c/d, obblighi D.Lgs. art.23/24/25, esito 'preliminare non vincolante'); logica centralizzata (no duplicazione) + complete() salva i nuovi campi
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:06:17 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b2f47c0a47 [FEAT] A2 migration 040: organizations += classificazione NIS2 (bilancio, autonomia, size-independent c.5, criteri c.9 b/c/d, mission, designation_basis, self_assessment preliminary) — applicata+verificata prod
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:56:56 +02:00
DevEnv nis2-agileandClaude Opus 4.8 37071e1d77 [DOCS] A2 onboarding: design + estratto verbatim art.3 D.Lgs.138/2024 (criteri sotto-soglia = c.9 lett.b/c/d CONFERMATO; individuazione = c.13)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:51:53 +02:00
DevEnv nis2-agileandClaude Opus 4.8 9fed095204 [FIX] A3: stringhe JS residue Asset -> bene/beni nelle pagine Inventario (titoli modale + notifiche)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 15:59:31 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d009341d5a [FIX] Completamento A1/A3 (segnalazioni Fattori): help.js citazioni obblighi -> D.Lgs.138/2024 art.23/24/25 + terminologia Asset -> Inventario/beni (pagine + guida)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 15:57:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 121b8c2003 [FIX] Allineamento normativo segnalazioni Fattori (A1+A3): citazioni obblighi -> D.Lgs.138/2024 art.23/24/25 (non Direttiva) + terminologia Asset -> Inventario
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 15:27:07 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d50955d95a [FEAT] Client CLI sicuro provisioning tester/admin (dry-run default, --expect guard, idempotente, SSO-sync-safe)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:10:26 +02:00
DevEnv nis2-agileandClaude Opus 4.8 eeeb014c49 [DOCS] Contesto: PWA v1.15.0 pushata+verificata + cron supervisore conferma autonoma (4 cicli rc=0)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 00:56:39 +02:00
DevEnv nis2-agileandClaude Opus 4.8 36513fa722 [FEAT] PWA installabile Android/iOS conforme AGID (v1.15.0)
- manifest.webmanifest + service worker (offline shell, /api network-only, asset stale-while-revalidate)
- set icone (192/512 + maskable + apple-touch + favicon), generate zero-deps (pure Node+zlib)
- tag PWA + viewport viewport-fit=cover (zoom mantenuto, WCAG 1.4.4) in 77 pagine HTML
- AGID/WCAG 2.1 AA: touch target >=44px, orientamento/zoom liberi, safe-area standalone
- generatori riproducibili: scripts/gen-pwa-icons.mjs + scripts/inject-pwa-head.mjs
- bump version 1.14.1 -> 1.15.0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:09:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 b80bf2361c [BACKUP] Sessione 2026-06-14: supervisore autonomo + contesto + mig.039 + UI V2 plan
- Supervisore autonomo ticket: prompt operativo + dry-run (scripts/), DRAFT rimosso; gate normativo gia' committato
- docs/CONTEXT_LAST_SESSION.md: sessione 2026-06-14 (supervisore LIVE, run#1/#2, rotazione Anthropic rinviata)
- docs/sql/039_integrity_keys.sql: migrazione integrita' DB (PK/UNIQUE/FK) gia' applicata in prod 12/6
- docs/MIGRATION_UI_V2.md: piano migrazione UI V2
- docs/nis2/incidente_r00/: 2 mockup incidente (gateway+dashboard)
- .gitignore: versiona public/vendor/ (asset Bootstrap Italia self-hosted)
- Fix accumulati: EmailService (kill-switch email), Incident/Onboarding/Organization/Services controllers, questionnaire, ReportService, CLAUDE.md standard

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:35:41 +02:00
DevEnv nis2-agileandClaude Opus 4.8 489a032658 [DOCS] Archivio adozione supervisore autonomo NIS2 (run#1 gate + run#2 release path + cron 17 */4 LIVE)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 14:08:19 +02:00
DevEnv nis2-agileandClaude Opus 4.8 dcbce3de5c [FIX] Help online: rimossa icona FontAwesome morta (fa-comment-alt) post-swap V2
- public/js/help.js: rimossa unica occorrenza <i class="fas fa-comment-alt"></i>
  (residuo non renderizzato dopo lo swap a Bootstrap Italia V2) + collassato lo
  spazio ridondante, nella guida "Come aprire una segnalazione".
- Cache-buster mirato: help.js?v=20260613 -> 20260614 su 35 pagine public/*.html
  (sibling common-bi.js/i18n.js/common.js/api.js/style.css lasciati invariati).
- public/version.json: 1.14.0 -> 1.14.1.
- Nessuna modifica a dati o schema DB. Ticket #367 (collaudo release supervisore).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 13:33:29 +02:00
DevEnv nis2-agile 0b32d4708f [DOCS] Review NIS2 del prompt operativo supervisore: approvata + 2 aggiunte (cache-buster JS/CSS, git backup) + risposte 4 domande 2026-06-14 10:32:55 +02:00
DevEnv nis2-agile 2db5aa1407 [DOCS] Bozza gate normativo supervisore autonomo ticket (nis2-supervisor-prompt.md)
Gate 'fonti certe' per autonomous-supervisor-agent (AgileHub id=32): nessuna valutazione chiusa senza
fonte normativa citata; difesa prompt-injection; punti delicati (Allegati 3/4, IS-4, 24h/72h/1mese);
confini tecnici (DB container TLS SELECT-only, release edit+USR2 no maintenance flag). Per binding AgileHub.
2026-06-14 08:57:36 +02:00
DevEnv nis2-agile ad3532a90a [FIX] Pannello ARIA: chiusura (X+Esc) robusta + niente sovrapposizione ai pallini (stile TRPG)
Aprendo il pannello ARIA ora si nascondono nx-bar (campana/supporto) + ai-chat-fab -> niente overlap;
la X e Esc chiudono e ripristinano i pallini. Prima la X era fragile e il pannello copriva i bottoni.
Bump common.js?v=20260613d. (TRPG usa ai-assistant.js con stesso pattern toggle/hide.)
2026-06-14 08:56:08 +02:00
DevEnv nis2-agile 4341d86c4c [UI] Allineato a TRPG/AgileHub: rimosso auto-inject feedback.js (doppione) -> solo bug-reporter
NIS2 aveva DUE sistemi feedback: feedback.js (pill cyan 'Segnala/Feedback' + modale scuro) + bug-reporter
AgileHub (campana+supporto). TRPG/ALLTAX usano solo bug-reporter (verificato: trpg ha app/js/bug-reporter.js,
nessun feedback.js). Rimosso l'auto-inject di feedback.js da common.js -> resta il trittico pallini
(notifiche + supporto + ARIA) come la suite. Verificato headless: feedback-fab assente, nx-bell/nx-bug/ai-chat
presenti. Bump common.js?v=20260613c (auto-refetch). feedback.js/api restano nel repo, non iniettati.
2026-06-14 08:33:13 +02:00
DevEnv nis2-agile 30cac8479a [FIX] Modali showModal() invisibili su V2 (solo sfondo grigio): conflitto .modal con Bootstrap Italia
BI introduce .modal{display:none;position:fixed} che nascondeva i nostri modali custom (help, genera-AI,
conferme, crea-rischio...) -> si vedeva solo il backdrop grigio. Aggiunto override scoped
'.modal-overlay .modal{display:block;position:relative;...}' in style.css (specificità > BI, caricato dopo).
Verificato headless: modale help display:block height:765 visible:true. Bump style.css?v=20260613b (auto-refetch).
2026-06-14 08:00:13 +02:00
DevEnv nis2-agile 959ede968d [FIX] Feedback widget: cablata X-API-Key NIS2 (nis2_ak_dev_...) in common.js + bump common.js?v=20260613b
Risolve il 401 del bug-reporter ('Header X-API-Key o Authorization Bearer richiesto'): il widget ora
manda X-API-Key -> gateway ticket AgileHub OK (tenant 7, provenance corretta). Chiave fornita da AgileHub
(full tenant-key, va nel JS pubblico = stesso compromesso TRPG/ALLTAX; debito 'token low-priv' rimandato).
Bump versione common.js -> auto-refetch senza hard-refresh.
2026-06-13 19:21:56 +02:00
DevEnv nis2-agile 3c484fe604 [FIX] Cache-buster ?v=20260613 sui JS/CSS modificati (help/common/common-bi/i18n/feedback/style/demo) + bump bug-reporter + demo assets
Risolve 'le modifiche non si vedono': /js/help.js era senza ?v -> browser serviva la versione cached
vecchia. 281 ref su 57 pagine + injection demo/bug-reporter aggiornate.
2026-06-13 19:17:59 +02:00
DevEnv nis2-agile b49044c57f [FIX] FAB segnalazione/ARIA: icone FontAwesome -> SVG inline (regressione swap BI) + slot data-api-key
Icone: bug-reporter.js (nx-bell fa-bell, nx-bug fa-life-ring) + common.js ai-chat-fab/header/send
(fa-wand-magic-sparkles, fa-paper-plane) erano FA, rimosso dallo swap BI -> cerchi vuoti. Sostituite con
SVG inline (currentColor, self-contained). Verificato headless: 3 FAB renderizzano svg.
Auth widget: cablato s.dataset.apiKey <- window.NEXUS_WIDGET_API_KEY (X-API-Key gateway AgileHub, era
assente -> 401). _detectUser ora legge anche nis2_access_token. SERVE la chiave X-API-Key da AgileHub.
2026-06-13 18:49:49 +02:00
DevEnv nis2-agile 703831c556 [DEMO] Checklist: step 9 = Assets/GV.OC-04 (era cross-analysis gate-ato) + azione re-tag RAG AgileHub 2026-06-13 13:03:01 +02:00
DevEnv nis2-agile 8c0074aa5d [DEMO] Polish step 9: cross-analysis (gate consultant) -> Assets/GV.OC-04 (visibile al demo-user)
cross-analysis e gate-ato consultant/super_admin: per il demo-user (compliance_manager) renderizza
'riservata ai Consulenti/INSUFFICIENT_ROLE' e NON costruisce #chat-input/#quick-qs/#tab-chat -> highlight
impossibili. Sostituito con Assets (relevance scoring GV.OC-04): feature distintiva NIS2, pienamente
visibile, gia nel dataset 996001. Highlight #asset-stats + #assets-container verificati in browser reale
(ok:true, errs:[]). L'AI resta dimostrata da ARIA-narratore + AI gap analysis (step 2).
RICHIEDE re-tag RAG lato AgileHub: step9 -> mappa 08_assets (era 15_cross-analysis).
2026-06-13 13:02:34 +02:00
DevEnv nis2-agile 3ef69dfd74 [DEMO] Run 10-step browser reale: fix manifest step 9 (cross-analysis -> #portfolio-header)
Harness same-origin sui 10 step ha trovato 1 difetto reale: step 9 puntava a #quick-qs/#chat-input che
il JS non renderizza (tab chat non-default, assenti nel DOM) -> ri-ancorato a #portfolio-header (presente,
highlightApplied:true confermato). Gli altri 'NOT_FOUND' erano artefatti --virtual-time-budget: incidents
#crit-*, policies #ai-*, supply-chain #assessment-live-score sono presenti nel DOM renderizzato (dump 7s),
il vero avatar li trova al loro at_ms. 10/10 step ora risolvibili.
2026-06-13 12:51:07 +02:00
DevEnv nis2-agile d96a5b5f49 [DEMO] Checklist: postMessage->highlight PROVATO in browser reale (ok:true, highlightApplied:true, errs:[])
Harness same-origin temp sul sottodominio + headless chrome: ready -> highlight #compliance-gauge ->
classe nis2-demo-hl applicata, 0 errori. Il SPA esegue i comandi del widget in un browser vero.
Resta solo il giro completo 10 step col widget AgileHub live (conferma di produzione).
2026-06-13 11:47:22 +02:00
DevEnv nis2-agile 7008b9ecc4 [DEMO] Checklist: aggiunta validazione browser headless NIS2 (auth+runtime OK, 2 bug fixati fcf8b09) 2026-06-13 11:31:14 +02:00
DevEnv nis2-agile fcf8b0963b [DEMO] Fix 2 bug SPA-side trovati al collaudo browser headless (auth in-memory + boot seed)
Il collaudo curl validava il backend ma non l'esecuzione JS nel browser. Con google-chrome --dump-dom:
1) common-bi.js: sync api.token/orgId IN-MEMORY (api.js crea l'istanza prima leggendo localStorage vuoto
   → checkAuth vedeva token null → redirect a login). Ora la SPA si auto-autentica.
2) demo-mode.js boot(): non bloccare sul seed quando HMAC_ENFORCE=false (cross-origin il seed non arriva;
   non serve, HMAC off) → il runtime (banner/listener/ready) ora parte.
Verificato headless su dashboard/risks/incidents: login:0, banner+nis2-demo-on presenti.
2026-06-13 11:30:55 +02:00
DevEnv nis2-agile f946b3a9ca [DEMO] Checklist smoke avatar-in-the-loop + telemetria demo_events (parent origin confermato dimostrazione)
Criteri oggettivi per-step (navigate+highlight attesi, ARIA fonti certe), pre-flight verde, e query
demo_events per riscontro lato NIS2 post-giro. Smoke browser-driven da AgileHub, nessuna finestra bloccante.
2026-06-13 11:00:45 +02:00
DevEnv nis2-agile bf14d9937c [DEMO] Collaudo E2E lato NIS2 PASS (0 difetti, 5 dimensioni) — pronti per giro avatar-in-the-loop
Via sottodominio: integrità proxy (md5 parity, 17 viste+7 asset 200), auto-auth (read 200/write 403,
tenant isolation, spoof org ignorato), manifest 10 step 21/21 selettori presenti, endpoint codici attesi,
asset demo gated. Auth reale intatta. Workflow collaudo wf_c473580d-ec3.
2026-06-13 10:36:09 +02:00
DevEnv nis2-agile 1f77f91ec6 [DEMO] Archivio: AgileHub Fase1 RAG(415)+formazione G1+sottodominio reverse-proxy; handoff #2 RISOLTO
Sottodominio nis2.dimostrazione.agile.software live (reverse-proxy). DEMO_BASE allineato (3e8a90f).
Punto coordinamento #2 (same-origin) marcato RISOLTO via sottodominio. Collaudo E2E in corso.
2026-06-13 10:33:59 +02:00
DevEnv nis2-agile 3e8a90f969 [DEMO] Sottodominio: DEMO_BASE -> nis2.dimostrazione.agile.software + origin allowlist postMessage
AgileHub ha consegnato il reverse-proxy nis2.dimostrazione.agile.software -> nis2.agile.software.
- DemoController.DEMO_BASE default = sottodominio (spa_iframe_url/manifest/credentials lì); env DEMO_BASE_URL override.
- demo-mode.js: origin-check ora ALLOWLIST (dimostrazione + nis2.dimostrazione) robusto alla topologia widget;
  outbound postMessage mirato all'origin reale del parent (ancestorOrigins/captured).
2026-06-13 10:31:46 +02:00
DevEnv nis2-agile 460d435a47 [DEMO] Auto-auth SPA demo: demo_jwt nel fragment spa_iframe_url + bootstrap token sincrono in common-bi.js
Chiude il gap auth: la SPA demo si auto-autentica col demo_jwt (token API nis2_access_token + org dal
payload) PRIMA di checkAuth, cross-origin (niente sessionStorage same-origin). spa_iframe_url ora atterra
su /dashboard.html?demo=..#djwt=... DemoController.DEMO_BASE override via env DEMO_BASE_URL (switch a
sottodominio senza codice). USR2 richiesto per DemoController.
2026-06-13 10:00:28 +02:00
DevEnv nis2-agile 4222e827f9 [DEMO] Archivio scambio AgileHub 2026-06-13: HMAC #1 CHIUSO (runtime-avatar = no HMAC per-msg)
- INCOMING_FROM_AGILEHUB: contratto HMAC external/v1 (canale ticket) + chiarimento canale runtime-avatar
  (X-Internal-Key, no HMAC per-messaggio) + persona ARIA pronta (RAG 402) + piano Fase 1.
- OUTGOING aggiornato: punto coordinamento #1 marcato CHIUSO (demo-mode.js HMAC_ENFORCE=false, a851caf).
2026-06-13 09:41:22 +02:00
DevEnv nis2-agile a851caf669 [DEMO] HMAC per-messaggio OFF: canale runtime-avatar e origin-verified (chiarimento AgileHub 2026-06-13)
Il postMessage widget->SPA e il canale runtime avatar (non external/v1): server-to-server usa
X-Internal-Key (gia su reset-dataset), il postMessage si fida di ev.origin (same-origin). HMAC verify
resta flag-gated (HMAC_ENFORCE=false) da riaccendere solo se al collaudo il widget firma.
2026-06-13 09:39:05 +02:00
DevEnv nis2-agile 48ad479c01 [DEMO] Handoff ad AgileHub: product-side pronto (formazione+demo) — riepilogo + punti coordinamento
Stato punti 1,2,3,4,6,7,8 = FATTI/deployati/collaudati. Da coordinare: HMAC canonical messaggio,
ops same-origin dimostrazione.agile.software, data-demo-action anchors, sweep 132 id (non bloccante).
2026-06-13 09:01:53 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d6d61885b4 [DEMO] Manifest tour (punto 4): nis2-tour-2026.json — 10 step ancorati a id reali verificati
Steps: dashboard/assessment/acn-gap/risks/incidents/policies/supply-chain/isms/cross-analysis/reports.
Highlight su selettori ESISTENTI verificati dalle mappe (non i 132 da-aggiungere). Narrazione senza
numeri inventati (ARIA usa RAG sul dataset 996001). express_subset [1,4,5,9]. Servito da GET /api/demo/manifest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 09:00:04 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d16fdd2c02 [DEMO] Frontend demo-mode (punto 4): demo-mode.js + demo-selectors.js + demo.css + hook ?demo=
Avatar di prodotto, product-demo-protocol v1.0.1 (NIS2 multi-page):
- public/js/demo-mode.js: runtime canale postMessage (inbound navigate/highlight/scroll_to/
  trigger_action/open_tab/...; outbound ready/spa_state_changed/step_animation_done/...),
  verifica origin+HMAC (chiave SHA-256(jwt:seed:demo-control-channel))+idempotency, banner read-only.
- public/js/demo-selectors.js: whitelist 17 viste (view->url) + azioni read-only sicure.
- public/css/demo.css: highlight pulse-blue/halo + banner.
- common-bi.js: hook che inietta gli asset SOLO con ?demo= (no-op per utenti normali).

NOTE: (a) HMAC canonical del MESSAGGIO da riconciliare con AgileHub al collaudo (canonicalize()).
(b) manca il manifest (steps+selettori) — dipende dagli id stabili in corso.
node --check OK su tutti.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 08:56:36 +02:00
DevEnv nis2-agileandClaude Opus 4.8 be29895446 [FORMAZIONE] Banca esercizi/quiz (punto 8): 32 domande + 9 esercizi sandbox, 10 moduli
Ancorata a tassonomia + mappe-logiche, risposte fondate su FONTI CERTE (Allegati 3/4, IS-4 solo
essenziali, ciclo 24h/72h/1mese, SoA 111 controlli, soglia rilevanza asset 40, ecc.). Formato per
motore quiz (esame soglia 80%). Esercizi pratici eseguibili nella sandbox org 996002.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 08:51:17 +02:00
DevEnv nis2-agileandClaude Opus 4.8 6900d942a4 [DEMO] resetDataset cablato a DemoSeedService (reset reale dataset demo/sandbox)
- application/services/DemoSeedService.php: logica seed/clone golden #151 -> 996001/996002 (riusabile)
- DemoController::resetDataset: re-seed reale via service (?scope=demo|sandbox|default both), non piu stub
- scripts/seed-demo-dataset.php: thin CLI wrapper sul service
Testato prod: POST /api/demo/reset-dataset (X-Internal-Key) scope=sandbox -> 200, 996002 ri-seedata.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 08:47:09 +02:00
DevEnv nis2-agile e0204dbaa7 [DEMO] fix seeder: utente demo via Database::insert (must_change_password default) + must_change_password=0
Eseguito su prod: org 996001 (DataCore Demo) + 996002 (sandbox) seedate. Guard deployato (USR2) e
testato end-to-end: read demo 200 (score 75), write demo 403 DEMO_READ_ONLY, auth reale intatta.
2026-06-13 08:39:32 +02:00
DevEnv nis2-agileandClaude Opus 4.8 e04eba62c1 [DEMO] Avatar prodotto: read-only guard (BaseController) + seeder dataset demo 996001/996002
Increment backend formazione-first (product-demo-protocol v1.0.1):
- BaseController::applyDemoGuard() — SAFE-BY-CONSTRUCTION (flusso auth normale invariato):
  riconosce demo_jwt scope=demo:read-only (blocca scritture 403 DEMO_READ_ONLY) e
  training:sandbox (scritture solo su org sandbox); contesto sintetico user=0 ruolo
  compliance_manager su org range 996xxx; mai super_admin. Short-circuit in requireAuth/
  requireOrgAccess/requireOrgRole. php -l OK. DA DEPLOYARE (USR2) + testare quando host disponibile.
- scripts/seed-demo-dataset.php — clona golden DataCore #151 in 996001 (demo RO) + 996002
  (sandbox scrivibile), idempotente, richiamabile da resetDataset. php -l OK. DA ESEGUIRE su host.

NB: chiave ssh host revocata a meta-sessione → seed/USR2/push in attesa di ri-provisioning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 08:19:53 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4dfab2a391 [FORMAZIONE] Avatar di prodotto — mappa-logica COMPLETA (22 viste) + SELETTORI_DEMO + tassonomia corso + fix kb.js
Deliverable formazione-first (DESIGN_AVATAR_FORMAZIONE / product-demo-complete):
- docs/mappa-logica/01..22_*.md: tutte le viste app V2 mappate (formato canonico standard §2),
  selettori VERIFICATI sul DOM reale, endpoint verificati su api.js/script, gate ruolo dai controller
- docs/mappa-logica/SELETTORI_DEMO.md: registry canonico (22 viste, 131 visualizzazione + 89 azioni)
  + worklist consolidata 132 id stabili da aggiungere (modifiche chirurgiche future)
- docs/formazione/TASSONOMIA_CORSO_NIS2.{md,json}: 10 moduli / 28 lezioni / ~7h45, prerequisiti+durate,
  esame con soglia 80%, sandbox 'prova tu' org 996002
- fix kb.js: ingest scope=ORG ora invia organization_id (prima 422 KB_ORG_REQUIRED) — bug trovato da A2

Prodotto con 4 trittici di agenti (A1 esecutore -> A2 revisore -> A3 controllore avversariale):
A2: ~14 correzioni (cross-ref, gate ruolo, comportamenti); A3 PASS: 38 selettori campionati 0 rotti,
18 endpoint reali, 0 violazioni normative (Allegati 3/4, IS-4, '1 mese', PII.* interna), 9 claim verificati.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:50:19 +02:00
DevEnv nis2-agileandClaude Opus 4.8 e1ab0b3ea0 [DEMO] Avatar di prodotto Fase 1 — fondazione backend (5 endpoint /api/demo/*)
product-demo-protocol v1.0.1, Modalita A. Additivo (zero impatto endpoint esistenti).

- DemoController: session-start, manifest, event, credentials, reset-dataset
  * demo_jwt scope=demo:read-only, org_id 996001 (range demo 996000-996999), TTL 30m
  * hmac_key_seed base64 44ch, rate-limit 5/min+50/h per IP, eventi idempotenti
- Migration 040: tabelle demo_sessions + demo_events
- Rotte 'demo' in index.php (chiavi camelCase: router camelCasa il segmento URL)
- docs/DEMO_AVATAR_NIS2.md: piano Fase1/Fase2, decisioni, punti integrazione AgileHub, gap protocollo

Smoke prod OK: session-start 201, manifest 200, credentials 200, event 204, reset 401(no key).
TODO Fase 1: read-only guard in requireAuth, dataset demo 996001, frontend demo-mode, manifest+mappe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:15:37 +02:00
DevEnv nis2-agileandClaude Opus 4.8 2b197440f6 [KB] Script ingest Guida UI V2 nella KB AI (eseguito: 8 chunk, scope SYSTEM)
ARIA ora conosce la nuova interfaccia V2 (docs/kb/V2_UI_NAVIGAZIONE.md indicizzato in
Qdrant nis2_kb scope SYSTEM). Header documenta il workaround mount (copia sotto application/).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 15:14:39 +02:00
DevEnv nis2-agileandClaude Opus 4.8 1c64211685 [UI] SWAP V2: Bootstrap Italia ora UFFICIALE su 27 pagine app (kill della vecchia UI)
Promosse le pagine -bi a nomi ufficiali (contenuto BI), link -bi->plain, titoli '(BI)' rimossi.
La vecchia UI delle pagine app e sostituita. Backup: git (commit 36967f9) + /tmp/nis2-v1-backup.

Promosse (27): auth (login/register/onboarding/forgot/reset) + app (dashboard/assessment/
risks/incidents/policies/supply-chain/training/assets/reports/settings/isms/acn-gap/normative/
whistleblowing/kb/cross-analysis/service-continuity/guida/companies) + admin (index/orgs/users).
common-bi.js: sidebar BI ora linka le pagine ufficiali.

ESCLUSA: index.html (landing marketing 1298 righe) -> conversione dedicata a parte (la -bi
era 557 righe, rischio perdita contenuti). Pagine marketing/demo (presentation/simulate/
workflow/architecture/index-en) restano vecchio stile (fuori scope app autenticata).

Verifica produzione: 27/27 servite 200 con BI, 0 link -bi residui, 0 '(BI)' nei titoli,
auth intatta (401 su pwd errata), common-bi.js node --check OK.

Rollback: git revert HEAD  (oppure ripristino da /tmp/nis2-v1-backup).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:49:15 +02:00
DevEnv nis2-agileandClaude Opus 4.8 36967f9452 [UI] V2 completezza Bootstrap Italia: 11 pagine app -bi mancanti + companies + help.js + KB doc
Completa la copertura V2 al 100% delle pagine app autenticate (pre-swap).
Additivo: nessun file live HTML promosso ancora (lo swap e separato).

- 9 pagine app -bi: isms, acn-gap, normative, whistleblowing, kb, cross-analysis,
  service-continuity (doc standalone), guida, companies
- admin/{index,organizations,users}-bi.html (path corretti, common-bi.js)
- help.js: +89 righe help contestuale pagine V2 (vincolo fonti certe)
- docs/kb/V2_UI_NAVIGAZIONE.md: doc UI/navigazione V2 per la KB AI (ARIA)
- Verifica: tutte servite 200, 0 CDN/FontAwesome, sprite validi, parita ID

Prodotto via Workflow a trittici (run wf_c4962fbe-93f) + fix manuale companies/sprite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:46:37 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4d676f2e21 [UI] Bootstrap Italia rollout (Opzione B) — vendor self-hostato + 16 pagine -bi + sidebar BI
Pipeline a trittici (Esecutore->Revisore->Controllore avversariale), 6 trittici T1-T6.
Tutto ADDITIVO: nessuna pagina/JS live modificata, produzione intatta. Swap non incluso.

- public/vendor/bootstrap-italia/ : BI v2.18.1 self-hostato (CSS+JS bundle+16 woff2+sprite), MAI CDN
- 16 pagine *-bi.html (login/register/onboarding/forgot/reset/index + dashboard/assessment/
  risks/incidents/policies/supply-chain/training/assets/reports/settings)
- public/js/common-bi.js : sidebar/topbar in markup BI (additivo, riusa stessi ID + common.js)
- Verifica sul path servito reale: 16/16 -> 200, 0 CDN, 0 FontAwesome residui, parita ID/hook completa, WCAG AA
- docs/SISTEMA_TRITTICI_AGENTI.md : design del sistema a trittici

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:31:53 +02:00
DevEnv nis2-agileandClaude Opus 4.8 06c94b3c05 [REVISIONE] Progetto revisione conformità+UI + 2 agenti esperti + correzioni R3 (audit NIS2/ISO)
Progetto di revisione unificato (docs/PROGETTO_REVISIONE_NIS2.md) che fonde:
- docs/PRINCIPI_AI_E_CONFORMITA_NORMATIVA.md (cyber + AI Act/GDPR/L.132 + principi AI)
- docs/VERIFICA_GAP_UI_AGID_BOOTSTRAP_ITALIA.md (gap UI vs AGID/Bootstrap Italia)
Agenti esperti: .claude/agents/nis2-expert.md + iso-27001-expert.md (read-only/advisory).

Audit R1 (NIS2) + R2 (ISO SGSI) = conformi, riserve minori. Correzioni R3 applicate
(lint + login reale OK, app su container TLS):
- IncidentController: final_report_due +30gg fissi -> +1 mese calendario, ancorato alla
  notifica reale in sendNotification (Art.23.4 lett.d).
- AIService prompt "report 30d" -> "1 mese dalla notifica 72h".
- help.js: GV.SC Allegato 2 -> Allegati 1 e 2 (Art.21.2(d)).
- nis2_sources.php: esplicitato Allegato 3=importanti / 4=essenziali.
- IsmsModelController export: nota PII.* = codifica interna (non numeri ufficiali 27018).
A4 (label whistleblowing "Art.32") DEFERITO: cita Art.32 D.Lgs.138/2024, da verificare sul testo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 09:54:14 +02:00
DevEnv nis2-agileandClaude Opus 4.8 ae7bb072d7 [DOCS] Manuale d'uso COMPLETO v1.14.0: accesso/registrazione/onboarding passo-passo + 18 moduli dall'help reale
Riscrittura completa: istruzioni concrete (login, registrazione 3 step, onboarding
4 step con campi reali, navigazione), per ogni modulo "cosa trovi e come si usa"
estratto dall'help contestuale del prodotto (help.js), FAQ, glossario. Rimosse le
interruzioni di pagina che lasciavano pagine vuote. Generatore self-contained
(estrae i contenuti da public/js/help.js via node).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 18:19:54 +02:00
DevEnv nis2-agileandClaude Opus 4.8 0a883b7cae [DOCS] Manuale d'uso prodotto NIS2 Agile (Word/.docx) v1.14.0 + generatore
Manuale completo in docs/MANUALE_UTENTE_NIS2_AGILE.docx (22 capitoli: intro,
onboarding, dashboard, Gap Analysis NIS2/ACN, Modello SGSI ISO 27001/17/18,
rischi, incidenti, policy, supply chain, formazione, asset, audit&report,
whistleblowing, normative, KB/AI, feedback, impostazioni, admin, API,
glossario, riferimenti). Generato con scripts/genera-manuale-utente.py
(OOXML puro, nessuna dipendenza esterna).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 17:43:46 +02:00
DevEnv nis2-agileandClaude Opus 4.8 9964b2b73c [FIX] migrate-isms runner: strip commenti --in coda riga (un commento conteneva ';')
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 17:08:17 +02:00
DevEnv nis2-agileandClaude Opus 4.8 99bca920cb [DOCS] NIS2 piano TLS-DB socket->container: explainer grafico (HTML)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 16:33:46 +02:00
DevEnv nis2-agile 3f74165531 [FEAT] Modello Organizzativo SGSI (ISO 27001/27017/27018) + SoA pre-popolato da NIS2
Nuovo modulo guidato in 6 step (cl. 4-10 + Statement of Applicability):
- migration 037 (isms_models/roles/soa/documents) + 038 (dataset 111 controlli:
  93 Annex A:2022 + 7 CLD/27017 + 11 PII/27018) + runner scripts/migrate-isms.php
- IsmsModelController (16 endpoint) registrato in index.php
- SoA pre-popolato dalle risposte Gap Analysis NIS2 (mapping iso27001_control)
- estensioni cloud condizionali 27017/27018 via flag uses_public_cloud/
  is_cloud_provider/processes_pii_in_cloud
- AIService::generateIsmsDocument + fonti ISO in nis2_sources.php
- frontend isms.html/isms.js + api client + sidebar + help + i18n IT/EN
- ingest KB ISO (scope SYSTEM, solo titoli/sintesi: no testo coperto da copyright)
- version.json 1.14.0; doc studio + deploy handoff

Strumento di supporto/pre-audit (non certificazione). Migration DA APPLICARE su host.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:32:42 +02:00
DevEnv nis2-agileandClaude Opus 4.8 66d50cd2cf [DOCS] TLS-DB nis2: CUTOVER FATTO 2026-06-11 — app su container nis2-db (TCP+TLSv1.3, REQUIRE SSL)
Anomalia socket chiusa. App ora client TCP+TLS verso il proprio container nis2-db
(DB_HOST=172.21.0.4), dati migrati (19 utenti), Ssl_version=TLSv1.3, REQUIRE SSL enforced
(plain rifiutato 1045), verificato dal worker fpm reale (login Fattori 200, ARIA RAG ok).
Runbook + context aggiornati. Guard anti-outage gia' in 4548cc6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 10:10:22 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4548cc6ff2 [FIX] TLS-DB nis2: guard anti-outage — salta SSL su connessione via UNIX socket
Lezione 2026-06-10 (3 outage): forzare SSL su una connessione via socket (DB_HOST
'localhost'/vuoto/path) da' PDOException [2002] "Cannot connect to MySQL using SSL"
su ogni richiesta -> app giu'. sslOptions() ora ritorna [] (resta in chiaro + log) se
DB_HOST indica un socket, anche col flag/env attivo. SSL si applica solo alle
connessioni TCP. Il socket e' IPC locale: nessun transito di rete da cifrare.
Cosi' riaccendere .db_ssl_on mentre l'app e' su socket NON puo' piu' causare outage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 06:45:19 +02:00
DevEnv nis2-agileandClaude Opus 4.8 dc05e316a0 [DOCS] TLS-DB nis2: decisione C (defer) — escluso da enforce, socket clear; path pulito futuro (bind 172.21 + validare da fpm)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 23:58:51 +02:00
DevEnv nis2-agileandClaude Opus 4.8 d98d52ed58 [DOCS] TLS-DB nis2: verità topologia (fpm→host SOCKET) + tentativo socket→TCP fallito su path fpm
CONTEXT/runbook aggiornati con le conclusioni 2026-06-10 sera:
- L'app HTTP (php-fpm) usa il MySQL HOST via UNIX SOCKET (localhost), NON il container
  (la "scoperta container" precedente era basata sul path CLI = falso positivo).
- TLS su socket non si applica e rompe (outage rientrato: rm flag + USR2).
- Switch socket→TCP via vault-net (172.30.0.1): CLI(root) OK ma fpm(www-data) esce con
  source=gateway → masquerade → 135.x → 1045. Switch NON eseguito (sarebbe outage).
- Lezione: validare SEMPRE sul path fpm reale (diag servito da php-fpm), mai CLI/docker exec.
- Pre-equip codice resta committato e inerte (flag OFF); attivabile quando il path TCP
  fpm sarà grantabile (bind 172.21 in finestra con restart MySQL).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 23:46:55 +02:00
DevEnv nis2-agileandClaude Opus 4.8 4d89b1e04b [FEAT] TLS-DB pre-equip nis2: PDO SSL gated default-OFF + CA (VIGILE 2026-06-10)
database.php: Database::sslOptions() — TLS verso MySQL gated (env DB_SSL=true o
flag-file application/config/.db_ssl_on), DEFAULT OFF. PDO::MYSQL_ATTR_SSL_CA +
VERIFY_SERVER_CERT=false; fail-safe se CA assente (resta in chiaro + log: un
VERIFY=false senza CA connetterebbe in chiaro silenziosamente). Merge con '+'
(preserva chiavi-intere PDO). Default-OFF provato inerte (base + [] === base).

Validato da 2 agenti: runtime app coperto 100% da Database::getInstance (0 raw PDO
in application/); review adversariale = SICURO default-OFF, nessun bug.

.gitignore: flag-file + db-ca.pem. Runbook: docs/TLS_DB_PREEQUIP_NIS2.md.
NB topologia verificata: l'app usa il DB CONTAINER (db->172.21.0.4, 8.0.45), non il
MySQL host -> CA + enforce vanno sul container db (a cura VIGILE).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:36:52 +02:00
456 changed files with 59882 additions and 10686 deletions
+27
View File
@@ -0,0 +1,27 @@
---
name: iso-27001-expert
description: Esperto degli standard ISO/IEC 27001:2022 (SGSI), 27002:2022 (controlli), 27017:2015 (cloud) e 27018:2019 (PII nel cloud). Usalo per validare il modulo Modello Organizzativo SGSI, la SoA, i 111 controlli, i mapping NIS2↔ISO, e i contenuti AI/help che citano gli standard. Distingue best practice (ISO) da obbligo normativo.
tools: Read, Grep, Glob, Bash, WebFetch
---
Sei un **esperto senior di sistemi di gestione della sicurezza delle informazioni (ISMS)** secondo la famiglia ISO/IEC 27000. Verifichi e validi il modulo "Modello Organizzativo SGSI" di NIS2 Agile (LIVE prod, v1.14.0; 111 controlli; SoA pre-popolata da NIS2; migrazioni 037/038), i mapping e i contenuti che citano gli standard.
## Principio non negoziabile
- **ISO = best practice internazionale, NON obbligo normativo.** Quando rivedi contenuti, assicurati che il prodotto NON presenti ISO come "obbligo di legge" (a differenza di NIS2/D.Lgs.138). Va dichiarato chiaramente.
- **Fonti certe**: gli standard ISO sono a pagamento e NON ingeribili integralmente; cita per numero/clausola/controllo, e marca `[da verificare sul testo ufficiale ISO]` i dettagli che non puoi confermare. Vietato inventare numeri di controllo o clausole.
- Registro del prodotto: `application/config/nis2_sources.php` (chiavi `iso_27001_2022`, `iso_27002_2022`, `iso_27017_2015`, `iso_27018_2019`).
## Cosa padroneggi
- **ISO/IEC 27001:2022** — requisiti dell'ISMS. Clausole **4–10** (contesto, leadership, pianificazione, supporto, attività operative, valutazione prestazioni, miglioramento). **Annex A**: **93 controlli** su **4 temi** (Organizzativi A.5, Persone A.6, Fisici A.7, Tecnologici A.8). Concetti chiave: **SoA** (Statement of Applicability), risk assessment/treatment, obiettivi, audit interni, riesame di direzione, miglioramento continuo.
- **ISO/IEC 27002:2022** — guida implementativa dei 93 controlli dell'Annex A; attributi (tipo controllo, proprietà sicurezza, concetti cybersecurity, capacità operative).
- **ISO/IEC 27017:2015** — controlli di sicurezza per **servizi cloud**, basato su 27002; aggiunge guida cloud-specifica + **7 controlli CLD.*** (CLD.6.3.1, CLD.8.1.5, CLD.9.5.1, CLD.9.5.2, CLD.12.1.5, CLD.12.4.5, CLD.13.1.4) `[verificare la numerazione esatta sul testo]`. Ruoli **cloud customer vs cloud provider**.
- **ISO/IEC 27018:2019** — protezione delle **PII (dati personali) nel cloud pubblico** come processor; allineato a principi privacy (consenso, finalità, trasparenza, diritti dell'interessato) — collega bene a GDPR.
## Come lavori
1. Verifica i **111 controlli** del modulo: che la codifica segua 27001:2022 Annex A (A.5/6/7/8) + i CLD.* di 27017 + gli extra 27018; che non ci siano controlli con codici inventati o della vecchia ISO 27001:2013 (114 controlli, numerazione diversa).
2. Controlla i **mapping** NIS2↔ISO e ISO↔SoA: che siano sensati e non inventati (es. Art.21.2 → A.5/A.8 pertinenti).
3. Verifica che AI/help **dichiarino "best practice, non obbligo"** quando citano ISO.
4. Per ogni rilievo: **elemento → verdetto → riferimento (clausola/controllo) → correzione**.
5. Segnala confusioni 27001:2013 vs 2022 (93 vs 114 controlli, 4 temi vs 14 domini).
Output conciso e strutturato, file:riga sul codice (`application/...`, SoA, migrazioni 037/038). Niente fronzoli: merito + riferimento allo standard.
+36
View File
@@ -0,0 +1,36 @@
---
name: nis2-expert
description: Esperto normativo NIS2 / cybersicurezza italiana. Usalo per validare affermazioni, classificazioni, scadenze, mapping e contenuti del prodotto rispetto a Direttiva (UE) 2022/2555, D.Lgs. 138/2024, Determine ACN 164179/2025 e 333017/2025, Allegati e tassonomia incidenti. Revisione documenti/AI/help, gap analysis di conformità, controllo "fonti certe". NON è un parere legale formale (quello è di Direzione/Legale).
tools: Read, Grep, Glob, Bash, WebFetch
---
Sei un **esperto senior di conformità NIS2** per il prodotto NIS2 Agile. Il tuo compito è **verificare e validare**, non implementare: rivedi codice, documenti, prompt AI, contenuti help e classificazioni rispetto al quadro normativo, e segnali errori/gap con la **fonte precisa**.
## Principio non negoziabile: fonti certe
- Ogni affermazione normativa DEVE citare la fonte (articolo/allegato/determina). **Vietato inventare** numeri di articolo, allegati, date.
- Se non sei certo, dillo e marca `[da verificare sul testo ufficiale]`. Preferisci sempre il riferimento **italiano operativo** (D.Lgs. 138/2024 + Determine ACN) per gli obblighi e la **Direttiva UE** per i principi.
- Il registro autoritativo del prodotto è `application/config/nis2_sources.php` (+ KB Qdrant `nis2_kb`). Verifica contro quello; i PDF originali sono in `docs/nis2/`.
## Quadro che padroneggi (e che devi far rispettare)
- **Direttiva (UE) 2022/2555 (NIS2)**: Art. 20 (governance/organi di gestione), **Art. 21** (misure di gestione del rischio, 10 categorie 21.2 a–j; 21.3 proporzionalità), **Art. 23** (notifica incidenti significativi).
- **Direttiva (UE) 2022/2557 (CER)**: resilienza soggetti critici (companion).
- **D.Lgs. 4 settembre 2024, n. 138**: recepimento IT; obblighi artt. 23, 24, 25, 29, 32.
- **Determinazione ACN n. 164179 del 14/04/2025**: misure di base + classificazione/notifica incidenti significativi (**IS-1, IS-2, IS-3, IS-4**).
- **Determinazione ACN n. 333017/2025**: piattaforma ACN, designazione rappresentanti NIS.
- **Ambiti** (Allegati I/II): settori essenziali/importanti.
## Mappe critiche — controlla SEMPRE che non siano invertite (errore ricorrente nel progetto)
- **Tassonomia incidenti (Allegati 3/4 della Determina 164179)**: **Allegato 3 = soggetti IMPORTANTI**, **Allegato 4 = soggetti ESSENZIALI**. (Memoria `reference_acn_allegati`: inversione = errore ricorrente, già corretto in AIService/help/IncidentController il 31/05; **verifica che NON sia rimasto invertito in `nis2_sources.php` o altrove**.)
- **Misure di base (Allegati 1/2 della Determina 164179)**: **Allegato 1 = IMPORTANTI** (37 misure / 87 requisiti), **Allegato 2 = ESSENZIALI** (43 misure / 116 requisiti).
- **Tempistiche notifica (Art. 23.4)**: preallarme entro **24h**, notifica completa entro **72h**, relazione finale entro **1 mese** dalla notifica completa (non "rilevazione + 30 giorni").
- **Regime obblighi**: i soggetti **importanti** NON sono tenuti all'IS-4 (incidenti ricorrenti); gli **essenziali** sì.
- **GV.OC-04 (NIST CSF 2.0)**: elenco sistemi rilevanti; nel prodotto = scoring asset 0-100, soglia ≥40 rilevante.
## Come lavori
1. Leggi le fonti del progetto (`nis2_sources.php`, `docs/nis2/`, codice rilevante) prima di pronunciarti.
2. Per ogni rilievo, produci: **affermazione esaminata → verdetto (corretto/errato/da verificare) → fonte → correzione suggerita**.
3. Segnala riferimenti inventati, mappe invertite, scadenze sbagliate, citazioni mancanti.
4. Distingui **obbligo vincolante** da **best practice** (es. ISO è best practice, non obbligo).
5. Confine: dai input tecnico-normativo; la **classificazione formale dell'entità e i pareri legali** spettano a Direzione/Legale — dichiaralo quando serve.
Output conciso, strutturato, con i file:riga quando rivedi codice. Niente disclaimer inutili; vai al merito con la fonte.
+16
View File
@@ -17,6 +17,12 @@ logs/
vendor/ vendor/
node_modules/ node_modules/
# Self-hosted frontend assets (Bootstrap Italia: CSS/JS/sprites/font) — devono
# essere versionati su Gitea (backup) per non vanificare il self-host a livello VCS.
# Negazione additiva alla regola 'vendor/' sopra (che altrimenti matcha public/vendor/).
!public/vendor/
!public/vendor/**
# IDE # IDE
.idea/ .idea/
.vscode/ .vscode/
@@ -39,5 +45,15 @@ docker/data/
*.bak* *.bak*
.backups/ .backups/
# Allegati ticket scaricati dal supervisore (transitori, input non fidato)
.ticket-attachments/
# Chiavi SSH effimere — NON committare MAI chiavi private # Chiavi SSH effimere — NON committare MAI chiavi private
.ssh-temp/ .ssh-temp/
# TLS-DB (VIGILE 2026-06-10): flag-file attivazione + CA locale (env-specifici, non committare)
application/config/.db_ssl_on
application/config/db-ca.pem
# Flag manutenzione (transitorio, runtime): presenza = sito in manutenzione (gate in .htaccess)
.maintenance
+68
View File
@@ -1,3 +1,30 @@
<!-- VIGILE:tls-db-2026-06-10 -->
## VIGILE 2026-06-10 — TLS DB + provenance (leggere a inizio sessione)
**Suite-wide (AgileHub/VIGILE)**: migrazione TLS-in-transito dei DB **per-utente** (`ALTER USER ... REQUIRE SSL`, MAI global `require_secure_transport`). **Finding PHP**: PDO/mysqlnd **NON cifra senza CA file** → servono `PDO::MYSQL_ATTR_SSL_CA => '<ca.pem>'` + `PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false` (il CA = `/var/lib/mysql/ca.pem` del proprio MySQL, distribuito al container). Node invece basta `ssl:{rejectUnauthorized:false}`. Doc autoritativo: `agile-services/docs/ANALISI_TLS_DB_STRUTTURALE_E_CENSIMENTO_2026_06_10.md`.
**QUESTO PRODOTTO — stato/azione**: ✅ **DONE (2026-06-16)**. Pre-equip PHP-CA fatto (PDO `MYSQL_ATTR_SSL_CA=application/config/db-ca.pem` + `VERIFY_SERVER_CERT=false`; app connessa in **TLS 1.3** `cipher=TLS_AES_256_GCM_SHA384`). Enforce `ALTER USER 'nis2_user'@'%' REQUIRE SSL` **già attivo** (applicato da VIGILE l'11/6 nel cutover container; `ssl_type=ANY`, confermato via `SHOW CREATE USER`). ⚠️ Gotcha: in **MySQL 8.0 `SHOW GRANTS` NON mostra la clausola `REQUIRE`** (solo `SHOW CREATE USER` la espone) → non usare SHOW GRANTS per verificare REQUIRE SSL. Conferma VIGILE: `docs/INCOMING_FROM_AGILEHUB_2026_06_16_tls_enforce_gia_attivo.md`.
**Inoltre (tutti i prodotti)**: ogni segnalazione/feedback deve portare la **release del prodotto** (`source_product_version`, standard version-provenance).
<!-- /VIGILE:tls-db-2026-06-10 -->
<!-- VIGILE 2026-06-09 feedback-version-provenance -->
## 🔴 PRIORITÀ — ogni segnalazione deve catturare versione+superficie (std `feedback-version-provenance` v1.0)
**Regola (hub_standards id=30):** ogni segnalazione/feedback DEVE portare e salvare **`surface`** (`web-v1`|`web-v2`|`android`|`ios`) + **`product_version`** (la **release REALE del prodotto**, NON la versione del widget) + **`build`**. Motivo: gap reale (ALLTAX #80, Romeo ha dovuto scrivere "2.9.2" a mano). **È additivo e non rompe nulla** (chi non lo manda funziona come prima), ma va adottato presto così ogni ticket dice subito "che release avevi".
**Lato AgileHub è GIÀ pronto:** il widget servito legge `data-app-version`/`data-app-build`/`data-surface`; il gateway external/v1 e ticket-ms salvano `source_product_version`/`source_surface`/`source_widget_*`.
**Da fare lato prodotto:**
1. **Widget tag**: aggiungere `data-app-version="<release vera>"` + `data-surface="web-v1|web-v2"` (+ `data-app-build`). Es: `<script src=".../widgets/bug-reporter.js" data-product="X" data-app-version="2.9.8" data-surface="web-v2" ...>`.
2. **App mobile**: già manda `app_version` → assicurarsi che il backend la **salvi** (molti la scartano).
3. **Tabella feedback propria** (se esiste, es. `feedback_reports`): aggiungere `client_surface`/`client_version`/`client_build` + il backend li salva.
4. **Web V1**: mandare la release vera del prodotto (es. version.json / BUILD), non quella del widget.
**Verifica**: una segnalazione di test da ogni superficie → il ticket/feedback ha `product_version` valorizzato (non null, non = widget version). Owner: VIGILE. Doc: `external-api-tenancy-model`-adiacente, standard slug `feedback-version-provenance`.
---
<!-- STANDARD:timezone-conventions:v1.0:start --> <!-- STANDARD:timezone-conventions:v1.0:start -->
## ⏰ ORARI E TIMEZONE — REGOLE OPERATIVE ## ⏰ ORARI E TIMEZONE — REGOLE OPERATIVE
@@ -253,6 +280,23 @@ NIS2 Agile e' una piattaforma SaaS multi-tenant per supportare le aziende nella
Target: PMI, Enterprise, Consulenti/CISO. Target: PMI, Enterprise, Consulenti/CISO.
## PWA conforme AGID — v1.15.0 (2026-06-14)
NIS2 e' una **PWA installabile** (Android/Chrome, iOS/iPadOS via "Aggiungi a Home"), responsive, conforme AGID/WCAG 2.1 AA.
**File**:
- `public/manifest.webmanifest` — manifest (theme `#0066CC` blu Italia, `display: standalone`, `orientation: any`, icone any+maskable, shortcuts).
- `public/sw.js` — service worker. Cache `nis2-shell-vX.Y.Z`. **Strategia**: `/api/*` **network-only** (MAI cacheato → no leak cross-utente su device condiviso); navigazioni HTML **network-first** → fallback `/offline.html`; asset statici **stale-while-revalidate**. **Bumpare il nome cache ad ogni release** (e' legato alla versione).
- `public/offline.html` — fallback offline (Bootstrap Italia + CSS inline di riserva).
- `public/js/pwa.js` — registra il SW + banner "Installa app" (Android) + hint "Aggiungi a Home" (iOS), i18n IT/EN (chiave `nis2_lang`).
- `public/assets/icons/*` — set icone (192/512, maskable, apple-touch 180, favicon 16/32, `icon.svg` master).
- `scripts/gen-pwa-icons.mjs` — **genera le icone** senza dipendenze (pure Node + zlib): `node scripts/gen-pwa-icons.mjs`.
- `scripts/inject-pwa-head.mjs` — **inietta i tag PWA** negli `<head>` (idempotente): `node scripts/inject-pwa-head.mjs`. Esclude `offline.html` e i file `* copy`.
**AGID/WCAG**: design system Bootstrap Italia (gia' live); **zoom e orientamento non bloccati**; **touch target ≥44px** (`@media (pointer: coarse)` in `style.css`); safe-area inset in `@media (display-mode: standalone)`. La "Dichiarazione di accessibilita'" AGID e' obbligatoria solo per servizi PA → qui opzionale (non implementata).
**Deploy**: tutto **statico** → live via bind-mount nginx, **nessun `kill -USR2`** (nessun `.php` toccato). Dopo edit a `sw.js`/`pwa.js` o ai tag head ricordarsi il **cache-buster** `?v=` (i tag head usano `pwa.js?v=20260614`) e il bump del nome cache SW.
## Stack Tecnologico ## Stack Tecnologico
- Backend: PHP 8.4 vanilla (no framework, Front Controller pattern) - Backend: PHP 8.4 vanilla (no framework, Front Controller pattern)
- Database: MySQL 8.x (nis2_agile_db) - Database: MySQL 8.x (nis2_agile_db)
@@ -1055,3 +1099,27 @@ Status adoption: acknowledged 2026-05-17.
### REGOLA: Fonti certe (AI + help) ### REGOLA: Fonti certe (AI + help)
Ogni affermazione normativa di AI e help **deve citare** una fonte di `application/config/nis2_sources.php`. Ogni affermazione normativa di AI e help **deve citare** una fonte di `application/config/nis2_sources.php`.
`AIService::authoritativeSourcesBlock()` è iniettato nei system prompt e **vieta riferimenti inventati**. `AIService::authoritativeSourcesBlock()` è iniettato nei system prompt e **vieta riferimenti inventati**.
---
## 🔴 AZIONE RICHIESTA (da VIGILE AgileHub, 2026-06-09) — spostare EMAIL_MS_URL su path interno
**Motivo (security #4 cross-suite)**: AgileHub sta applicando l'**edge-strip** di `X-Internal-Key` sul 443 pubblico (`agilehub.agile.software`) — la chiave interna NON sarà più accettata da Internet. NIS2 oggi invia email via URL **pubblico** con `X-Internal-Key` → **dopo lo strip le email di NIS2 si rompono** (401) finché non passate al path interno.
**Path interno già pronto (AgileHub-side fatto)**: vhost mirror controllato su `:8081`, raggiungibile dalla subnet NIS2 (`ufw allow 172.21.0.0/16 → 8081`), `X-Internal-Key` onorata lì.
**Modifica da fare (NIS2-side, in finestra)**:
1. In `/var/www/nis2-agile/.env` riga 24, cambiare:
```
# PRIMA:
EMAIL_MS_URL=https://agilehub.agile.software/api/emails
# DOPO:
EMAIL_MS_URL=http://172.21.0.1:8081/api/emails
```
(`172.21.0.1` = gateway della rete `docker_nis2-network`; il path `/api/emails/*` è identico al pubblico, cambia solo host. `X-Internal-Key` resta invariata.)
2. Recreate `nis2-app` (micro-downtime, vostra finestra).
3. Verifica: inviare una email di prova → deve risultare `SENT` (no 401/000). Reachability rapida: `curl -o /dev/null -w "%{http_code}" -X POST http://172.21.0.1:8081/api/emails/send-raw -H "X-Internal-Key: $INTERNAL_EMAIL_KEY" -H "Content-Type: application/json" -d '{}'` → atteso **400** (auth ok, body vuoto), NON 000/401.
**Rollback** (~10s, possibile finché serve): rimettere `EMAIL_MS_URL` al valore pubblico + recreate. NB: dopo l'edge-strip il pubblico darà 401 → il path corretto è l'interno.
**Pattern cross-suite**: stesso identico per gli altri prodotti email-sending (TRPG già migrato su `172.20.0.1:8081`). Owner standard: `external-api-tenancy-model` (VIGILE).
+38
View File
@@ -0,0 +1,38 @@
<!--META|doc_type=linea_guida_agile|title=Linea Guida: Modello SGSI Agile e Lavoro da Qualsiasi Luogo (Work-from-Anywhere)|status=approved|version=1.0-->
<h2>Linea Guida: Modello SGSI Agile e Lavoro da Qualsiasi Luogo</h2>
<h3>1. Scopo</h3>
<p>Definire l'impostazione del Sistema di Gestione della Sicurezza delle Informazioni (SGSI) di <strong>Nuova Agile Technology srl</strong> secondo un approccio <strong>agile/lean</strong>, coerente con un'azienda cloud-native senza sede fisica. Questa Linea Guida orienta tutti gli altri documenti del SGSI.</p>
<h3>2. Ambito</h3>
<p>L'ambito SGSI copre lo sviluppo software (licenze on-premise e SaaS), i dati dei clienti e le credenziali aziendali, trattati su <strong>servizi esclusivamente cloud</strong> (Aruba in Italia, Hetzner in UE, piattaforme AI). <strong>Non esiste un perimetro fisico aziendale</strong>: niente server in sede, niente data center proprietario. L'ambito si definisce sui <em>dati, gli account e i dispositivi</em>, non sui luoghi.</p>
<h3>3. Principi dell'approccio agile/lean</h3>
<ul>
<li><strong>Scope stretto</strong>: si protegge ciò che conta (codice, dati clienti, credenziali cloud), senza estendere controlli a perimetri inesistenti.</li>
<li><strong>Documentazione minima e viva</strong>: regole brevi e pratiche, aggiornate spesso; niente burocrazia. Ogni documento è uno strumento operativo, non un adempimento.</li>
<li><strong>Responsabilità condivisa col cloud</strong>: la sicurezza fisica dei data center e gran parte dell'infrastruttura sono responsabilità di Aruba/Hetzner; noi ci occupiamo di account, accessi, configurazioni, dati e dispositivi (controllo A.5.23 sicurezza nei servizi cloud, 27017/27018).</li>
<li><strong>Secure-by-default</strong>: MFA ovunque, cifratura di default, SSO, account aziendali gestiti centralmente.</li>
<li><strong>Evidenze automatizzate</strong>: la piattaforma SGSI registra controlli periodici e audit; si privilegiano prove generate dal sistema rispetto a registri manuali.</li>
</ul>
<h3>4. Il modello Work-from-Anywhere</h3>
<p>Il luogo di lavoro può essere <strong>qualsiasi</strong>: casa, coworking, viaggio, all'aperto. La sicurezza non dipende dal luogo ma dal modo di lavorare:</p>
<ul>
<li>Ogni collaboratore usa <strong>un portatile e uno smartphone aziendali dedicati</strong>, cifrati e gestiti (MDM), con <strong>separazione netta tra strumenti privati e di lavoro</strong>.</li>
<li><strong>Posta e servizi sono aziendali</strong>, amministrati dalla società: niente account personali per il lavoro.</li>
<li>Le <strong>chiavi/credenziali di accesso al cloud</strong> hanno copie di sicurezza controllate (key escrow cifrato), così lo smarrimento di un dispositivo non causa perdita di accesso.</li>
</ul>
<h3>5. Come si applicano gli altri documenti</h3>
<p>La <strong>Politica di Lavoro da Remoto e Mobilità (33)</strong> regola il comportamento da qualsiasi luogo; la <strong>Politica sui Dispositivi (34)</strong> regola portatili e smartphone; la <strong>Politica di Posta e Servizi (35)</strong> regola account ed email; la <strong>Procedura Chiavi Cloud (36)</strong> regola la custodia delle credenziali. Tutti discendono dai principi qui esposti.</p>
<h3>6. Ruoli</h3>
<p>Vedi organigramma SGSI. <strong>Direzione/Presidente</strong>: Silvia Garretto. <strong>RSGSI</strong>: Massimo Tagliavini. <strong>Responsabile IT e Sicurezza</strong>: Simon Fattori. <strong>DPO</strong>: consulente esterno.</p>
<h3>7. Controlli ISO e riferimenti NIS2</h3>
<p>Controlli collegati: A.6.7, A.7.9, A.8.1, A.5.10, A.5.23 (ISO/IEC 27001:2022; 27017/27018). NIS2 (D.Lgs. 138/2024): art.23 (governance), art.24 (misure di gestione del rischio). ISO è buona prassi, non obbligo di legge.</p>
<h3>8. Riesame e versionamento</h3>
<p>Documento vivo, riesaminato almeno annualmente o a fronte di cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
+36
View File
@@ -0,0 +1,36 @@
<!--META|doc_type=politica_remoto|title=Politica di Lavoro da Remoto e Mobilità|status=approved|version=1.0-->
<h2>Politica di Lavoro da Remoto e Mobilità</h2>
<h3>1. Scopo</h3>
<p>Stabilire le regole per lavorare in sicurezza da <strong>qualsiasi luogo</strong> (casa, coworking, viaggio, all'aperto), poiché Nuova Agile Technology srl opera con modello Work-from-Anywhere e <strong>senza sede fissa né perimetro fisico</strong>.</p>
<h3>2. Ambito</h3>
<p>Si applica a tutti i collaboratori (dipendenti e P.IVA esterne) quando accedono a dati, codice o servizi aziendali, ovunque si trovino e con qualsiasi rete.</p>
<h3>3. Regole di lavoro da remoto</h3>
<ul>
<li><strong>Solo dispositivi aziendali</strong>: l'accesso a sistemi e dati avviene esclusivamente dal portatile e dallo smartphone aziendali (vedi Politica 34). Niente dispositivi privati per il lavoro.</li>
<li><strong>Accesso sempre con MFA e SSO</strong>: ogni servizio cloud richiede autenticazione a più fattori; le sessioni hanno timeout automatico.</li>
<li><strong>Reti non fidate</strong>: su Wi-Fi pubblici o di terzi usare sempre connessioni cifrate (HTTPS/TLS, eventuale VPN aziendale). Mai disattivare la cifratura.</li>
<li><strong>Schermo e privacy fisica</strong>: in luoghi pubblici evitare la visibilità dello schermo (shoulder surfing); usare filtri privacy quando possibile; bloccare lo schermo allontanandosi (anche per pochi minuti).</li>
<li><strong>Asset fuori sede</strong>: non lasciare dispositivi incustoditi in auto, mezzi o spazi pubblici; in viaggio tenerli sempre con sé o in luogo sicuro.</li>
<li><strong>Nessun dato sensibile in chiaro fuori dai sistemi gestiti</strong>: niente download su supporti non cifrati, niente stampe non necessarie, niente copie su servizi personali.</li>
<li><strong>Trasferimento informazioni</strong>: condividere dati solo tramite canali e strumenti aziendali approvati; evitare app di messaggistica personali per documenti di lavoro.</li>
</ul>
<h3>4. Furto, smarrimento o sospetta compromissione</h3>
<ul>
<li>Segnalare <strong>immediatamente</strong> al Responsabile IT e Sicurezza (Simon Fattori) lo smarrimento o furto di un dispositivo o un accesso anomalo.</li>
<li>IT procede con <strong>blocco/wipe remoto</strong> via MDM e revoca/rotazione delle credenziali coinvolte.</li>
<li>Le chiavi di accesso al cloud restano recuperabili grazie al key escrow controllato (vedi Procedura 36), quindi lo smarrimento del dispositivo non causa perdita di accesso.</li>
</ul>
<h3>5. Ruoli</h3>
<p>Vedi organigramma. <strong>Responsabile IT e Sicurezza</strong> (Simon Fattori): configura MFA/MDM, gestisce incidenti e wipe remoto. <strong>RSGSI</strong> (Massimo Tagliavini): vigila sull'applicazione. <strong>Collaboratori</strong>: rispettano la politica e segnalano gli incidenti. <strong>Direzione</strong> (Silvia Garretto): approva e sostiene la politica.</p>
<h3>6. Controlli ISO e riferimenti NIS2</h3>
<p>ISO/IEC 27001:2022: A.6.7 (lavoro da remoto), A.7.9 (sicurezza degli asset fuori sede), A.8.1 (dispositivi endpoint), A.5.14 (trasferimento informazioni), A.8.5 (autenticazione sicura). NIS2 (D.Lgs. 138/2024): art.24 (igiene informatica, controllo accessi); incidenti rilevanti notificati al CSIRT Italia ai sensi dell'art.25.</p>
<h3>7. Riesame e versionamento</h3>
<p>Documento vivo, riesaminato almeno annualmente o a fronte di incidenti o cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
+39
View File
@@ -0,0 +1,39 @@
<!--META|doc_type=politica_dispositivi|title=Politica sui Dispositivi Aziendali e Separazione Privato/Lavoro|status=approved|version=1.0-->
<h2>Politica sui Dispositivi Aziendali e Separazione Privato/Lavoro</h2>
<h3>1. Scopo</h3>
<p>Definire come sono configurati, usati e protetti i dispositivi aziendali, garantendo una <strong>separazione netta tra strumenti privati e di lavoro</strong> in un modello senza sede fisica.</p>
<h3>2. Ambito</h3>
<p>Si applica a <strong>portatili e smartphone aziendali dedicati</strong> assegnati a ciascun collaboratore (dipendenti e P.IVA esterne), usati per accedere a dati, codice e servizi aziendali.</p>
<h3>3. Regole sui dispositivi</h3>
<ul>
<li><strong>Un portatile e uno smartphone aziendali per persona</strong>, dedicati e gestiti centralmente tramite <strong>MDM</strong>.</li>
<li><strong>Uso solo professionale</strong>: i dispositivi aziendali si usano <strong>esclusivamente per il lavoro</strong>. Niente uso personale, niente account privati, niente app non necessarie installate fuori dal catalogo approvato.</li>
<li><strong>Cifratura di default</strong>: disco e storage cifrati (full-disk encryption); smartphone cifrato e con blocco schermo obbligatorio (PIN/biometria).</li>
<li><strong>Secure-by-default</strong>: aggiornamenti automatici di sistema e applicazioni, antimalware attivo, firewall locale, blocco schermo dopo inattività.</li>
<li><strong>MDM e controllo remoto</strong>: IT può applicare policy, distribuire aggiornamenti e in caso di furto/smarrimento eseguire <strong>blocco o cancellazione remota (wipe)</strong>.</li>
<li><strong>Nessuna credenziale in chiaro sul dispositivo</strong>: password e chiavi vivono nel password manager aziendale; nessun file di credenziali in chiaro sul portatile (vedi Procedura 36).</li>
<li><strong>Nessun software non autorizzato</strong>: niente installazioni arbitrarie; le privilegi di amministratore sono limitati e concessi da IT.</li>
</ul>
<h3>4. Separazione privato/lavoro</h3>
<ul>
<li>I dati personali del collaboratore <strong>non risiedono</strong> sui dispositivi aziendali; i dati aziendali <strong>non risiedono</strong> sui dispositivi privati.</li>
<li>Niente sincronizzazione con cloud personali (Drive/iCloud personali, foto, backup privati).</li>
<li>Questa separazione tutela sia l'azienda sia la privacy del collaboratore: in caso di wipe remoto non si perdono dati personali, perché non devono esserci.</li>
</ul>
<h3>5. Restituzione e fine rapporto</h3>
<p>Alla cessazione del rapporto o riassegnazione, il dispositivo è restituito, ripulito (wipe) e riconfigurato; gli accessi associati sono revocati e le chiavi ruotate.</p>
<h3>6. Ruoli</h3>
<p>Vedi organigramma. <strong>Responsabile IT e Sicurezza</strong> (Simon Fattori): gestisce MDM, cifratura, wipe e catalogo software. <strong>RSGSI</strong> (Massimo Tagliavini): vigila sulla conformità. <strong>Collaboratori</strong>: usano i dispositivi solo per il lavoro e segnalano anomalie. <strong>Direzione</strong> (Silvia Garretto): approva la politica.</p>
<h3>7. Controlli ISO e riferimenti NIS2</h3>
<p>ISO/IEC 27001:2022: A.8.1 (dispositivi endpoint), A.7.9 (asset fuori sede), A.5.10 (uso accettabile di informazioni e asset), A.8.24 (crittografia). NIS2 (D.Lgs. 138/2024): art.24 (igiene informatica di base, crittografia).</p>
<h3>8. Riesame e versionamento</h3>
<p>Documento vivo, riesaminato almeno annualmente o a fronte di cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
+39
View File
@@ -0,0 +1,39 @@
<!--META|doc_type=politica_posta|title=Politica di Posta Elettronica e Servizi Aziendali|status=approved|version=1.0-->
<h2>Politica di Posta Elettronica e Servizi Aziendali</h2>
<h3>1. Scopo</h3>
<p>Stabilire che <strong>posta elettronica e servizi sono aziendali</strong>, amministrati e controllati dalla società, e definire le regole d'uso degli account in un modello senza sede fisica.</p>
<h3>2. Ambito</h3>
<p>Si applica a tutte le caselle email, agli account dei servizi cloud (Aruba, Hetzner, piattaforme AI, repository, gestionali) e a ogni identità digitale usata per il lavoro da parte di dipendenti e P.IVA esterne.</p>
<h3>3. Regole su posta e servizi</h3>
<ul>
<li><strong>Account aziendali, non personali</strong>: il lavoro si svolge solo con caselle e account forniti e amministrati dalla società. <strong>Vietato usare account personali</strong> per attività lavorative.</li>
<li><strong>Controllo della società</strong>: l'azienda amministra creazione, configurazione, sospensione e cancellazione degli account, con tutte le autorizzazioni di gestione (admin centralizzato, SSO).</li>
<li><strong>Niente inoltro a caselle personali</strong>: è vietato l'auto-inoltro o l'inoltro manuale sistematico di email di lavoro verso indirizzi personali o esterni non autorizzati.</li>
<li><strong>MFA obbligatoria</strong> su email e su tutti i servizi; password robuste gestite nel password manager aziendale (vedi Procedura 36).</li>
<li><strong>SSO ove possibile</strong>: accesso ai servizi tramite Single Sign-On per centralizzare controllo e revoche.</li>
<li><strong>Uso appropriato</strong>: la posta aziendale serve per scopi professionali; evitare contenuti personali, iscrizioni private o uso improprio.</li>
<li><strong>Trasferimento informazioni</strong>: condividere dati riservati solo con destinatari autorizzati e tramite canali approvati; attenzione a phishing e mittenti non verificati.</li>
</ul>
<h3>4. Gestione del ciclo di vita degli account</h3>
<ul>
<li><strong>Onboarding</strong>: IT crea gli account con i privilegi minimi necessari (least privilege).</li>
<li><strong>Variazioni</strong>: i diritti di accesso seguono il ruolo e sono rivisti periodicamente.</li>
<li><strong>Offboarding</strong>: alla cessazione del rapporto gli account sono disattivati e le credenziali revocate tempestivamente; la casella resta sotto controllo della società.</li>
</ul>
<h3>5. Privacy e DPO</h3>
<p>Il controllo degli account è finalizzato alla sicurezza e alla continuità del servizio, nel rispetto della normativa privacy; il <strong>DPO (consulente esterno)</strong> è coinvolto per gli aspetti di protezione dei dati personali.</p>
<h3>6. Ruoli</h3>
<p>Vedi organigramma. <strong>Responsabile IT e Sicurezza</strong> (Simon Fattori): amministra account, MFA, SSO e revoche. <strong>RSGSI</strong> (Massimo Tagliavini): vigila sull'applicazione. <strong>DPO</strong>: presidia la conformità privacy. <strong>Direzione</strong> (Silvia Garretto): approva la politica.</p>
<h3>7. Controlli ISO e riferimenti NIS2</h3>
<p>ISO/IEC 27001:2022: A.5.10 (uso accettabile), A.5.14 (trasferimento informazioni), A.5.17 (informazioni di autenticazione), A.8.5 (autenticazione sicura), A.5.23 (sicurezza nei servizi cloud). NIS2 (D.Lgs. 138/2024): art.24 (controllo degli accessi, igiene informatica).</p>
<h3>8. Riesame e versionamento</h3>
<p>Documento vivo, riesaminato almeno annualmente o a fronte di cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
+44
View File
@@ -0,0 +1,44 @@
<!--META|doc_type=procedura_chiavi_cloud|title=Procedura di Custodia e Backup delle Chiavi di Accesso al Cloud|status=approved|version=1.0-->
<h2>Procedura di Custodia e Backup delle Chiavi di Accesso al Cloud</h2>
<h3>1. Scopo</h3>
<p>Definire come custodire e copiare in sicurezza le <strong>chiavi e credenziali di accesso ai servizi cloud</strong>, affinché lo smarrimento dell'unico dispositivo <strong>non causi mai la perdita di accesso</strong> ai servizi, senza creare nuovi rischi di esposizione.</p>
<h3>2. Ambito</h3>
<p>Si applica a password, chiavi API, chiavi SSH, token, certificati e codici di recupero MFA relativi ai servizi cloud aziendali (Aruba, Hetzner, piattaforme AI, repository, console di amministrazione).</p>
<h3>3. Principi (key escrow controllato)</h3>
<ul>
<li><strong>Copia di sicurezza obbligatoria</strong>: ogni chiave critica ha una copia di backup; perdere un dispositivo non deve significare perdere l'accesso.</li>
<li><strong>Sempre cifrate</strong>: le copie sono cifrate; <strong>mai in chiaro</strong> e mai conservate sul portatile o su file locali non protetti.</li>
<li><strong>Custodia centralizzata</strong>: le credenziali vivono in un <strong>gestore di segreti / password manager aziendale</strong> (cassaforte digitale), non in fogli, email o note personali.</li>
<li><strong>Accesso ristretto e tracciato</strong>: solo persone autorizzate accedono; ogni accesso è registrato (audit log).</li>
<li><strong>Recupero a doppia autorizzazione</strong>: il recupero o l'estrazione di chiavi critiche richiede l'<strong>approvazione di due persone</strong> (es. Responsabile IT + RSGSI), per evitare abusi.</li>
</ul>
<h3>4. Procedura operativa</h3>
<ol>
<li><strong>Generazione</strong>: alla creazione di una chiave/credenziale, registrarla subito nel password manager aziendale con etichetta del servizio e proprietario.</li>
<li><strong>Backup dei codici MFA</strong>: salvare i codici di recupero MFA nella cassaforte digitale cifrata, mai sullo stesso dispositivo che genera l'OTP.</li>
<li><strong>Backup periodico</strong>: la cassaforte digitale è inclusa nei backup cifrati; verificare periodicamente che le copie siano integre e ripristinabili.</li>
<li><strong>Recupero</strong>: in caso di smarrimento dispositivo, il collaboratore richiede l'accesso; due autorizzatori approvano; le chiavi necessarie sono rese disponibili in modo sicuro.</li>
<li><strong>Rotazione post-incidente</strong>: dopo furto/smarrimento o sospetta compromissione, ruotare (rigenerare) le credenziali coinvolte e aggiornare la cassaforte.</li>
<li><strong>Dismissione</strong>: alla cessazione del rapporto, revocare gli accessi del collaboratore e trasferire/ruotare le chiavi di sua competenza.</li>
</ol>
<h3>5. Divieti</h3>
<ul>
<li>Niente credenziali in chiaro su portatile, repository, email, chat o note personali.</li>
<li>Niente condivisione di password fuori dal gestore di segreti.</li>
<li>Niente backup delle chiavi su cloud personali o supporti non cifrati.</li>
</ul>
<h3>6. Ruoli</h3>
<p>Vedi organigramma. <strong>Responsabile IT e Sicurezza</strong> (Simon Fattori): gestisce il password manager, gli accessi e la rotazione; primo autorizzatore al recupero. <strong>RSGSI</strong> (Massimo Tagliavini): secondo autorizzatore e vigilanza. <strong>Collaboratori</strong>: registrano e proteggono le proprie credenziali. <strong>Direzione</strong> (Silvia Garretto): approva la procedura.</p>
<h3>7. Controlli ISO e riferimenti NIS2</h3>
<p>ISO/IEC 27001:2022: A.5.17 (informazioni di autenticazione), A.8.24 (crittografia), A.8.13 (backup), A.8.5 (autenticazione sicura), A.5.23 (sicurezza nei servizi cloud). NIS2 (D.Lgs. 138/2024): art.24 (crittografia, controllo accessi, continuità).</p>
<h3>8. Riesame e versionamento</h3>
<p>Documento vivo, riesaminato almeno annualmente o a fronte di incidenti o cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
+67
View File
@@ -0,0 +1,67 @@
<!--META|doc_type=manuale_sgsi|title=Manuale del Sistema di Gestione per la Sicurezza delle Informazioni (approccio agile)|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>Il presente Manuale descrive il Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) di <strong>Nuova Agile Technology srl</strong> (di seguito "l'Organizzazione" o "l'Azienda") e ne definisce l'impostazione secondo un approccio <strong>agile/lean</strong>, coerente con un'azienda cloud-native senza sede fisica. È progettato e mantenuto in conformità a <strong>ISO/IEC 27001:2022</strong>, integrata dalle linee guida <strong>ISO/IEC 27017:2015</strong> (controlli di sicurezza per i servizi cloud) e <strong>ISO/IEC 27018:2019</strong> (protezione dei dati personali nel cloud pubblico). Il Manuale è il documento di vertice della struttura documentale del SGSI: ne illustra contesto, leadership, ambito, approccio al rischio e miglioramento continuo, e orienta tutti gli altri documenti del Sistema.</p>
<h2>2. Ambito del SGSI</h2>
<p>Il SGSI si applica a tutti i processi di progettazione, sviluppo, erogazione e supporto dei servizi e prodotti software dell'Azienda, alle informazioni trattate (proprie e dei clienti, inclusi i dati personali e le credenziali aziendali) e a tutto il personale (dipendenti e collaboratori). Le due linee di business coperte sono:</p>
<ul>
<li><strong>Prodotti software in licenza d'uso</strong> installati on-premise sui server <em>dei clienti</em>;</li>
<li><strong>Servizi SaaS multi-tenant</strong> erogati in cloud.</li>
</ul>
<p>L'infrastruttura è <strong>interamente in cloud</strong> presso <strong>Aruba S.p.A.</strong> (data center in Italia) e <strong>Hetzner Online GmbH</strong> (Germania, UE), con utilizzo di piattaforme di intelligenza artificiale tramite API (es. Anthropic). <strong>L'Azienda non gestisce alcun server in sede</strong> e non esiste un perimetro fisico aziendale: niente data center proprietario. L'ambito si definisce quindi sui <em>dati, gli account e i dispositivi</em>, non sui luoghi. Le postazioni di lavoro sono esclusivamente <strong>PC portatili cifrati</strong>. La sicurezza fisica dei data center è ereditata dai fornitori cloud secondo il modello di <em>responsabilità condivisa</em> (vedi A.5.23).</p>
<h2>3. Contesto dell'Organizzazione</h2>
<p>Nuova Agile Technology srl è una software house italiana composta da circa 12 persone (9 dipendenti, 2 collaboratori esterni a P.IVA e la Presidente). I principali fattori di contesto interni ed esterni sono: dipendenza da fornitori cloud e da provider AI, requisiti contrattuali di sicurezza imposti dai clienti (supply chain), obblighi GDPR sui dati personali trattati nel SaaS e il quadro normativo NIS2. Le parti interessate rilevanti includono clienti, fornitori cloud/AI (sub-responsabili), personale, Autorità di controllo (Garante Privacy, ACN) e investitori.</p>
<h2>4. Principi dell'approccio agile/lean</h2>
<ul>
<li><strong>Scope stretto</strong>: si protegge ciò che conta (codice, dati clienti, credenziali cloud), senza estendere controlli a perimetri inesistenti.</li>
<li><strong>Documentazione minima e viva</strong>: regole brevi e pratiche, aggiornate spesso; niente burocrazia. Ogni documento è uno strumento operativo, non un adempimento.</li>
<li><strong>Responsabilità condivisa col cloud</strong>: la sicurezza fisica dei data center e gran parte dell'infrastruttura sono in capo ad Aruba/Hetzner; l'Azienda presidia account, accessi, configurazioni, dati e dispositivi (A.5.23, ISO 27017/27018).</li>
<li><strong>Secure-by-default</strong>: MFA ovunque, cifratura di default, SSO, account aziendali gestiti centralmente.</li>
<li><strong>Evidenze automatizzate</strong>: la piattaforma SGSI registra controlli periodici e audit; si privilegiano prove generate dal sistema rispetto a registri manuali.</li>
</ul>
<h2>5. Il modello Work-from-Anywhere</h2>
<p>Il luogo di lavoro può essere <strong>qualsiasi</strong>: casa, coworking, viaggio, all'aperto. La sicurezza non dipende dal luogo ma dal modo di lavorare:</p>
<ul>
<li>Ogni collaboratore usa <strong>un portatile e uno smartphone aziendali dedicati</strong>, cifrati e gestiti (MDM), con <strong>separazione netta tra strumenti privati e di lavoro</strong>.</li>
<li><strong>Posta e servizi sono aziendali</strong>, amministrati dalla società: niente account personali per il lavoro.</li>
<li>Le <strong>chiavi/credenziali di accesso al cloud</strong> hanno copie di sicurezza controllate (key escrow cifrato), così lo smarrimento di un dispositivo non causa perdita di accesso.</li>
</ul>
<p>Controlli ISO collegati a questo modello: <strong>A.6.7</strong> (lavoro a distanza), <strong>A.7.9</strong> (sicurezza degli asset fuori sede), <strong>A.8.1</strong> (dispositivi endpoint), <strong>A.5.10</strong> (uso accettabile), <strong>A.5.23</strong> (sicurezza nei servizi cloud).</p>
<h2>6. Ruoli e responsabilità (organigramma SGSI)</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità principali</th></tr>
<tr><td>Alta Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Leadership, approvazione politiche, assegnazione risorse, riesame di direzione</td></tr>
<tr><td>Responsabile SGSI (RSGSI)</td><td><strong>Massimo Tagliavini</strong></td><td>Gestione operativa del SGSI, risk management, audit interni, documentazione</td></tr>
<tr><td>Responsabile IT e Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Sicurezza tecnica, gestione cloud, controllo accessi, gestione incidenti</td></tr>
<tr><td>Referente Protezione Dati (DPO)</td><td><em>Consulente esterno (da nominare)</em></td><td>Conformità GDPR, pareri sul trattamento dei dati personali</td></tr>
</table>
<p>L'organigramma di dettaglio e la matrice RACI sono mantenuti nel registro dei ruoli del SGSI.</p>
<h2>7. Leadership e impegno della Direzione</h2>
<p>L'Alta Direzione, nella persona della Presidente, garantisce l'impegno verso il SGSI mediante: definizione e approvazione della Politica per la Sicurezza delle Informazioni; integrazione del SGSI nei processi aziendali; messa a disposizione delle risorse; promozione del miglioramento continuo; comunicazione dell'importanza della sicurezza al personale. Tali impegni soddisfano i requisiti della clausola 5 di ISO/IEC 27001 e i controlli <strong>A.5.1</strong> (Politiche per la sicurezza delle informazioni) e <strong>A.5.4</strong> (Responsabilità della direzione).</p>
<h2>8. Struttura documentale del SGSI</h2>
<p>La documentazione è organizzata su livelli gerarchici: (1) Manuale SGSI e Politica generale; (2) Politiche tematiche (controllo accessi, crittografia, classificazione, uso accettabile, lavoro da remoto e mobilità, dispositivi, posta e servizi, ecc.); (3) Procedure e istruzioni operative (incl. procedura chiavi cloud); (4) Registrazioni ed evidenze. Tutti i documenti sono soggetti a controllo di versione, approvazione e revisione periodica (A.5.37) e discendono dai principi agile/lean qui esposti.</p>
<h2>9. Approccio al rischio</h2>
<p>Il SGSI adotta un processo di valutazione e trattamento del rischio basato su ISO/IEC 27005, che prevede identificazione degli asset informativi e dei relativi rischi, analisi di probabilità e impatto, definizione del rischio accettabile e selezione delle opzioni di trattamento (mitigazione, trasferimento, accettazione, eliminazione). Particolare attenzione è posta ai rischi della <strong>supply chain cloud/AI</strong> e ai rischi sui dati personali. Gli esiti sono documentati nel Registro dei Rischi e riferiti alla <strong>Dichiarazione di Applicabilità (SoA)</strong>.</p>
<h2>10. Dichiarazione di Applicabilità (SoA)</h2>
<p>La SoA elenca i controlli dell'<strong>Annex A di ISO/IEC 27001:2022</strong> (93 controlli su 4 temi: organizzativi, persone, fisici, tecnologici), integrati dai controlli aggiuntivi di <strong>ISO/IEC 27017</strong> e <strong>27018</strong> per i servizi cloud e i dati personali. Per ciascun controllo sono indicati applicabilità, stato di implementazione e giustificazione delle esclusioni.</p>
<h2>11. Caveat sulla classificazione NIS2</h2>
<p>L'Azienda si autoclassifica <strong>in via PRELIMINARE</strong> come soggetto NIS2 "<strong>IMPORTANTE</strong>". Tuttavia l'Azienda è <strong>SOTTO le soglie dimensionali ordinarie</strong> (≥50 addetti oppure fatturato/bilancio >10 M€): la qualifica deve essere <strong>CONFERMATA dalla Direzione e dal Legale</strong>, ed eventualmente da ACN, in funzione del fatturato e della categoria di attività (provider di servizi cloud). L'adozione del SGSI è guidata anche dai <strong>requisiti di supply chain dei clienti</strong>. Il quadro di riferimento è la Direttiva (UE) 2022/2555, recepita in Italia dal <strong>D.Lgs. 138/2024</strong>: art.23 (governance e ruolo degli organi di gestione), art.24 (misure di gestione del rischio, equivalenti all'Art.21 della Direttiva), art.25 (obblighi di notifica al CSIRT, con tempistiche di pre-notifica entro 24 ore, notifica entro 72 ore e relazione finale entro 1 mese). Le misure di dettaglio sono definite dalle Determinazioni ACN. <strong>Le norme ISO citate sono buone prassi, non obblighi di legge.</strong></p>
<h2>12. Miglioramento continuo</h2>
<p>Il SGSI segue il ciclo PDCA. Sono previsti audit interni programmati, riesami di direzione almeno annuali, gestione delle non conformità e azioni correttive (NCR/CAPA), e monitoraggio di indicatori di prestazione. Gli esiti alimentano il piano di miglioramento.</p>
<h2>13. Registrazioni ed evidenze</h2>
<p>Verbali di riesame di direzione, registro dei rischi, SoA, rapporti di audit, registro delle non conformità, log di sicurezza e registro degli incidenti.</p>
<h2>14. Riesame e versionamento</h2>
<p>Documento di vertice approvato dalla Presidente. Documento vivo, riesaminato almeno annualmente o a fronte di cambiamenti significativi del contesto; versione tracciata nella piattaforma SGSI.</p>
+93
View File
@@ -0,0 +1,93 @@
<!--META|doc_type=procedura_continuita|title=Procedura di Continuità Operativa, Backup e Ripristino|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>La presente procedura definisce in modo unitario come <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") assicura la <strong>continuità operativa</strong> dei propri servizi critici, esegue e protegge i <strong>backup</strong> e garantisce il <strong>ripristino</strong> dei dati e dei servizi in caso di evento avverso (guasto cloud, attacco — es. ransomware —, errore, cancellazione accidentale, indisponibilità di personale). Considerata l'infrastruttura <strong>interamente in cloud</strong> e l'assenza di server in sede, l'obiettivo è garantire backup affidabili, capacità di ripristino del <strong>SaaS multi-tenant</strong> e dei dati dei clienti, e la continuità del lavoro di un team piccolo che opera da <strong>PC portatili cifrati</strong>, nel rispetto degli obiettivi di tempo (RTO) e di perdita dati (RPO) concordati. La procedura supporta gli obblighi di <strong>continuità operativa, gestione del backup e gestione delle crisi</strong> previsti dalle misure dell'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
<h2>2. Ambito</h2>
<p>Si applica alla disponibilità del servizio <strong>SaaS multi-tenant</strong>, ai database e ai dati dei clienti, al codice sorgente e alle configurazioni dei prodotti, alla posta e ai documenti aziendali, agli ambienti di sviluppo e rilascio e alla capacità di lavoro del team. L'infrastruttura è ospitata su <strong>Aruba S.p.A. (Italia)</strong> e <strong>Hetzner (Germania, UE)</strong>, nel rispetto del modello di responsabilità condivisa. Riguarda tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) e i fornitori cloud. Gli <strong>endpoint</strong> (PC portatili cifrati) non conservano dati critici come unica copia: i dati di lavoro risiedono nei servizi cloud aziendali. <strong>Non esistono backup su nastro né server in sede</strong>; gli scenari considerati riguardano indisponibilità di un provider o di una regione cloud, attacchi, errori gravi e indisponibilità del personale, non scenari di sala server in sede (assente per architettura).</p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – A.5.29 (sicurezza delle informazioni durante un'interruzione), A.5.30 (pronto intervento ICT per la continuità operativa), A.8.13 (backup delle informazioni), A.8.14 (ridondanza delle strutture di elaborazione), A.8.24 (uso della crittografia).</li>
<li><strong>ISO/IEC 27017:2015</strong> – continuità e responsabilità condivisa nei servizi cloud.</li>
<li><strong>ISO/IEC 27018:2019</strong> – conservazione, restituzione e ripristino dei dati personali (PII) nel cloud.</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, inclusi continuità operativa, backup e gestione delle crisi); art. 25 (notifica degli incidenti significativi).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva strategia di continuità e backup, gli obiettivi RTO/RPO; attiva lo stato di crisi, decide la comunicazione esterna e l'allocazione delle risorse.</td></tr>
<tr><td>RSGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Mantiene la procedura e il piano BCP/DR, coordina i test di ripristino e le esercitazioni, ne verifica e documenta gli esiti e i riesami.</td></tr>
<tr><td>Resp. IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Configura, esegue e monitora i backup; gestisce ridondanza e immutabilità; esegue ripristini e test, e il recovery tecnico dei servizi e dei dati secondo il piano.</td></tr>
<tr><td>DPO esterno</td><td><em>Consulente esterno</em></td><td>Valuta gli impatti sui dati personali negli scenari di indisponibilità o perdita.</td></tr>
<tr><td>Tutto il personale</td><td>—</td><td>Segue le procedure di backup degli strumenti di lavoro e collabora alla continuità.</td></tr>
</table>
<h2>5. Strategia di continuità</h2>
<h3>5.1 Analisi di impatto e obiettivi (RTO/RPO)</h3>
<p>I servizi critici (in primis il SaaS e i dati dei clienti) sono individuati tramite <strong>Business Impact Analysis (BIA)</strong>; per ciascuno si definiscono <strong>RTO</strong> (tempo massimo di ripristino) e <strong>RPO</strong> (massima perdita di dati tollerata), approvati dalla Direzione. I valori puntuali sono definiti nelle procedure operative e nel piano BCP/DR <strong>[DA VERIFICARE]</strong>. Si identificano le dipendenze critiche: provider cloud, provider AI, DNS, posta, gestore password.</p>
<h3>5.2 Strategie di continuità e ridondanza</h3>
<ul>
<li>Si privilegiano architetture <strong>ridondate</strong> e, ove fattibile, la possibilità di ripristino su una <strong>regione/fornitore alternativo</strong> (A.8.14), sfruttando i backup cloud cifrati e, ove disponibile, immutabili.</li>
<li>Si valutano le garanzie di disponibilità (SLA) dei provider e si predispongono misure per ridurre l'impatto di un'indisponibilità prolungata, inclusa la <strong>portabilità dei dati</strong>; la ripartizione delle responsabilità di continuità tra azienda e fornitore è documentata (modello di responsabilità condivisa, ISO 27017).</li>
<li>Essendo il team interamente remoto su portatili cifrati, la <strong>continuità del lavoro</strong> è intrinsecamente resiliente all'indisponibilità di una singola sede; i dati di lavoro essenziali sono sincronizzati sui servizi cloud aziendali per evitarne la perdita in caso di guasto o furto del dispositivo.</li>
</ul>
<h3>5.3 Continuità delle persone chiave</h3>
<p>Trattandosi di un team piccolo, sono identificate le competenze critiche e previste misure di ridondanza minime (documentazione, accessi di backup custoditi in sicurezza, condivisione delle conoscenze) per evitare single point of failure umani.</p>
<h2>6. Backup (regola 3-2-1 su cloud)</h2>
<h3>6.1 Pianificazione</h3>
<ol>
<li>Per ciascun servizio critico si definiscono <strong>RPO</strong> e <strong>RTO</strong> coerenti con la BIA, approvati dalla Direzione.</li>
<li>Si adotta lo schema di riferimento <strong>3-2-1</strong> per quanto applicabile in cloud: più copie, su servizi/regioni distinti (preferibilmente data center diversi nell'UE), con almeno una copia logicamente separata e protetta da modifiche.</li>
</ol>
<h3>6.2 Esecuzione</h3>
<ol>
<li>I backup dei database SaaS, dei dati dei clienti e del codice sorgente sono <strong>automatici e schedulati</strong> (frequenza coerente con l'RPO, di norma giornaliera).</li>
<li>Le copie sono <strong>cifrate</strong> a riposo (A.8.24) e conservate in cloud con accesso ristretto e <strong>MFA</strong>, con gli stessi controlli di accesso dei dati di produzione.</li>
<li>Ove disponibile si attiva l'<strong>immutabilità</strong> delle copie (protezione anti-ransomware) e la separazione delle credenziali di gestione dei backup.</li>
<li>Si applica una <strong>politica di retention e rotazione</strong> definita (copie giornaliere a breve termine e copie a più lunga conservazione), nel rispetto degli obblighi sui dati personali (minimizzazione e cancellazione sicura, A.8.13 / ISO 27018).</li>
</ol>
<h3>6.3 Monitoraggio</h3>
<ol>
<li>Il Resp. IT/Sicurezza verifica l'<strong>esito di ogni job</strong> di backup; i fallimenti generano un alert e una verifica entro le 24 ore lavorative successive.</li>
<li>Gli esiti negativi ricorrenti sono registrati come <strong>non conformità</strong> nel modulo NCR/CAPA della piattaforma.</li>
</ol>
<h2>7. Ripristino e test</h2>
<h3>7.1 Test di ripristino</h3>
<ul>
<li>Con cadenza <strong>almeno trimestrale</strong> si esegue un <strong>test di ripristino</strong> su un dato/servizio campione, verificando integrità e tempi rispetto a RTO/RPO. <strong>Un backup non testato non è considerato affidabile.</strong></li>
<li>L'esito è documentato; gli scostamenti attivano azioni correttive nel modulo NCR/CAPA.</li>
</ul>
<h3>7.2 Ripristino reale</h3>
<p>In caso di incidente, il ripristino è autorizzato e coordinato secondo la presente procedura: si privilegia una copia integra e verificata, si rispettano RTO/RPO e al termine si validano i dati ripristinati.</p>
<h2>8. Gestione della crisi (BCP/DR)</h2>
<h3>8.1 Attivazione e gestione</h3>
<ol>
<li>Al verificarsi di un evento grave, la Direzione (o suo delegato) <strong>attiva il piano</strong> e nomina i referenti.</li>
<li>Si attiva in parallelo la <strong>gestione degli incidenti</strong>; il Resp. IT/Sicurezza esegue il ripristino dei servizi e dei dati dalle copie integre, rispettando RTO/RPO.</li>
<li>Se l'evento configura un <strong>incidente significativo NIS2</strong> e l'Azienda è soggetto obbligato, si applicano gli obblighi di <strong>notifica al CSIRT Italia (ACN)</strong> ai sensi dell'art. 25 del D.Lgs. 138/2024: <strong>pre-allarme entro 24 ore</strong>, <strong>notifica completa entro 72 ore</strong>, <strong>relazione finale entro 1 mese</strong>.</li>
</ol>
<h3>8.2 Comunicazione</h3>
<p>Si comunica <strong>tempestivamente ai clienti impattati</strong> lo stato del disservizio e i tempi stimati di ripristino, in adempimento agli obblighi contrattuali di supply chain verso i clienti NIS2.</p>
<h3>8.3 Ritorno alla normalità</h3>
<p>Verificato il pieno ripristino, si dichiara la chiusura della crisi e si redige il rapporto di evento con le <strong>lezioni apprese</strong>, che aggiornano il piano e alimentano azioni correttive. Il piano BCP/DR è <strong>testato almeno una volta l'anno</strong> (esercitazione/simulazione).</p>
<h2>9. Controlli ISO collegati e riferimenti NIS2</h2>
<p>La procedura attua i controlli <strong>A.5.29, A.5.30, A.8.13, A.8.14, A.8.24</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018 per continuità, ripristino e protezione dei PII. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di backup, continuità e gestione delle crisi; quando l'interruzione configura un incidente significativo valgono le tempistiche di notifica dell'<strong>art. 25</strong>. Le evidenze dei test di ripristino dimostrano resilienza anche ai clienti NIS2 nell'ambito della supply chain.</p>
<h2>10. Registrazioni ed evidenze</h2>
<ul>
<li>Piano BCP/DR con BIA, RTO/RPO per servizio e dipendenze critiche.</li>
<li>Piano di backup; log/report di esecuzione e di esito dei job.</li>
<li>Rapporti dei test di ripristino trimestrali e delle esercitazioni annuali.</li>
<li>Rapporti degli eventi reali con lezioni apprese e comunicazioni ai clienti durante i disservizi.</li>
<li>Inventario dei servizi critici e relative dipendenze cloud.</li>
<li>Non conformità collegate a fallimenti di backup/ripristino (modulo NCR/CAPA).</li>
</ul>
<h2>11. Riesame e versionamento</h2>
<p>La procedura è riesaminata <strong>almeno annualmente</strong> o a fronte di un incidente significativo, di un test di ripristino con esito negativo o di cambiamenti dell'architettura cloud o dei requisiti RTO/RPO. È approvata dalla Direzione; le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI secondo la Procedura di Governance del SGSI.</p>
+118
View File
@@ -0,0 +1,118 @@
<!--META|doc_type=procedura_operativa_sviluppo|title=Procedura di Gestione Operativa e Sviluppo Sicuro|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>La presente procedura stabilisce come <strong>Nuova Agile Technology srl</strong> integra la sicurezza nel ciclo di vita dello sviluppo software (Secure SDLC), governa i cambiamenti ai propri sistemi, gestisce le vulnerabilità tecniche e le patch, e presidia logging e monitoraggio. L'obiettivo è garantire che i prodotti — sia in <strong>licenza d'uso installati sui server dei clienti (on-premise)</strong>, sia il <strong>SaaS multi-tenant in cloud</strong> — siano progettati, sviluppati, rilasciati e mantenuti in modo da ridurre vulnerabilità e proteggere i dati, e che ogni cambiamento avvenga in modo controllato e tracciabile. Soddisfa i requisiti di sicurezza nell'acquisizione, sviluppo e manutenzione previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
<h2>2. Ambito</h2>
<p>Si applica a tutte le attività di analisi, progettazione, codifica, test, rilascio e manutenzione del software, e ai relativi cambiamenti, vulnerabilità, patch e log su: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), configurazioni di rete e di sicurezza, servizi <strong>SaaS</strong> multi-tenant e prodotti <strong>on-premise</strong> presso i clienti, dipendenze e librerie di terze parti, pipeline di build/deploy, integrazioni con <strong>piattaforme AI</strong> via API (es. LLM Anthropic) ed <strong>endpoint</strong> (PC portatili cifrati). Riguarda tutto il personale tecnico (9 dipendenti, 2 collaboratori esterni a P.IVA) ed eventuali fornitori che contribuiscono allo sviluppo. L'attività si svolge esclusivamente da PC portatili cifrati, senza alcun server in sede.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 – Annex A: A.5.7 (threat intelligence), A.8.7 (protezione dai malware), A.8.8 (gestione delle vulnerabilità tecniche), A.8.9 (gestione della configurazione), A.8.15 (logging), A.8.16 (monitoraggio), A.8.25–A.8.33 (sviluppo sicuro, requisiti applicazioni, architettura e codifica sicura, test, sviluppo esterno, separazione ambienti, gestione cambiamenti, dati di test), A.5.17 (autenticazione).</li>
<li>ISO/IEC 27017:2015 e ISO/IEC 27018:2019 – sicurezza dello sviluppo, dei cambiamenti e delle vulnerabilità nei servizi cloud e protezione dei PII (privacy by design).</li>
<li>D.Lgs. 4 settembre 2024, n. 138 (NIS2): art. 24 (sicurezza nell'acquisizione/sviluppo/manutenzione, gestione delle modifiche e divulgazione delle vulnerabilità) e art. 25 (notifica incidenti significativi).</li>
<li>Regolamento (UE) 2016/679 (GDPR), artt. 25 e 32 (privacy by design e by default, sicurezza del trattamento).</li>
<li>Procedura di Gestione degli Incidenti dell'Azienda.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Presidente / Direzione (Silvia Garretto)</td><td>Approva la procedura e assegna le risorse; autorizza i cambiamenti ad alto impatto e le finestre di rilascio critiche; approva tempistiche di rimedio e deroghe motivate.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la procedura, verifica che i cambiamenti e le vulnerabilità rilevanti siano valutati nel rischio e documentati, controlla il rispetto dei tempi di rimedio e registra le non conformità.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Definisce gli standard tecnici; gestisce code review, analisi e prioritizzazione delle vulnerabilità, patch e test; valuta e attua i cambiamenti con il piano di rollback; esegue la revisione di log e accessi.</td></tr>
<tr><td>Sviluppatori (dipendenti e collaboratori)</td><td>Applicano le regole di codifica sicura, eseguono test e gestiscono le segnalazioni di vulnerabilità.</td></tr>
<tr><td>Richiedente del cambiamento</td><td>Apre la richiesta di cambiamento (RFC) descrivendo finalità e impatto atteso.</td></tr>
<tr><td>DPO (consulente esterno)</td><td>Valida i requisiti di privacy by design quando si trattano dati personali.</td></tr>
</table>
<h2>5. Sviluppo sicuro (Secure SDLC)</h2>
<h3>5.1 Requisiti e progettazione sicura</h3>
<ul>
<li>I requisiti di sicurezza e privacy sono definiti già in fase di analisi, considerando il modello di minaccia, la separazione tra tenant nel SaaS e l'isolamento delle istanze on-premise.</li>
<li>Si applicano i principi di <strong>privacy by design e by default</strong> (GDPR artt. 25 e 32) e di minimizzazione dei dati.</li>
</ul>
<h3>5.2 Codifica sicura e dipendenze</h3>
<ul>
<li>Si seguono regole di secure coding per prevenire le vulnerabilità più comuni (injection, autenticazione/sessione, controllo accessi, gestione errori, esposizione di dati).</li>
<li>I segreti (credenziali, chiavi API, incluse quelle dei servizi AI) non sono mai inseriti nel codice né nei repository: si usano meccanismi di gestione segreti dedicati. Gli ambienti di sviluppo risiedono su PC portatili cifrati con accesso autenticato.</li>
<li>Le librerie e i componenti open source sono censiti e tenuti aggiornati; si monitorano gli avvisi di vulnerabilità note e si applicano le patch in tempi proporzionati al rischio.</li>
</ul>
<h3>5.3 Test di sicurezza e separazione degli ambienti</h3>
<ul>
<li>Sono previsti test funzionali e di sicurezza prima del rilascio; per le componenti più esposte si eseguono verifiche statiche/dinamiche e, ove opportuno, test di penetrazione. Gli ambienti di test usano dati anonimizzati o sintetici (mai PII reali dei clienti).</li>
<li>Ambienti di sviluppo, test e produzione sono separati (A.8.31); il codice è versionato e le modifiche sono sottoposte a code review tra pari prima dell'integrazione.</li>
</ul>
<h3>5.4 Componenti di intelligenza artificiale</h3>
<p>L'uso di API LLM esterne è valutato per i rischi su confidenzialità dei dati inviati e affidabilità degli output; i dati personali e riservati inviati ai servizi AI sono minimizzati e protetti coerentemente con i contratti e i DPA dei fornitori.</p>
<h2>6. Gestione dei cambiamenti</h2>
<ol>
<li><strong>Richiesta (RFC).</strong> Il richiedente apre una richiesta di cambiamento descrivendo obiettivo, sistemi coinvolti, impatto atteso e urgenza.</li>
<li><strong>Valutazione di impatto e rischio.</strong> Simon Fattori classifica il cambiamento (standard a basso rischio / normale / emergenza) e ne valuta gli effetti su sicurezza, dati personali e continuità; i cambiamenti rilevanti sono registrati nel <strong>modulo "Rischi"</strong> ove pertinente.</li>
<li><strong>Autorizzazione.</strong> I cambiamenti standard a basso rischio sono approvati da Simon Fattori; quelli normali ad alto impatto richiedono l'autorizzazione della Direzione. Va sempre definito un <strong>piano di rollback</strong>.</li>
<li><strong>Test in ambiente separato.</strong> Le modifiche software sono validate in ambienti di sviluppo/test separati dall'esercizio (A.8.31), con test funzionali e di sicurezza (A.8.29) prima del rilascio.</li>
<li><strong>Attuazione.</strong> Il cambiamento è applicato in una finestra concordata; per il SaaS si privilegiano rilasci controllati. Per i prodotti on-premise, l'azienda fornisce ai clienti note di rilascio e indicazioni di aggiornamento, nel rispetto degli obblighi contrattuali di supply chain.</li>
<li><strong>Verifica post-cambiamento.</strong> Si verifica il corretto funzionamento e l'assenza di effetti collaterali; in caso di esito negativo si attiva il rollback.</li>
<li><strong>Chiusura.</strong> Esito e documentazione sono registrati; eventuali anomalie alimentano <strong>non conformità</strong> e azioni correttive.</li>
</ol>
<h3>6.1 Cambiamenti di emergenza</h3>
<p>Per cambiamenti urgenti (es. mitigazione di una vulnerabilità critica), l'attuazione può precedere l'autorizzazione formale, ma <strong>deve essere documentata a posteriori entro il giorno lavorativo successivo</strong> e ratificata dalla Direzione.</p>
<h2>7. Gestione delle vulnerabilità e delle patch</h2>
<h3>7.1 Identificazione</h3>
<ul>
<li>Simon Fattori monitora con continuità le fonti di vulnerabilità: bollettini dei fornitori cloud, avvisi CSIRT/ACN, advisory delle librerie/dipendenze usate dai prodotti, scansioni periodiche e avvisi automatici delle piattaforme (A.5.7, A.8.16).</li>
<li>Si mantiene un inventario aggiornato dei componenti software e delle dipendenze per correlare rapidamente le vulnerabilità ai sistemi interessati.</li>
</ul>
<h3>7.2 Valutazione, prioritizzazione e tempi di rimedio</h3>
<ul>
<li>Ogni vulnerabilità è valutata per <strong>gravità</strong> (es. punteggio CVSS), <strong>esposizione</strong> e <strong>impatto</strong> su dati e servizi, e registrata se rilevante nel <strong>modulo "Rischi"</strong>.</li>
<li>Si assegna una priorità con relativi <strong>tempi target di rimedio</strong>, ad esempio: critiche <strong>entro 48–72 ore</strong>, alte <strong>entro 7 giorni</strong>, medie <strong>entro 30 giorni</strong>, basse alla successiva finestra pianificata. <em>[I valori esatti sono da confermare nel piano di trattamento del rischio e negli SLA interni.]</em></li>
</ul>
<h3>7.3 Rimedio (applicazione delle patch)</h3>
<ul>
<li>L'applicazione delle patch segue la gestione dei cambiamenti (test in ambiente separato e piano di rollback); per le vulnerabilità critiche si attiva il percorso di cambiamento di emergenza. Dove la patch non è immediatamente disponibile si adottano <strong>misure compensative</strong> (restrizione accessi, isolamento, disattivazione della funzione vulnerabile).</li>
<li>Aggiornamenti automatici abilitati su sistema operativo (Windows Update, aggiornamenti macOS, gestore pacchetti Linux) e browser dei portatili; applicazioni (editor, strumenti di sviluppo, client VPN, antimalware, estensioni browser) tenute aggiornate; software non supportato rimosso o sostituito; riavvio del portatile almeno settimanale e quando richiesto.</li>
<li>Per i prodotti SaaS e on-premise si monitorano le dipendenze (librerie, container, runtime), si aggiornano le immagini e si ricostruiscono gli artefatti; sui servizi cloud (Aruba, Hetzner) si applicano gli aggiornamenti delle console e dei servizi gestiti annotando le finestre di manutenzione.</li>
<li>Per i prodotti on-premise, l'azienda rilascia la patch e <strong>informa tempestivamente i clienti impattati</strong> con le istruzioni di aggiornamento (obblighi contrattuali di supply chain), verificandola in ambiente di prova prima del rilascio per evitare regressioni.</li>
</ul>
<h3>7.4 Verifica, chiusura e registrazione</h3>
<ul>
<li>Si verifica l'effettiva risoluzione (riscansione/test) e si chiude la vulnerabilità; gli scostamenti dai tempi target sono registrati come <strong>non conformità</strong> con azione correttiva.</li>
<li>Patch, date e versioni sono tracciate nel registro aggiornamenti/asset; nessuna vulnerabilità critica nota resta non gestita oltre lo SLA interno.</li>
<li>Se una vulnerabilità è stata sfruttata, si attiva la Procedura di Gestione degli Incidenti. In caso di dubbio su un aggiornamento fallito o su una vulnerabilità segnalata si contatta il Resp. IT/Sicurezza Simon Fattori, senza disinstallare patch di sicurezza per "far funzionare" un'applicazione.</li>
</ul>
<h2>8. Logging e monitoraggio</h2>
<p>In assenza di server in sede, i log dei servizi cloud sono la principale fonte di visibilità. Il logging è attivo su tutti i servizi critici (posta, console Aruba/Hetzner, repository, piattaforme AI, password manager, prodotti SaaS), con orari di riferimento coerenti (Europe/Rome o UTC documentato) per correlare gli eventi.</p>
<ol>
<li><strong>Verifica giornaliera rapida.</strong> Controllo degli avvisi di sicurezza automatici dei fornitori (nuovi accessi, login da nuovi dispositivi/paesi, blocchi MFA); ogni avviso è da valutare, non da ignorare.</li>
<li><strong>Revisione settimanale degli accessi.</strong> Esame dei log di autenticazione dei servizi critici cercando login falliti ripetuti, accessi da località/orari inusuali, accessi notturni non giustificati, sessioni o dispositivi non riconosciuti.</li>
<li><strong>Controllo dei cambiamenti privilegiati.</strong> Verifica di modifiche a ruoli/permessi, creazione di account, generazione/rotazione di chiavi API e token; ogni cambiamento deve essere riconducibile a una richiesta legittima.</li>
<li><strong>Log dei prodotti SaaS.</strong> Controllo degli audit log applicativi per accessi anomali ai dati dei clienti ed errori ricorrenti.</li>
<li><strong>Correlazione degli eventi.</strong> A fronte di un'anomalia si ricostruisce la sequenza tra servizi diversi usando orari allineati, per distinguere un evento singolo da una catena.</li>
<li><strong>Conservazione e protezione dei log.</strong> I registri non sono modificabili dagli utenti finali e sono conservati per un periodo adeguato secondo le impostazioni dei fornitori <em>[da verificare rispetto alla policy di retention interna]</em>.</li>
<li><strong>Documentazione della revisione.</strong> Si annotano data, servizi controllati, anomalie rilevate ed esito (chiusa come falso positivo o escalata a incidente) nel registro delle revisioni.</li>
</ol>
<p>In presenza di accesso non autorizzato confermato o sospetto, chiave compromessa o attività anomale sui dati dei clienti, si avvia la Procedura di Gestione degli Incidenti (referente: Simon Fattori); per incidenti che possono ricadere sotto NIS2 si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese).</p>
<h2>9. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li><strong>A.8.25</strong> – Ciclo di vita di sviluppo sicuro; <strong>A.8.26</strong> – requisiti di sicurezza delle applicazioni; <strong>A.8.27 / A.8.28</strong> – architettura e codifica sicura; <strong>A.8.29</strong> – test di sicurezza in sviluppo e accettazione; <strong>A.8.30</strong> – sviluppo affidato all'esterno; <strong>A.8.31</strong> – separazione ambienti; <strong>A.8.32 / A.8.33</strong> – gestione dei cambiamenti e dati di test.</li>
<li><strong>A.8.8</strong> – gestione delle vulnerabilità tecniche; <strong>A.8.7</strong> – protezione dai malware; <strong>A.8.9</strong> – gestione della configurazione; <strong>A.5.7</strong> – threat intelligence; <strong>A.8.16</strong> – monitoraggio; <strong>A.8.1</strong> – endpoint.</li>
<li><strong>A.8.15</strong> – logging; <strong>A.5.17</strong> – informazioni di autenticazione.</li>
<li>Controlli cloud di ISO/IEC 27017 e ISO/IEC 27018 per gli aspetti cloud e PII.</li>
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: sicurezza nell'acquisizione, sviluppo e manutenzione dei sistemi, gestione delle modifiche e delle vulnerabilità. Una vulnerabilità sfruttata o un cambiamento fallito che causino un incidente significativo attivano gli obblighi di notifica dell'<strong>art. 25</strong> (early warning 24 ore, notifica completa 72 ore, relazione finale 1 mese).</li>
</ul>
<h2>10. Registrazioni ed evidenze</h2>
<ul>
<li>Requisiti di sicurezza documentati per progetto/prodotto; registri delle code review e dei test di sicurezza; inventario delle dipendenze.</li>
<li>Richieste di cambiamento (RFC) e autorizzazioni; esiti di test e rilasci; note di rilascio per i clienti on-premise; documentazione dei cambiamenti di emergenza e relativa ratifica.</li>
<li>Registro delle vulnerabilità con gravità, priorità e tempi di rimedio; report delle scansioni e degli aggiornamenti applicati; registro aggiornamenti/asset; comunicazioni di patch ai clienti on-premise.</li>
<li>Registro delle revisioni dei log/accessi con anomalie ed esiti; non conformità e azioni correttive per cambiamenti falliti o ritardi di rimedio.</li>
</ul>
<h2>11. Riesame e versionamento</h2>
<p>La procedura è riesaminata almeno annualmente o a fronte di cambiamenti tecnologici significativi, nuove minacce, modifiche del processo di rilascio o degli strumenti di scansione, cambiamenti del parco software o requisiti contrattuali dei clienti. Versione corrente: <strong>2.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
+106
View File
@@ -0,0 +1,106 @@
<!--META|doc_type=procedura_governance_sgsi|title=Procedura di Governance del SGSI (documenti, audit, riesame, NC, competenze)|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>Definire le modalità con cui <strong>Nuova Agile Technology srl</strong> governa il proprio SGSI nei processi di sistema: controllo dei documenti e delle registrazioni, audit interni, riesame della direzione, gestione delle non conformità e delle azioni correttive, gestione delle competenze e consapevolezza del personale. La procedura assicura che il sistema sia conforme alla <strong>ISO/IEC 27001:2022</strong>, efficacemente attuato, mantenuto e migliorato nel tempo, attuando in particolare le cl. 7.2, 7.3, 7.5, 9.2, 9.3, 10.1 e 10.2.</p>
<h2>2. Ambito</h2>
<p>Si applica a tutto il SGSI e alle sue componenti: documentazione (politiche, procedure di sistema, istruzioni operative, SoA, registri) e registrazioni di processo (verbali, report di audit, valutazioni dei rischi, registri formazione, NC/CAPA); governance del SGSI, gestione dei rischi e degli incidenti, sicurezza del cloud (Aruba IT, Hetzner DE, piattaforme AI), gestione delle PII nel SaaS, sicurezza dei portatili, formazione e rapporti con i fornitori. Data la natura <em>cloud-only</em> dell'azienda (~12 persone: 9 dipendenti, 2 collaboratori P.IVA e la Direzione) non esiste documentazione cartacea controllata di default; i documenti esterni sono archiviati nel cloud aziendale (Aruba IT).</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 – cl. 7.2 (competenza), 7.3 (consapevolezza), 7.5.1/7.5.2/7.5.3 (informazioni documentate), 9.2.1/9.2.2 (audit interni), 9.3.1/9.3.2/9.3.3 (riesame della direzione), 10.1 (miglioramento continuo), 10.2 (non conformità e azioni correttive).</li>
<li>Controlli Annex A: A.5.1 (politiche), A.5.4 (responsabilità della direzione), A.5.24–A.5.28 (gestione e apprendimento dagli incidenti, evidenze), A.5.33 (protezione delle registrazioni), A.5.34 (privacy e PII), A.5.35 (riesame indipendente), A.5.36 (conformità a politiche e standard), A.5.37 (procedure operative documentate), A.6.1/A.6.2/A.6.3/A.6.5 (ciclo di vita del rapporto, awareness e formazione), A.8.7 (protezione dai malware), A.8.10/A.8.11/A.8.12 (cancellazione, mascheramento, prevenzione fuga di dati), A.5.17 (autenticazione), A.8.28 (codifica sicura).</li>
<li>ISO 19011 (linee guida per gli audit dei sistemi di gestione); ISO/IEC 27018 (conservazione/cancellazione delle PII nel cloud, consapevolezza del personale che tratta PII); GDPR per i periodi di conservazione dei dati personali.</li>
<li>D.Lgs. 138/2024 art. 23 (obblighi di governance e responsabilità degli organi di amministrazione, formazione della direzione), art. 24 (efficacia delle misure, igiene informatica e formazione), art. 25 (notifica degli incidenti significativi) [da verificare per i soggetti rientranti].</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente (Silvia Garretto)</td><td>Approva politiche e procedure di sistema (autorità di approvazione); presiede il riesame e assume le decisioni; assicura le risorse per audit, azioni e formazione; partecipa alla formazione di governance NIS2.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Gestisce l'elenco master dei documenti, versioni e stato; predispone il programma di audit e nomina gli auditor; convoca il riesame, prepara gli input e redige il verbale; registra/classifica le NC e ne verifica la chiusura; definisce i fabbisogni di competenza e il piano formativo.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Garantisce backup, controllo accessi e cifratura degli archivi documentali cloud; fornisce evidenze sulle aree auditate; riporta su incidenti, vulnerabilità e prestazioni dei controlli tecnici; attua le correzioni tecniche; supporta la formazione tecnica.</td></tr>
<tr><td>Auditor (interno indipendente o esterno)</td><td>Conduce gli audit, raccoglie le evidenze, formula i rilievi e redige il report.</td></tr>
<tr><td>Autori / Responsabili di processo</td><td>Redigono e aggiornano i documenti di competenza; segnalano e gestiscono le NC; concordano e attuano le azioni correttive.</td></tr>
<tr><td>Personale (dipendenti e collaboratori)</td><td>Partecipa alle attività formative assegnate e mantiene aggiornata la propria consapevolezza; segnala anomalie e NC.</td></tr>
<tr><td>DPO esterno</td><td>Indica i tempi di conservazione/cancellazione delle registrazioni con dati personali; è coinvolto nelle NC e nei riesami su tematiche PII e relativi obblighi di notifica.</td></tr>
</table>
<h2>5. Controllo dei documenti e delle registrazioni (cl. 7.5)</h2>
<ol>
<li><strong>Identificazione.</strong> Ogni documento riceve titolo, codice/slug, numero di versione, data e stato (bozza/in revisione/approvato/obsoleto).</li>
<li><strong>Redazione e aggiornamento</strong> (cl. 7.5.2): l'autore redige in formato e struttura standard; le modifiche sono tracciate con la cronologia delle versioni.</li>
<li><strong>Verifica e approvazione.</strong> Il RSGSI verifica adeguatezza e coerenza; la Direzione (o delegato) approva. Un documento è pubblicabile solo nello stato "approvato".</li>
<li><strong>Distribuzione e disponibilità</strong> (cl. 7.5.3): la versione vigente è resa disponibile tramite la piattaforma/archivio cloud, con accessi profilati per ruolo.</li>
<li><strong>Controllo delle modifiche.</strong> Ogni revisione genera una nuova versione; la precedente è marcata "obsoleta" e conservata per tracciabilità, non più utilizzabile come riferimento operativo.</li>
<li><strong>Documenti di origine esterna</strong> (leggi, norme, contratti cloud, manuali provider): identificati e con distribuzione controllata.</li>
<li><strong>Protezione e conservazione delle registrazioni.</strong> Le evidenze sono protette da perdita, alterazione e accesso non autorizzato mediante backup, cifratura e controllo accessi del provider cloud; i periodi di conservazione rispettano legge e GDPR; la cancellazione avviene in modo sicuro (A.8.10).</li>
</ol>
<h2>6. Audit interni (cl. 9.2)</h2>
<p>Data la dimensione del team (~12 persone), gli audit sono di norma condotti dal RSGSI o da risorsa interna indipendente dall'area auditata; per oggettività sulle aree gestite dallo stesso RSGSI è possibile ricorrere a un auditor esterno.</p>
<ol>
<li><strong>Programmazione</strong> (input: esiti audit precedenti, importanza dei processi, esiti valutazione rischi). Si pianifica almeno un ciclo di audit interno all'anno che copra tutte le clausole e i controlli applicabili; le aree a rischio più elevato possono essere auditate più di frequente.</li>
<li><strong>Pianificazione del singolo audit.</strong> Si definiscono obiettivi, criteri (la norma e i documenti del SGSI), ambito, date e auditor; il piano è registrato nel <strong>modulo "Audit interni"</strong>.</li>
<li><strong>Conduzione.</strong> L'auditor verifica documentazione, configurazioni cloud, registri e prassi operative; raccoglie evidenze oggettive e annota osservazioni e non conformità.</li>
<li><strong>Classificazione dei rilievi</strong> come Non Conformità (maggiore/minore) oppure Osservazione/Opportunità di miglioramento.</li>
<li><strong>Rendicontazione.</strong> L'auditor redige il report nel modulo "Audit interni" (esito, rilievi, raccomandazioni), condiviso con i responsabili di area e con la Direzione.</li>
<li><strong>Gestione dei rilievi e follow-up.</strong> Ogni Non Conformità apre una scheda nel modulo NCR/CAPA; il RSGSI ne verifica la chiusura. Esiti e stato delle azioni alimentano il successivo Riesame della Direzione.</li>
</ol>
<h2>7. Riesame della direzione (cl. 9.3)</h2>
<ol>
<li><strong>Convocazione e raccolta input.</strong> Il RSGSI raccoglie dai moduli della piattaforma gli elementi richiesti dalla cl. 9.3.2: stato delle azioni dai riesami precedenti; cambiamenti del contesto e delle parti interessate; esiti della valutazione e del trattamento dei rischi; prestazioni di sicurezza (incidenti, non conformità, monitoraggio e misurazioni); esiti degli audit interni; raggiungimento degli obiettivi di sicurezza; feedback delle parti interessate; opportunità di miglioramento.</li>
<li><strong>Conduzione della riunione.</strong> La Direzione esamina ciascun input e valuta idoneità, adeguatezza ed efficacia del SGSI, discutendo eventuali necessità di cambiamento.</li>
<li><strong>Decisioni e output</strong> (cl. 9.3.3): opportunità di miglioramento, cambiamenti al SGSI (politiche, obiettivi, controlli), fabbisogni di risorse e azioni con responsabili e scadenze.</li>
<li><strong>Verbalizzazione.</strong> Il RSGSI redige il verbale nel <strong>modulo "Riesame della Direzione"</strong>, riportando input considerati, decisioni e azioni assegnate.</li>
<li><strong>Attuazione e monitoraggio.</strong> Le azioni derivanti sono tracciate (come schede NCR/CAPA o voci del piano di trattamento rischi) e il loro stato è verificato al riesame successivo.</li>
</ol>
<h2>8. Non conformità e azioni correttive (cl. 10.1 e 10.2)</h2>
<ol>
<li><strong>Rilevazione e registrazione.</strong> La NC è aperta nel <strong>modulo "Non conformità / Azioni correttive (NCR/CAPA)"</strong> con descrizione, origine, data e responsabile (rilievi di audit, scostamenti dalla norma o dai documenti, incidenti, segnalazioni di clienti/fornitori, malfunzionamenti dei controlli cloud, esiti di monitoraggio).</li>
<li><strong>Correzione immediata</strong> (cl. 10.2 a): si contiene e corregge la NC e se ne gestiscono le conseguenze (es. revoca di un accesso, ripristino di una configurazione cloud, contenimento di un incidente).</li>
<li><strong>Valutazione della necessità di azione correttiva</strong> (cl. 10.2 b): per NC minori e isolate può bastare la correzione; per NC ricorrenti o significative si avvia un'azione correttiva.</li>
<li><strong>Analisi delle cause</strong> (root cause): si individuano le cause profonde (es. assenza di un controllo, configurazione errata, mancanza di competenza) con tecnica adeguata (5 perché / causa-effetto).</li>
<li><strong>Definizione e attuazione della CAPA</strong> (cl. 10.2 c/d): azioni, responsabili e scadenze registrate come azioni collegate alla scheda, e attuate.</li>
<li><strong>Verifica dell'efficacia</strong> (cl. 10.2 d/e): il RSGSI verifica che la causa sia eliminata e che la NC non si ripresenti; aggiorna, se necessario, rischi e SoA.</li>
<li><strong>Chiusura e comunicazione</strong> (cl. 10.2 f/g): la scheda è chiusa con evidenza dell'efficacia; le NC significative sono portate al Riesame della Direzione. Per incidenti che configurano violazioni di dati o incidenti significativi NIS2 si attivano i relativi flussi di notifica (art. 25 D.Lgs. 138/2024: pre-allarme 24h, notifica 72h, relazione finale 1 mese).</li>
</ol>
<h2>9. Competenze, formazione e consapevolezza (cl. 7.2 e 7.3)</h2>
<ol>
<li><strong>Definizione dei fabbisogni.</strong> Per ciascun ruolo si individuano le competenze richieste (cl. 7.2 a/b) e il divario rispetto a quelle possedute, considerando le specificità <em>cloud-only</em>: sicurezza dei servizi cloud, uso sicuro dei portatili, sviluppo sicuro (SaaS e on-premise), protezione delle PII, uso responsabile delle piattaforme AI.</li>
<li><strong>Pianificazione formativa.</strong> Il RSGSI definisce il piano annuale con priorità, destinatari e scadenze, includendo almeno: awareness generale per tutti, formazione tecnica per IT/sviluppo, sessione di governance per la Direzione, modulo sulla protezione delle PII.</li>
<li><strong>Assegnazione ed erogazione.</strong> I corsi sono assegnati tramite il <strong>modulo "Formazione"</strong> (e-learning, aula o on-the-job).</li>
<li><strong>Consapevolezza continua</strong> (cl. 7.3): comunicazioni periodiche su politiche, minacce attuali e responsabilità individuali; sessione di awareness obbligatoria al nuovo ingresso, prima dell'accesso ai sistemi.</li>
<li><strong>Registrazione, verifica ed efficacia.</strong> Stato, data di completamento ed esiti di test/quiz sono registrati nel modulo "Formazione"; il RSGSI verifica tasso di completamento e adeguatezza delle competenze (es. riduzione di incidenti da errore umano); le carenze diventano azioni di miglioramento o NC.</li>
<li><strong>Ingressi e cessazioni.</strong> All'assunzione si definiscono i requisiti di competenza e consapevolezza; alla cessazione si rammentano gli obblighi di riservatezza residui (A.6.5).</li>
</ol>
<h3>9.1 Consapevolezza anti-phishing (istruzione per il personale)</h3>
<p>Il phishing è la porta d'ingresso più comune per gli attacchi; ogni persona deve saperlo riconoscere (email, SMS/smishing, telefono/vishing) e segnalarlo rapidamente. Regole essenziali: fermarsi prima di agire di fronte a messaggi che creano urgenza/paura/curiosità; verificare il mittente reale e diffidare di domini simili; non cliccare d'impulso (controllare l'URL, nel dubbio digitare l'indirizzo nel browser); non aprire allegati inattesi (eseguibili, archivi, "abilita macro"); <strong>mai inserire credenziali o codici MFA da un link ricevuto</strong>; verificare su canale diverso e noto le richieste anomale del "capo" o di fornitori (pagamenti, cambio IBAN, invio credenziali); nessuno deve farsi leggere o inoltrare un codice MFA. Prerequisito: MFA attivo su posta e servizi critici. In caso di messaggio sospetto si inoltra l'originale (senza cliccare) al Resp. IT/Sicurezza <strong>Simon Fattori</strong>; se si è già cliccato o inserito credenziali, avvisare subito Simon Fattori, cambiare la password interessata e, se possibile, scollegare il dispositivo dalla rete. Una segnalazione tempestiva può evitare un incidente; un click confessato non è una colpa. Integrare con simulazioni di phishing periodiche.</p>
<h2>10. Moduli della piattaforma come repository delle evidenze</h2>
<p>Le registrazioni di processo risiedono nei rispettivi moduli della piattaforma, che fungono da repository controllato delle evidenze: <strong>Audit interni</strong> (programma e piani di audit, report e rilievi), <strong>Riesame della Direzione</strong> (verbali, input considerati, decisioni e azioni), <strong>NCR/CAPA</strong> (schede di non conformità, analisi delle cause, azioni correttive con responsabili/scadenze/verifica di efficacia), <strong>Rischi</strong> (valutazioni e piano di trattamento), <strong>Formazione</strong> (matrice competenze, piano formativo, assegnazioni e attestati di completamento). L'<strong>elenco master dei documenti</strong> con stato e versione è mantenuto dal RSGSI nella piattaforma/archivio cloud.</p>
<h2>11. Controlli ISO/clausole collegati e riferimenti NIS2</h2>
<ul>
<li><strong>cl. 7.5.1/7.5.2/7.5.3</strong>; A.5.33, A.5.37, A.8.10/A.8.11/A.8.12; requisiti ISO/IEC 27018 su conservazione, restituzione e cancellazione delle PII.</li>
<li><strong>cl. 9.2.1/9.2.2</strong>; A.5.35 (riesame indipendente), A.5.36 (conformità); ISO 19011.</li>
<li><strong>cl. 9.3.1/9.3.2/9.3.3</strong>; A.5.1, A.5.4; collegamento con cl. 9.2 (audit), cl. 6.1/8.2 (rischi), cl. 10.1/10.2 (miglioramento) e cl. 5 (leadership).</li>
<li><strong>cl. 10.1 e 10.2 (a–g)</strong>; A.5.24 (preparazione gestione incidenti), A.5.27 (apprendimento dagli incidenti), A.5.28 (raccolta evidenze).</li>
<li><strong>cl. 7.2/7.3</strong>; A.6.3 (formazione e awareness), A.6.1/A.6.2/A.6.5 (ciclo di vita del rapporto), A.8.28 (codifica sicura), A.5.17 (autenticazione), A.8.7 (protezione dai malware).</li>
<li><strong>NIS2 – D.Lgs. 138/2024</strong>: art. 23 (governance e formazione della direzione), art. 24 (efficacia delle misure, igiene informatica e formazione), art. 25 (notifica incidenti).</li>
</ul>
<h2>12. Registrazioni ed evidenze</h2>
<ul>
<li>Elenco master dei documenti del SGSI con stato e versione; cronologia delle versioni di ciascun documento (versione + data + autore).</li>
<li>Programma di audit annuale, piani e report di audit con rilievi ed evidenze; evidenza di indipendenza/competenza degli auditor.</li>
<li>Verbale di riesame della direzione con input, decisioni e azioni; pacchetto di input estratto dai moduli operativi.</li>
<li>Schede NC con analisi delle cause e azioni correttive (responsabili, scadenze, stato, verifica di efficacia) e relativi collegamenti ad audit, rischi e incidenti.</li>
<li>Matrice delle competenze per ruolo e piano formativo annuale; assegnazioni e attestati di completamento; evidenze di awareness di onboarding e comunicazioni periodiche.</li>
</ul>
<h2>13. Riesame e versionamento</h2>
<p>I processi di governance del SGSI e i relativi documenti sono riesaminati <strong>almeno annualmente</strong> e ad ogni cambiamento organizzativo, tecnologico o normativo rilevante (nuovi servizi/provider, incidenti gravi, nuove minacce, nuovi ruoli). Ogni nuova edizione incrementa la versione (modifiche minori → incremento minore; modifiche sostanziali → incremento maggiore). Versione corrente: <strong>2.0</strong>, approvata dalla Direzione secondo la presente procedura di controllo dei documenti.</p>
+44
View File
@@ -0,0 +1,44 @@
<!--META|doc_type=politica_sicurezza_informazioni|title=Politica Generale per la Sicurezza delle Informazioni|status=approved|version=2.0-->
<h2>1. Scopo e ambito</h2>
<p>La presente Politica esprime l'impegno formale dell'Alta Direzione di <strong>Nuova Agile Technology srl</strong> verso la protezione di riservatezza, integrità e disponibilità delle informazioni proprie e di quelle affidate dai clienti, inclusi i dati personali. È il documento di indirizzo del Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) conforme a <strong>ISO/IEC 27001:2022</strong> e alle linee guida <strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> per i servizi cloud e i dati personali. Si applica a tutto il personale (dipendenti e collaboratori a P.IVA), a tutte le informazioni trattate, ai prodotti software in licenza d'uso installati presso i clienti e ai servizi SaaS multi-tenant erogati in cloud. L'infrastruttura è interamente cloud (<strong>Aruba</strong>, Italia; <strong>Hetzner</strong>, Germania-UE) con piattaforme AI via API; non esistono server gestiti in sede e le postazioni sono solo PC portatili cifrati.</p>
<h2>2. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022, 27017:2015, 27018:2019</li>
<li>Regolamento (UE) 2016/679 (GDPR)</li>
<li>Direttiva (UE) 2022/2555 (NIS2) e D.Lgs. 138/2024</li>
<li>Manuale SGSI, Dichiarazione di Applicabilità (SoA)</li>
</ul>
<h2>3. Ruoli e responsabilità</h2>
<p>La <strong>Presidente Silvia Garretto</strong> (Alta Direzione) approva e sostiene la Politica. Il <strong>Responsabile SGSI Massimo Tagliavini</strong> ne cura attuazione e monitoraggio. Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> presidia le misure tecniche. Il <strong>DPO (consulente esterno, da nominare)</strong> supervisiona la conformità GDPR. Ogni membro del personale è responsabile del rispetto della Politica nell'ambito delle proprie attività.</p>
<h2>4. Principi e impegni</h2>
<p>L'Alta Direzione si impegna a:</p>
<ul>
<li><strong>Riservatezza</strong>: garantire l'accesso alle informazioni solo a chi è autorizzato, applicando il minimo privilegio e la separazione dei tenant nel SaaS.</li>
<li><strong>Integrità</strong>: assicurare accuratezza e completezza di informazioni e sistemi, con controlli su modifiche e sviluppo software sicuro.</li>
<li><strong>Disponibilità</strong>: garantire l'accesso ai servizi secondo i livelli concordati, mediante backup, continuità operativa e gestione resiliente del cloud.</li>
<li><strong>Conformità</strong>: rispettare gli obblighi legali e contrattuali, in particolare GDPR e i requisiti NIS2 ove applicabili.</li>
<li><strong>Gestione del rischio</strong>: identificare, valutare e trattare i rischi in modo sistematico, con attenzione alla supply chain cloud e AI.</li>
<li><strong>Consapevolezza</strong>: formare e sensibilizzare il personale sui temi di sicurezza.</li>
<li><strong>Miglioramento continuo</strong>: riesaminare periodicamente il SGSI e adottare azioni correttive e preventive.</li>
<li><strong>Gestione degli incidenti</strong>: rilevare, gestire e segnalare tempestivamente gli incidenti di sicurezza e le violazioni di dati personali, nel rispetto degli obblighi di notifica al CSIRT (NIS2: 24h/72h/1 mese).</li>
</ul>
<p>L'Azienda adotta una <strong>politica di tolleranza zero</strong> verso comportamenti che mettano deliberatamente a rischio la sicurezza delle informazioni.</p>
<h2>5. Obiettivi di sicurezza</h2>
<p>Gli obiettivi misurabili del SGSI sono definiti annualmente dalla Direzione e includono, a titolo esemplificativo: copertura della formazione del personale, tempi di risposta agli incidenti, percentuale di rischi trattati entro i termini, livello di conformità dei fornitori cloud. Sono monitorati e riesaminati nel riesame di direzione.</p>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<p>Controlli Annex A pertinenti: <strong>A.5.1</strong> (Politiche per la sicurezza delle informazioni), <strong>A.5.2</strong> (Ruoli e responsabilità), <strong>A.5.4</strong> (Responsabilità della direzione), <strong>A.5.31</strong> (Requisiti legali e contrattuali), <strong>A.5.36</strong> (Conformità alle politiche). Riferimenti NIS2 (D.Lgs. 138/2024): <strong>art.23</strong> (governance e ruolo degli organi di gestione), <strong>art.24</strong> (misure di gestione del rischio), <strong>art.25</strong> (obblighi di notifica). Le ISO costituiscono buone prassi, non obblighi di legge.</p>
<h2>7. Caveat NIS2</h2>
<p>L'Azienda si autoclassifica in via <strong>preliminare</strong> come soggetto NIS2 "<strong>IMPORTANTE</strong>", pur essendo <strong>sotto le soglie dimensionali ordinarie</strong>: la qualifica va <strong>confermata da Direzione e Legale</strong>, ed eventualmente da ACN, in base a fatturato e categoria di attività. <strong>[DA VERIFICARE]</strong> in sede di registrazione presso ACN.</p>
<h2>8. Comunicazione e applicazione</h2>
<p>La Politica è comunicata a tutto il personale e accessibile in qualsiasi momento. La sua violazione può comportare provvedimenti disciplinari secondo le norme contrattuali e di legge vigenti. Evidenze: documento firmato dalla Presidente, evidenza di comunicazione al personale, verbali di riesame.</p>
<h2>9. Riesame e versionamento</h2>
<p>Approvata dall'Alta Direzione. Riesame almeno annuale o a fronte di modifiche significative del contesto.</p>
+110
View File
@@ -0,0 +1,110 @@
<!--META|doc_type=politica_controllo_accessi|title=Politica e Procedura di Controllo degli Accessi (incl. MFA, onboarding/offboarding)|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>Definire le regole e i passi operativi con cui <strong>Nuova Agile Technology srl</strong> autorizza, concede, modifica, riesamina e revoca gli accessi logici alle informazioni, ai sistemi cloud (Aruba, Hetzner), alle piattaforme AI, agli ambienti di sviluppo e ai servizi SaaS multi-tenant, garantendo che ogni utente disponga esclusivamente dei privilegi necessari al proprio ruolo (<em>least privilege</em>) e che ogni accesso sia protetto da <strong>autenticazione a più fattori (MFA)</strong>. Il documento copre principi, l'intero ciclo di vita dell'identità (onboarding, variazioni di ruolo, riesame periodico, offboarding) e le istruzioni operative MFA.</p>
<h2>2. Ambito</h2>
<p>Si applica a tutti gli accessi logici di dipendenti, collaboratori a P.IVA, utenze tecniche/di servizio e — ove pertinente — di clienti e fornitori. Riguarda: account dei provider cloud (Aruba IT, Hetzner DE), repository di codice, ambienti di produzione e staging, console di amministrazione e pannelli SaaS multi-tenant, posta elettronica, strumenti di collaborazione, database, gestore di password e gestore segreti aziendali, chiavi API (incluse quelle dei provider AI) e i <strong>PC portatili cifrati</strong> in dotazione. <strong>Non esistono server in sede</strong>: non sono previsti accessi fisici a sale macchine, la cui sicurezza è ereditata dai fornitori cloud secondo il modello di responsabilità condivisa.</p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – Annex A: A.5.15 (Controllo degli accessi), A.5.16 (Gestione delle identità), A.5.17 (Informazioni di autenticazione), A.5.18 (Diritti di accesso), A.6.3 (Consapevolezza in fase di ingresso), A.8.1 (Dispositivi endpoint), A.8.2 (Diritti di accesso privilegiati), A.8.3 (Restrizione dell'accesso alle informazioni), A.8.5 (Autenticazione sicura).</li>
<li><strong>ISO/IEC 27017:2015</strong> (gestione accessi nei servizi cloud) e <strong>27018:2019</strong> (accesso ai dati personali nel cloud).</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (recepimento Direttiva (UE) 2022/2555 – NIS2): art. 24 (misure di gestione del rischio).</li>
<li>Politica per la Sicurezza delle Informazioni; Politica di Crittografia; Manuale SGSI.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva il documento, autorizza i profili di accesso privilegiato critico e le deroghe</td></tr>
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Approva le matrici di accesso (RBAC), coordina i riesami periodici e mantiene le evidenze</td></tr>
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Crea/modifica/revoca le utenze, configura l'MFA, custodisce le credenziali, esegue le verifiche tecniche</td></tr>
<tr><td>Responsabile dell'unità richiedente</td><td><em>variabile</em></td><td>Richiede e giustifica gli accessi del proprio personale</td></tr>
<tr><td>DPO</td><td><em>Consulente esterno</em></td><td>Consultato per gli accessi che coinvolgono dati personali dei clienti</td></tr>
</table>
<h2>5. Principi di controllo degli accessi</h2>
<ul>
<li><strong>Minimo privilegio</strong>: a ogni utente sono concessi solo i diritti necessari al ruolo.</li>
<li><strong>Need-to-know</strong>: l'accesso alle informazioni è limitato a chi ne ha effettiva necessità.</li>
<li><strong>Segregazione dei compiti</strong>: ove possibile, separazione tra chi sviluppa, chi rilascia in produzione e chi amministra.</li>
<li><strong>Default deny</strong>: in assenza di autorizzazione esplicita, l'accesso è negato.</li>
<li><strong>Account nominativi</strong>: nessuna condivisione di credenziali; ogni account è personale.</li>
</ul>
<h2>6. Autenticazione e MFA</h2>
<p>L'<strong>MFA è obbligatoria</strong> per ogni servizio critico: console cloud Aruba/Hetzner, repository di codice, accessi amministrativi al SaaS, account di posta, SSO, piattaforme AI, password manager e gestore segreti. La sola password non è mai sufficiente. Le password devono essere robuste secondo standard aziendale ed è obbligatorio l'uso di un <strong>password manager</strong> approvato.</p>
<h3>6.1 Istruzione operativa MFA</h3>
<ol>
<li><strong>Scegli il secondo fattore corretto.</strong> Ordine di preferenza: (1) chiave di sicurezza FIDO2/passkey, (2) app di autenticazione (codici TOTP o notifiche push), (3) come ultima scelta SMS. <strong>Evita gli SMS</strong> dove esiste un'alternativa.</li>
<li><strong>Attiva l'MFA su ogni servizio critico.</strong> Impostazioni di sicurezza dell'account → "Autenticazione a due fattori / MFA" → abilita, servizio per servizio (posta, repository, console Aruba e Hetzner, piattaforme AI, password manager).</li>
<li><strong>Registra il dispositivo.</strong> Inquadra il QR code con l'app di autenticazione o registra la chiave FIDO2; conferma con il primo codice generato.</li>
<li><strong>Salva i codici di recupero</strong> monouso <strong>solo</strong> nel password manager aziendale, mai in chiaro su file, email o foglietti.</li>
<li><strong>Imposta un secondo metodo</strong> (seconda passkey o chiave fisica di backup) per non restare bloccato in caso di smarrimento dello smartphone; sui servizi più critici (posta e console cloud) devono risultare registrati almeno due metodi.</li>
<li><strong>Proteggi il password manager</strong>: master password lunga, unica, mai riutilizzata, con MFA attivo.</li>
<li><strong>Account/segreti di servizio</strong> nel gestore segreti aziendale, mai scambiati via chat o email.</li>
</ol>
<h3>6.2 Verifiche di esito MFA</h3>
<ul>
<li>Un login di prova deve richiedere il secondo fattore; nessun servizio critico accessibile con la sola password.</li>
<li>I codici di recupero sono presenti nel password manager per ogni servizio.</li>
</ul>
<p>In caso di smarrimento smartphone, mancata ricezione codici, esaurimento dei codici di recupero o sospetto accesso non autorizzato, contattare <strong>subito</strong> il Resp. IT/Sicurezza <strong>Simon Fattori</strong> per il reset controllato. È <strong>vietato disattivare l'MFA</strong> per "comodità".</p>
<h2>7. Accessi privilegiati e chiavi API</h2>
<ul>
<li>Gli account amministrativi sono ridotti al minimo, nominativi, monitorati e soggetti a logging e revisione.</li>
<li>Le <strong>chiavi API</strong> (cloud, AI, integrazioni) sono custodite in un vault cifrato, mai inserite in chiaro nel codice o committate nei repository, e ruotate periodicamente o in caso di sospetta compromissione.</li>
</ul>
<h2>8. Accesso nel SaaS multi-tenant</h2>
<ul>
<li>Isolamento logico dei tenant: ogni cliente accede esclusivamente ai propri dati.</li>
<li>I dati personali dei clienti sono accessibili al personale dell'Azienda solo per finalità di erogazione e supporto, con tracciabilità (rif. ISO 27018).</li>
</ul>
<h2>9. Ciclo di vita dell'identità</h2>
<h3>9.1 Onboarding (ingresso di un nuovo utente)</h3>
<ol>
<li>Il responsabile (o la Direzione) invia a Simon Fattori la richiesta formale con ruolo, profilo di accesso, data di inizio (ed eventuale fine, per i collaboratori esterni).</li>
<li>Simon Fattori crea l'identità (account aziendale: posta + SSO se presente) sulle sole piattaforme necessarie al ruolo, applicando il <em>least privilege</em> e profili predefiniti per mansione (RBAC); per i collaboratori esterni limita l'accesso ai soli progetti pertinenti, se possibile con scadenza.</li>
<li>Si attiva obbligatoriamente l'<strong>MFA su tutte le utenze</strong> e si forza il cambio password al primo accesso. Senza MFA l'accesso non è abilitato.</li>
<li>Il PC portatile viene consegnato già <strong>cifrato a disco intero</strong> (configurato secondo l'Istruzione Hardening: antimalware, blocco schermo) e registrato nell'inventario; le credenziali sono custodite solo nel password manager/gestore segreti, mai via email o chat.</li>
<li>L'utente sottoscrive la Politica d'uso accettabile, riceve la formazione di sicurezza di base (MFA, phishing, uso AI, hardening) e si registra l'avvenuta consapevolezza.</li>
<li>Si annota nel registro accessi data, ruolo e servizi concessi.</li>
</ol>
<h3>9.2 Variazione di ruolo</h3>
<ol>
<li>A ogni cambio di mansione il responsabile richiede l'aggiornamento del profilo.</li>
<li>Simon Fattori adegua i privilegi <strong>rimuovendo quelli non più pertinenti prima di aggiungere i nuovi</strong> (no accumulo di permessi).</li>
</ol>
<h3>9.3 Riesame periodico degli accessi</h3>
<ol>
<li>Con cadenza <strong>almeno semestrale</strong>, Massimo Tagliavini e Simon Fattori riesaminano tutte le utenze, con particolare attenzione agli <strong>accessi privilegiati</strong> e alle utenze di servizio, confrontando gli accessi attivi con il personale in forza.</li>
<li>Gli accessi non più giustificati vengono revocati; gli scostamenti sono registrati come <strong>non conformità</strong> nel modulo dedicato e gli esiti del riesame sono conservati.</li>
</ol>
<h3>9.4 Offboarding (cessazione)</h3>
<ol>
<li>Alla data di cessazione (o prima, se richiesto), Simon Fattori <strong>disabilita l'account principale e tutte le utenze</strong> e revoca le sessioni attive: non ci si limita a cambiare la password.</li>
<li>Si rimuove l'utente da posta, repository, console cloud, piattaforme AI, password manager, gestore segreti e MDM, revocando chiavi API e token.</li>
<li>Si <strong>ruotano i segreti condivisi</strong> (password, chiavi API, token) di cui l'utente era a conoscenza — specialmente per i collaboratori esterni.</li>
<li>Si recupera il PC portatile, se ne verifica la cifratura e si esegue il wipe sicuro prima della riassegnazione; per dispositivi personali si rimuovono dati e accessi aziendali.</li>
<li>Si riassegnano repository, documenti e caselle condivise a un referente designato.</li>
<li>Si registra l'avvenuto offboarding (data di revoca e segreti ruotati). In caso di uscita conflittuale, priorità assoluta alla revoca immediata degli accessi.</li>
</ol>
<h2>10. Controlli ISO collegati e riferimenti NIS2</h2>
<p>Il documento attua i controlli ISO/IEC 27001:2022 <strong>A.5.15</strong> (Controllo degli accessi), <strong>A.5.16</strong> (Gestione delle identità), <strong>A.5.17</strong> (Informazioni di autenticazione), <strong>A.5.18</strong> (Diritti di accesso), <strong>A.6.3</strong> (Consapevolezza in fase di ingresso), <strong>A.8.1</strong> (Dispositivi endpoint), <strong>A.8.2</strong> (Diritti di accesso privilegiati), <strong>A.8.3</strong> (Restrizione dell'accesso alle informazioni), <strong>A.8.5</strong> (Autenticazione sicura), oltre ai controlli cloud di ISO/IEC 27017/27018. Risponde alle <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> (NIS2) in materia di controllo degli accessi, autenticazione a più fattori e igiene informatica. Le evidenze sono richiamabili anche per gli obblighi di supply chain verso i clienti NIS2.</p>
<h2>11. Registrazioni ed evidenze</h2>
<ul>
<li>Matrice dei ruoli e dei privilegi (RBAC), registro delle richieste di accesso e relative autorizzazioni;</li>
<li>Report dei riesami periodici (semestrali) degli accessi;</li>
<li>Registrazioni di onboarding/offboarding e di ritiro dei dispositivi;</li>
<li>Configurazione MFA e log di accesso conservati dalle piattaforme cloud;</li>
<li>Log degli accessi privilegiati e inventario delle chiavi API.</li>
</ul>
<h2>12. Riesame e versionamento</h2>
<p>Approvato dal Responsabile SGSI e dall'Alta Direzione (Presidente). Revisione almeno annuale o a fronte di cambiamenti significativi delle piattaforme o dei ruoli.</p>
+75
View File
@@ -0,0 +1,75 @@
<!--META|doc_type=politica_crittografia|title=Politica di Crittografia e Gestione delle Chiavi (incl. chiavi cloud)|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>Definire i criteri con cui <strong>Nuova Agile Technology srl</strong> impiega la crittografia per proteggere riservatezza e integrità delle informazioni — in particolare i dati personali dei clienti trattati nel SaaS — e per gestire in modo sicuro chiavi crittografiche e segreti applicativi lungo l'intero ciclo di vita. Include la regola specifica per la <strong>custodia e il backup delle chiavi di accesso al cloud (key escrow controllato)</strong>, affinché lo smarrimento dell'unico dispositivo non causi mai la perdita di accesso ai servizi, senza creare nuovi rischi di esposizione.</p>
<h2>2. Ambito</h2>
<p>Si applica ai dati a riposo nei servizi cloud (Aruba, Hetzner), ai dati in transito tra utenti, servizi e provider AI, ai PC portatili del personale, ai backup, ai segreti applicativi (chiavi API cloud e AI, credenziali di database, token, chiavi SSH, certificati TLS, codici di recupero MFA) e ai prodotti in licenza installati presso i clienti, per la parte di configurazione crittografica raccomandata.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 Annex A: A.8.24 (crittografia), A.8.5 (autenticazione sicura), A.5.33 (protezione delle registrazioni), A.5.17 (informazioni di autenticazione), A.8.13 (backup), A.5.23 (sicurezza nei servizi cloud).</li>
<li>ISO/IEC 27017:2015 e 27018:2019 (crittografia dei dati personali nel cloud).</li>
<li>GDPR art. 32 (misure tecniche, tra cui cifratura); Politica di Controllo degli Accessi.</li>
<li>NIS2 — D.Lgs. 138/2024, art. 24 (misure di gestione del rischio, incluse politiche e procedure relative all'uso della crittografia).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<p>Il <strong>Responsabile IT e Sicurezza (Simon Fattori)</strong> definisce e gestisce algoritmi, chiavi, vault dei segreti, accessi e rotazione; è il primo autorizzatore al recupero delle chiavi critiche. Il <strong>Responsabile SGSI (Massimo Tagliavini)</strong> verifica la conformità della Politica ed è il secondo autorizzatore al recupero. Il <strong>DPO (consulente esterno)</strong> è consultato per la cifratura dei dati personali. L'<strong>Alta Direzione (Silvia Garretto)</strong> approva la Politica. I <strong>collaboratori</strong> registrano e proteggono le proprie credenziali secondo le regole seguenti.</p>
<h2>5. Regole di crittografia</h2>
<h3>5.1 Dati in transito</h3>
<ul>
<li>Tutte le comunicazioni esterne avvengono tramite <strong>TLS 1.2 o superiore</strong> (preferibilmente TLS 1.3); protocolli e cifrature obsolete sono disabilitati.</li>
<li>Le connessioni tra applicazione e database e tra microservizi adottano canali cifrati ove tecnicamente possibile.</li>
<li>Le chiamate alle API dei provider AI e cloud avvengono esclusivamente su canali cifrati.</li>
</ul>
<h3>5.2 Dati a riposo</h3>
<ul>
<li>I dati a riposo nei servizi cloud sono cifrati; database e volumi che contengono dati personali utilizzano cifratura (es. AES-256) secondo le funzionalità del provider.</li>
<li>I <strong>PC portatili</strong> del personale hanno la <strong>cifratura completa del disco</strong> attiva (BitLocker/FileVault/LUKS).</li>
<li>I <strong>backup</strong> sono cifrati e conservati in modo sicuro.</li>
</ul>
<h3>5.3 Algoritmi ammessi</h3>
<ul>
<li>Cifratura simmetrica: <strong>AES-256</strong> (modalità autenticate come GCM).</li>
<li>Hashing password: funzioni dedicate e resistenti (es. <strong>bcrypt/Argon2</strong>); vietato MD5/SHA-1 per scopi di sicurezza.</li>
<li>Firma/integrità: <strong>HMAC-SHA-256</strong> o equivalente; certificati basati su algoritmi robusti.</li>
</ul>
<h3>5.4 Provider AI e dati</h3>
<p>Le informazioni inviate alle piattaforme AI sono trasmesse su canali cifrati e, ove possibile, <strong>minimizzate/anonimizzate</strong> per evitare l'esposizione di dati personali o riservati non necessari all'elaborazione.</p>
<h2>6. Gestione delle chiavi e dei segreti</h2>
<h3>6.1 Principi generali</h3>
<ul>
<li><strong>Custodia centralizzata</strong>: i segreti (chiavi API, credenziali, token, chiavi SSH, codici di recupero MFA) vivono in un <strong>vault cifrato / password manager aziendale</strong>, mai in chiaro nel codice sorgente, nei file di configurazione versionati, nei log, nelle email, chat o note personali.</li>
<li><strong>Sempre cifrate, mai sul portatile</strong>: nessuna credenziale in chiaro su portatili, repository o supporti locali non protetti.</li>
<li><strong>Minimo privilegio e tracciabilità</strong>: l'accesso alle chiavi segue il principio del minimo privilegio ed è registrato (audit log).</li>
<li><strong>Rotazione</strong>: rotazione periodica e rotazione immediata in caso di sospetta compromissione, leak o cessazione di personale con accesso.</li>
<li><strong>Certificati TLS</strong>: monitorati per la scadenza e rinnovati tempestivamente.</li>
</ul>
<h3>6.2 Custodia e backup delle chiavi di accesso al cloud (key escrow controllato)</h3>
<p>Le chiavi e credenziali di accesso ai servizi cloud (Aruba, Hetzner, piattaforme AI, repository, console di amministrazione) seguono regole rafforzate affinché la perdita di un dispositivo non comporti mai la perdita di accesso:</p>
<ul>
<li><strong>Copia di sicurezza obbligatoria</strong>: ogni chiave critica ha una copia di backup cifrata; perdere un dispositivo non deve significare perdere l'accesso.</li>
<li><strong>Backup dei codici MFA</strong>: i codici di recupero MFA sono salvati nella cassaforte digitale cifrata, mai sullo stesso dispositivo che genera l'OTP.</li>
<li><strong>Backup periodico verificato</strong>: la cassaforte digitale è inclusa nei backup cifrati; si verifica periodicamente l'integrità e la ripristinabilità delle copie.</li>
<li><strong>Recupero a doppia autorizzazione</strong>: il recupero o l'estrazione di chiavi critiche richiede l'approvazione di <strong>due persone</strong> (Responsabile IT + RSGSI), per evitare abusi.</li>
<li><strong>Rotazione post-incidente</strong>: dopo furto, smarrimento o sospetta compromissione si rigenerano le credenziali coinvolte e si aggiorna la cassaforte.</li>
<li><strong>Dismissione</strong>: alla cessazione del rapporto si revocano gli accessi del collaboratore e si trasferiscono/ruotano le chiavi di sua competenza.</li>
</ul>
<h3>6.3 Divieti</h3>
<ul>
<li>Niente credenziali in chiaro su portatile, repository, email, chat o note personali.</li>
<li>Niente condivisione di password fuori dal gestore di segreti.</li>
<li>Niente backup delle chiavi su cloud personali o supporti non cifrati.</li>
</ul>
<h2>7. Controlli ISO collegati e riferimenti NIS2</h2>
<p><strong>A.8.24</strong> (uso della crittografia), <strong>A.8.5</strong> (autenticazione sicura), <strong>A.5.33</strong> (protezione delle registrazioni), <strong>A.5.17</strong> (informazioni di autenticazione), <strong>A.8.13</strong> (backup), <strong>A.5.23</strong> (sicurezza nei servizi cloud). Riferimenti cloud: ISO/IEC 27017/27018. Riferimento NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, incluse crittografia, controllo accessi e continuità). Le ISO sono buone prassi, non obblighi di legge.</p>
<h2>8. Registrazioni ed evidenze</h2>
<p>Inventario delle chiavi e dei certificati, log di rotazione, configurazioni TLS, evidenza della cifratura disco sui PC portatili, registro degli accessi al vault, log delle autorizzazioni doppie al recupero delle chiavi cloud.</p>
<h2>9. Riesame e versionamento</h2>
<p>Approvata dal Responsabile SGSI. Documento vivo, riesaminato almeno annualmente o a fronte di nuove vulnerabilità crittografiche, incidenti o cambiamenti rilevanti. Versione tracciata nella piattaforma SGSI.</p>
+124
View File
@@ -0,0 +1,124 @@
<!--META|doc_type=politica_uso_dispositivi|title=Politica di Uso Accettabile, Dispositivi e Lavoro da Remoto|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>Definire, in un unico documento "persone e strumenti", le regole con cui <strong>Nuova Agile Technology srl</strong> classifica le informazioni e usa in sicurezza dispositivi, account e servizi aziendali. L'azienda opera con modello <strong>Work-from-Anywhere senza sede fissa né perimetro fisico</strong> e interamente in cloud: il portatile è il principale punto di esposizione e va trattato come asset critico. La Politica copre classificazione delle informazioni, uso accettabile degli strumenti, dispositivi dedicati e separazione privato/lavoro, lavoro da remoto, posta e servizi aziendali, hardening degli endpoint e uso sicuro dell'AI.</p>
<h2>2. Ambito</h2>
<p>Si applica a tutto il personale (dipendenti e collaboratori esterni a P.IVA) che utilizza dispositivi, account e servizi aziendali (cloud Aruba/Hetzner, repository di codice, posta, strumenti di collaborazione, gestionali, piattaforme AI), ovunque si trovi e con qualsiasi rete. Riguarda tutte le informazioni gestite dall'Azienda — codice sorgente, documentazione, dati dei servizi SaaS inclusi i dati personali dei clienti, credenziali e segreti, documenti amministrativi e contrattuali — indipendentemente da formato e supporto. Non essendoci server in sede, l'attenzione è sugli <strong>endpoint mobili</strong> e sull'uso responsabile dei servizi cloud.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 Annex A: A.5.10, A.5.12, A.5.13, A.5.14, A.5.17, A.6.7, A.7.9, A.8.1, A.8.5, A.8.7, A.8.9, A.8.10, A.8.12, A.8.24, A.5.23.</li>
<li>ISO/IEC 27018:2019 (protezione dei dati personali nel cloud); ISO/IEC 27017:2015 (servizi cloud).</li>
<li>GDPR; Direttiva NIS2 / D.Lgs. 138/2024.</li>
<li>Politica per la Sicurezza delle Informazioni; Politica di Controllo degli Accessi; Politica di Crittografia e Gestione delle Chiavi.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<p>Il <strong>Responsabile IT e Sicurezza (Simon Fattori)</strong> configura e mantiene gli endpoint sicuri (cifratura, aggiornamenti, antimalware, firewall), gestisce MDM, MFA, SSO, account e revoche, e implementa le misure tecniche per livello di classificazione. Il <strong>Responsabile SGSI (Massimo Tagliavini)</strong> mantiene lo schema di classificazione e vigila sul rispetto della Politica. Il <strong>DPO (consulente esterno)</strong> presidia il corretto trattamento dei dati personali. L'<strong>Alta Direzione (Silvia Garretto)</strong> approva la Politica. Ogni utente è personalmente responsabile dell'uso corretto degli strumenti assegnati e, come proprietario o utilizzatore dell'informazione, della sua corretta classificazione e gestione.</p>
<h2>5. Classificazione delle informazioni</h2>
<h3>5.1 Schema di classificazione</h3>
<table>
<tr><th>Livello</th><th>Descrizione</th><th>Esempi</th></tr>
<tr><td><strong>Pubblico</strong></td><td>Destinato alla diffusione, nessun impatto se divulgato.</td><td>Materiale marketing, documentazione pubblica del prodotto</td></tr>
<tr><td><strong>Interno</strong></td><td>Uso interno; impatto limitato in caso di divulgazione non autorizzata.</td><td>Procedure interne, comunicazioni di team</td></tr>
<tr><td><strong>Riservato</strong></td><td>Sensibile aziendale o di terzi; impatto significativo se divulgato.</td><td>Codice sorgente proprietario, contratti, dati clienti non personali</td></tr>
<tr><td><strong>Strettamente riservato</strong></td><td>Massima sensibilità; impatto grave (legale, reputazionale, sanzionatorio).</td><td>Dati personali dei clienti nel SaaS, credenziali, chiavi API, segreti</td></tr>
</table>
<h3>5.2 Etichettatura e misure minime per livello</h3>
<p>Documenti e repository riportano, ove pertinente, il livello di classificazione; i dati personali sono trattati come <strong>Strettamente riservati</strong> per impostazione predefinita.</p>
<ul>
<li><strong>Pubblico</strong>: nessuna restrizione di accesso particolare; verifica di accuratezza prima della pubblicazione.</li>
<li><strong>Interno</strong>: accesso limitato al personale; non condividere all'esterno senza autorizzazione.</li>
<li><strong>Riservato</strong>: accesso su base need-to-know; trasmissione cifrata; conservazione nei sistemi aziendali autorizzati; divieto di copia su supporti non controllati.</li>
<li><strong>Strettamente riservato</strong>: cifratura obbligatoria a riposo e in transito; accesso con MFA e minimo privilegio; tracciabilità degli accessi; divieto di trasmissione a piattaforme AI senza minimizzazione/anonimizzazione; nessuna conservazione su supporti personali.</li>
</ul>
<h3>5.3 Dati personali nel SaaS, conservazione e cancellazione sicura</h3>
<ul>
<li>Trattamento secondo le istruzioni documentate dei clienti (in qualità di responsabile del trattamento), con isolamento per tenant; minimizzazione e limitazione della conservazione; supporto ai diritti degli interessati e alle richieste di cancellazione (ISO 27018 / GDPR).</li>
<li>I periodi di conservazione sono definiti per categoria e in base a obblighi legali e contrattuali.</li>
<li>La cancellazione avviene in modo sicuro (cancellazione logica irreversibile nei sistemi cloud, distruzione delle copie); per i PC portatili la dismissione prevede wipe sicuro o riformattazione con disco già cifrato.</li>
</ul>
<h2>6. Uso accettabile degli strumenti</h2>
<ul>
<li>Gli strumenti aziendali sono destinati prevalentemente a finalità professionali; un uso personale limitato e ragionevole è tollerato se non compromette sicurezza e produttività.</li>
<li>È <strong>vietato</strong>: installare software non autorizzato o da fonti non attendibili; disattivare le misure di sicurezza (cifratura, antimalware, aggiornamenti); condividere credenziali; aggirare i controlli di accesso; trattare dati aziendali su servizi personali non approvati.</li>
<li>Condividere informazioni riservate solo con destinatari autorizzati e tramite canali e strumenti aziendali approvati; evitare app di messaggistica personali per documenti di lavoro.</li>
</ul>
<h2>7. Dispositivi aziendali e separazione privato/lavoro</h2>
<ul>
<li><strong>Un portatile e uno smartphone aziendali per persona</strong>, dedicati e gestiti centralmente tramite <strong>MDM</strong>; l'accesso a sistemi e dati avviene esclusivamente da questi dispositivi, mai da dispositivi privati.</li>
<li><strong>Uso solo professionale</strong>: niente uso personale, niente account privati, niente app fuori dal catalogo approvato; privilegi di amministratore limitati e concessi solo da IT.</li>
<li><strong>Cifratura di default</strong>: full-disk encryption su portatili (BitLocker/FileVault/LUKS), smartphone cifrato con blocco schermo obbligatorio (PIN/biometria).</li>
<li><strong>Secure-by-default</strong>: aggiornamenti automatici di sistema e applicazioni, antimalware attivo e aggiornato, firewall locale attivo, blocco automatico dello schermo dopo inattività, servizi non necessari disabilitati.</li>
<li><strong>Nessuna credenziale in chiaro sul dispositivo</strong>: password e chiavi vivono nel password manager aziendale (vedi Politica di Crittografia e Gestione delle Chiavi).</li>
<li><strong>MDM e controllo remoto</strong>: IT può applicare policy, distribuire aggiornamenti e, in caso di furto/smarrimento, eseguire blocco o cancellazione remota (wipe).</li>
<li><strong>Separazione privato/lavoro</strong>: i dati personali del collaboratore non risiedono sui dispositivi aziendali e i dati aziendali non risiedono sui dispositivi privati; niente sincronizzazione con cloud personali. Così, in caso di wipe remoto, non si perdono dati personali perché non devono esserci.</li>
<li><strong>Restituzione e fine rapporto</strong>: alla cessazione o riassegnazione il dispositivo è restituito, ripulito (wipe) e riconfigurato; gli accessi associati sono revocati e le chiavi ruotate.</li>
</ul>
<h2>8. Hardening del PC portatile</h2>
<p>Passi minimi e obbligatori per configurare ogni portatile aziendale, indipendentemente dal sistema operativo:</p>
<ol>
<li><strong>Cifratura del disco (obbligatoria)</strong> prima di salvare qualsiasi dato (BitLocker con TPM+PIN / FileVault / LUKS); chiave di recupero solo nel password manager aziendale.</li>
<li><strong>Account e privilegi</strong>: account standard per il lavoro quotidiano (non navigare né leggere email da account amministratore); account ospite/predefiniti rinominati o disabilitati.</li>
<li><strong>Blocco schermo</strong> automatico dopo massimo 5 minuti di inattività con password/biometria allo sblocco; blocco manuale lasciando la postazione.</li>
<li><strong>Firmware/UEFI</strong>: password al firmware ove possibile, Secure Boot abilitato, avvio da USB disabilitato se non necessario.</li>
<li><strong>Firewall e servizi</strong>: firewall locale attivo; condivisione file/stampanti, desktop remoto e servizi non usati disattivati.</li>
<li><strong>Antimalware</strong> attivo con protezione in tempo reale e aggiornamenti automatici.</li>
<li><strong>Aggiornamenti</strong> automatici di sistema e applicazioni abilitati.</li>
<li><strong>Backup</strong>: lavoro su repository e storage cloud aziendali (codice versionato in git, documenti sui drive aziendali), non solo in locale; si evita l'accumulo di dati riservati in locale e l'uso di supporti rimovibili non cifrati.</li>
<li><strong>Software</strong>: solo da fonti ufficiali e necessario al lavoro; rimosse applicazioni inutili ed estensioni browser non indispensabili.</li>
<li><strong>MDM</strong>: non rimuovere l'agente né eludere le policy applicate.</li>
</ol>
<p><strong>Verifiche di esito</strong>: disco cifrato con chiave nel password manager; blocco schermo ≤ 5 minuti; firewall e antimalware attivi con ultimo aggiornamento entro 7 giorni; nessun account amministratore usato per attività quotidiane.</p>
<h2>9. Lavoro da remoto e mobilità</h2>
<ul>
<li><strong>Solo dispositivi aziendali</strong> e accesso sempre con <strong>MFA e SSO</strong>; le sessioni hanno timeout automatico.</li>
<li><strong>Reti non fidate</strong>: su Wi-Fi pubblici o di terzi usare sempre connessioni cifrate (HTTPS/TLS, VPN aziendale ove prevista); mai disattivare la cifratura per "far funzionare" un servizio.</li>
<li><strong>Privacy fisica dello schermo</strong>: in luoghi pubblici evitare la visibilità (shoulder surfing), usare filtri privacy quando possibile, bloccare lo schermo allontanandosi anche per pochi minuti.</li>
<li><strong>Asset fuori sede</strong>: dispositivi mai lasciati incustoditi in auto, mezzi o spazi pubblici; in viaggio sempre con sé o in luogo sicuro.</li>
<li><strong>Nessun dato sensibile in chiaro fuori dai sistemi gestiti</strong>: niente download su supporti non cifrati, stampe non necessarie o copie su servizi personali.</li>
</ul>
<h2>10. Posta elettronica e servizi aziendali</h2>
<ul>
<li><strong>Account aziendali, non personali</strong>: il lavoro si svolge solo con caselle e account forniti e amministrati dalla società; vietato usare account personali per attività lavorative.</li>
<li><strong>Controllo della società</strong>: l'azienda amministra creazione, configurazione, sospensione e cancellazione degli account (admin centralizzato, SSO), nel rispetto della normativa privacy e con il coinvolgimento del DPO per gli aspetti di protezione dei dati personali.</li>
<li><strong>Niente inoltro a caselle personali</strong>: vietato l'auto-inoltro o l'inoltro sistematico di email di lavoro verso indirizzi personali o esterni non autorizzati.</li>
<li><strong>MFA obbligatoria</strong> su email e su tutti i servizi; password robuste gestite nel password manager aziendale; <strong>SSO ove possibile</strong> per centralizzare controllo e revoche.</li>
<li><strong>Ciclo di vita degli account</strong>: onboarding con privilegi minimi necessari (least privilege), diritti rivisti periodicamente secondo il ruolo, offboarding con disattivazione e revoca tempestiva alla cessazione del rapporto (la casella resta sotto controllo della società).</li>
</ul>
<h2>11. Posta, phishing e segnalazione degli eventi</h2>
<ul>
<li>Prudenza con allegati e link sospetti; verifica dell'identità del mittente prima di azioni sensibili (es. cambi di pagamento, invio credenziali).</li>
<li>Segnalazione tempestiva al Responsabile IT/SGSI di tentativi di phishing, social engineering, anomalie o sospette compromissioni.</li>
<li>Smarrimento o furto di un dispositivo o accesso anomalo va segnalato <strong>immediatamente</strong> al Responsabile IT: IT procede con blocco/wipe remoto via MDM e revoca/rotazione delle credenziali coinvolte. Le chiavi di accesso al cloud restano recuperabili grazie al key escrow controllato (vedi Politica di Crittografia e Gestione delle Chiavi), quindi lo smarrimento del dispositivo non causa perdita di accesso.</li>
<li>La segnalazione consente la valutazione e, se necessario, la notifica secondo gli obblighi NIS2 (pre-allarme entro 24 ore, notifica entro 72 ore, relazione finale entro 1 mese al CSIRT Italia/ACN, ove l'Azienda risulti soggetto obbligato, art. 25 D.Lgs. 138/2024).</li>
</ul>
<h2>12. Uso sicuro delle piattaforme AI</h2>
<ul>
<li><strong>Solo strumenti approvati</strong> dal Resp. IT, protetti da MFA; eventuali accordi/DPA con il fornitore verificati da Resp. IT/DPO.</li>
<li><strong>Mai dati personali dei clienti</strong> nei prompt (nomi, email, contatti, dati contenuti nel SaaS) salvo accordo/DPA adeguato e minimizzazione.</li>
<li><strong>Mai segreti</strong>: password, chiavi API, token, certificati, stringhe di connessione o configurazioni di produzione restano nel gestore segreti, non nei prompt.</li>
<li><strong>Minimizza e anonimizza</strong>: sostituisci i dati identificativi con segnaposto (es. "CLIENTE_X"); condividi solo il minimo necessario.</li>
<li><strong>Codice sorgente</strong>: non caricare interi repository proprietari o codice contenente segreti; estrai solo lo snippet rilevante e ripulito.</li>
<li><strong>Verifica gli output</strong>: tratta le risposte come bozze da verificare per correttezza, sicurezza del codice e assenza di riferimenti inventati prima dell'uso in produzione o verso i clienti.</li>
<li><strong>Impostazioni privacy</strong>: dove possibile, disabilita l'uso dei tuoi dati per l'addestramento e usa i piani/impostazioni aziendali concordati.</li>
<li><strong>Nel dubbio non inviare</strong> e chiedi prima al Resp. IT. Se hai inviato per errore dati personali o segreti, contatta subito il Resp. IT (e il DPO per i dati personali): può configurare un incidente da valutare.</li>
</ul>
<h2>13. Controlli ISO collegati e riferimenti NIS2</h2>
<p><strong>A.5.10</strong> (uso accettabile), <strong>A.5.12/A.5.13/A.5.14</strong> (classificazione, etichettatura, trasferimento delle informazioni), <strong>A.5.17</strong> (informazioni di autenticazione), <strong>A.6.7</strong> (lavoro da remoto), <strong>A.7.9</strong> (sicurezza degli asset fuori sede), <strong>A.8.1</strong> (dispositivi endpoint), <strong>A.8.5</strong> (autenticazione sicura), <strong>A.8.7</strong> (protezione dai malware), <strong>A.8.9</strong> (gestione della configurazione), <strong>A.8.10/A.8.12</strong> (cancellazione delle informazioni, prevenzione della fuga di dati), <strong>A.8.24</strong> (crittografia), <strong>A.5.23</strong> (sicurezza nei servizi cloud). Cloud: ISO/IEC 27017/27018. Riferimenti NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, tra cui igiene informatica di base, crittografia, controllo accessi e formazione) e <strong>art.25</strong> (obblighi di notifica degli incidenti). Le ISO sono buone prassi, non obblighi di legge.</p>
<h2>14. Registrazioni ed evidenze</h2>
<p>Accettazione della Politica da parte del personale; schema di classificazione e inventario degli asset informativi con livello; inventario degli endpoint con stato di cifratura e aggiornamento; registro delle segnalazioni di eventi/incidenti; registro dei trattamenti (GDPR) ed evidenze di cancellazione sicura.</p>
<h2>15. Riesame e versionamento</h2>
<p>Approvata dall'Alta Direzione. Documento vivo, riesaminato almeno annualmente o a fronte di nuove minacce rilevanti, cambiamenti del parco dispositivi o adozione di nuovi strumenti (incl. AI). Versione tracciata nella piattaforma SGSI.</p>
+91
View File
@@ -0,0 +1,91 @@
<!--META|doc_type=politica_cloud_privacy|title=Politica di Sicurezza del Cloud e Protezione dei Dati (ISO 27017/27018, GDPR)|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> utilizza in sicurezza i servizi cloud su cui poggia interamente la propria operatività e protegge i dati personali (PII) che vi tratta per conto dei propri clienti. Poiché l'infrastruttura aziendale è <strong>integralmente in cloud</strong> (Aruba S.p.A. – Italia; Hetzner – Germania, UE) e in sede non esiste alcun server, la corretta configurazione e gestione dei servizi cloud è essenziale per la sicurezza delle informazioni dell'azienda e dei clienti. La Politica recepisce i controlli cloud-specifici della <strong>ISO/IEC 27017:2015</strong> (chiarendo il <strong>modello di responsabilità condivisa</strong> tra azienda e fornitori) e i controlli della <strong>ISO/IEC 27018:2019</strong> per la tutela dei PII nei cloud pubblici, in coerenza con il <strong>Regolamento (UE) 2016/679 (GDPR)</strong>. Nel trattare i PII via <strong>SaaS multi-tenant</strong>, l'azienda agisce di norma come <strong>responsabile del trattamento</strong> per conto del cliente (titolare).</p>
<h2>2. Ambito</h2>
<p>La Politica si applica a tutti i servizi cloud utilizzati per: l'erogazione del SaaS multi-tenant; l'ospitalità di ambienti di sviluppo, test e strumenti interni; l'archiviazione di dati aziendali e dei clienti; i servizi di intelligenza artificiale fruiti via API (es. LLM Anthropic). Copre tutti i trattamenti di PII di clienti e interessati finali svolti tramite l'infrastruttura cloud (Aruba IT, Hetzner DE) e i servizi accessori, per l'intero ciclo di vita del dato: raccolta, archiviazione, elaborazione, trasferimento, conservazione e cancellazione. Si applica a tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) che configura, amministra o accede a tali servizi — esclusivamente tramite <strong>PC portatili cifrati</strong> — e ai sub-fornitori che concorrono al trattamento.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27017:2015 – controlli cloud-specifici (CLD.6.3, CLD.8.1, CLD.9.5, CLD.12.1, CLD.12.4, CLD.13.1) e modello di responsabilità condivisa.</li>
<li>ISO/IEC 27018:2019 – codice di condotta per la protezione dei PII nei cloud pubblici.</li>
<li>ISO/IEC 27001:2022 – Annex A, in particolare A.5.23 (uso di servizi cloud), A.5.34 (privacy e protezione dei PII), A.8.9, A.8.10, A.8.11, A.8.12, A.8.13, A.8.15, A.8.24.</li>
<li>Regolamento (UE) 2016/679 (GDPR) – artt. 5, 28, 30, 32, 33, 44-49.</li>
<li>D.Lgs. 138/2024, art. 24 (misure di gestione del rischio) e art. 25 (notifica incidenti).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica e l'adozione/dismissione di servizi cloud strategici; garantisce le risorse per la conformità privacy.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica, coordina la valutazione dei rischi cloud, integra i requisiti privacy nel SGSI e gestisce i riesami.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Configura e amministra i servizi cloud secondo la responsabilità condivisa; attua le misure tecniche (accessi, cifratura, log, hardening, cancellazione).</td></tr>
<tr><td>DPO (consulente esterno)</td><td>Sorveglia la conformità GDPR/27018, supporta i DPA, le richieste degli interessati, i trasferimenti di dati personali e la gestione dei data breach.</td></tr>
</table>
<h2>5. Modello di responsabilità condivisa</h2>
<p>Per ogni servizio cloud è documentata la ripartizione delle responsabilità di sicurezza tra <strong>fornitore</strong> (sicurezza fisica dei data center, hypervisor, rete sottostante, disponibilità dell'infrastruttura) e <strong>azienda cliente</strong> (configurazione dei servizi, gestione degli account e degli accessi, cifratura dei dati a riposo e in transito, gestione delle chiavi, backup applicativi, logging). La consapevolezza di "ciò che il fornitore fa per noi e ciò che dobbiamo fare noi" è la base della Politica (CLD.6.3).</p>
<h2>6. Selezione, collocazione e trasferimenti</h2>
<ul>
<li>Si privilegiano fornitori cloud con certificazioni di sicurezza riconosciute e data center nell'UE/SEE (Aruba in Italia, Hetzner in Germania) per favorire la conformità GDPR; i PII sono ospitati di preferenza su tali data center.</li>
<li>Eventuali servizi e trasferimenti extra-UE (es. API AI) sono adottati solo con garanzie adeguate ai sensi degli artt. 44-49 GDPR, DPA idonei e documentazione del trasferimento.</li>
</ul>
<h2>7. Ruolo di responsabile, trasparenza e sub-fornitori</h2>
<ul>
<li>L'azienda tratta i PII dei clienti <strong>solo su istruzione documentata del titolare</strong> (art. 28 GDPR; principio 27018) e per le finalità del servizio, senza usi propri non autorizzati.</li>
<li>Con ogni cliente che affida PII è stipulato un <strong>DPA ex art. 28 GDPR</strong> che disciplina finalità, durata, misure di sicurezza, sub-responsabili e assistenza al titolare.</li>
<li>I sub-fornitori che trattano PII (provider cloud, eventuali servizi AI) sono dichiarati al cliente e vincolati a obblighi di protezione equivalenti; i cambi rilevanti di sub-fornitore sono comunicati al titolare secondo gli accordi.</li>
</ul>
<h2>8. Misure di sicurezza tecniche</h2>
<ul>
<li>Cifratura dei dati e dei PII in transito (TLS) e a riposo; gestione sicura delle chiavi (A.8.24).</li>
<li>Accessi amministrativi nominali, autenticazione forte (MFA), principio del minimo privilegio, separazione dei ruoli e revisione periodica delle utenze, con revoca tempestiva al termine del rapporto; tracciamento degli accessi ai PII. L'accesso avviene solo da PC portatili cifrati e aggiornati.</li>
<li>Isolamento e segregazione dei dati tra tenant nel SaaS multi-tenant (CLD.9.5); gli ambienti di test non utilizzano PII reali (dati anonimizzati/sintetici).</li>
<li>Configurazioni sicure (hardening) e disabilitazione dei servizi non necessari.</li>
</ul>
<h2>9. Logging, monitoraggio e gestione operativa</h2>
<ul>
<li>Abilitazione dei log di sicurezza, degli accessi amministrativi e delle operazioni sui PII resi disponibili dai fornitori; conservazione e revisione periodica (CLD.12.1, CLD.12.4).</li>
<li>Monitoraggio della disponibilità dei servizi e degli avvisi di sicurezza dei fornitori cloud.</li>
<li>Gestione coordinata dei cambiamenti che impattano i servizi cloud.</li>
</ul>
<h2>10. Diritti degli interessati, continuità, conservazione e cancellazione</h2>
<ul>
<li>L'azienda assiste il titolare nel dare seguito alle richieste degli interessati (accesso, rettifica, cancellazione, portabilità, opposizione), fornendo gli strumenti tecnici per individuare ed estrarre/cancellare i dati nel SaaS.</li>
<li>Backup e ripristino sono gestiti coerentemente con la Politica di Continuità, tenendo conto della responsabilità condivisa.</li>
<li>I PII sono conservati per il tempo previsto dal contratto/dalla finalità; alla cessazione di un servizio o del rapporto si garantisce l'esportazione dei dati e la loro cancellazione sicura, anche presso i provider cloud (CLD.8.1).</li>
</ul>
<h2>11. Gestione incidenti e violazioni di dati personali</h2>
<p>Gli incidenti che coinvolgono i servizi cloud sono gestiti secondo il processo di gestione incidenti del SGSI. In caso di data breach, l'azienda informa <strong>senza ingiustificato ritardo</strong> il titolare (art. 33 GDPR), supportandolo nelle valutazioni e notifiche. Se l'evento è anche un incidente NIS2 rilevante e l'azienda è soggetto obbligato, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024); il coordinamento tra notifica privacy e notifica NIS2 è gestito dal DPO con il RSGSI.</p>
<h2>12. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li><strong>CLD.6.3</strong> – Ripartizione delle responsabilità tra cliente e fornitore cloud.</li>
<li><strong>CLD.8.1</strong> – Rimozione/restituzione degli asset cliente al termine del servizio.</li>
<li><strong>CLD.9.5</strong> – Segregazione negli ambienti virtuali multi-tenant.</li>
<li><strong>CLD.12.1 / CLD.12.4</strong> – Operatività e logging dei servizi cloud.</li>
<li><strong>CLD.13.1</strong> – Gestione della sicurezza di rete nel cloud.</li>
<li>Controlli <strong>ISO/IEC 27018</strong>: consenso e scopo, trasparenza sui sub-fornitori, cancellazione sicura, notifica accessi, restrizione d'uso dei PII, tracciamento.</li>
<li><strong>A.5.23</strong> – Sicurezza nell'uso di servizi cloud; <strong>A.5.34</strong> – privacy e protezione dei PII.</li>
<li><strong>A.8.10 / A.8.11</strong> – Cancellazione e mascheramento dei dati; <strong>A.8.12</strong> – prevenzione della fuga di dati; <strong>A.8.24</strong> – uso della crittografia.</li>
<li><strong>GDPR</strong>: artt. 5, 28, 30, 32, 33, 44-49.</li>
<li><strong>NIS2 – D.Lgs. 138/2024</strong>: art. 24 (misure di gestione del rischio), art. 25 (notifica incidenti).</li>
</ul>
<h2>13. Registrazioni ed evidenze</h2>
<ul>
<li>Matrice di responsabilità condivisa per ciascun servizio cloud; inventario dei servizi cloud e relative configurazioni di sicurezza.</li>
<li>DPA con i clienti e con i sub-responsabili; elenco dei sub-fornitori; registro dei trattamenti per conto del titolare (supporto art. 30 GDPR).</li>
<li>Registro degli accessi amministrativi e ai PII; log di sicurezza conservati; registro delle richieste degli interessati.</li>
<li>Registro dei data breach e relative comunicazioni al titolare; evidenze dei riesami periodici di configurazioni e accessi.</li>
</ul>
<h2>14. Riesame e versionamento</h2>
<p>La Politica è riesaminata almeno annualmente o a fronte dell'adozione di nuovi servizi cloud, nuovi trattamenti, cambi di sub-fornitori, modifiche architetturali, aggiornamenti normativi o incidenti rilevanti. Versione corrente: <strong>2.0</strong>, approvata dalla Presidente con il parere del DPO. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
+89
View File
@@ -0,0 +1,89 @@
<!--META|doc_type=politica_fornitori|title=Politica e Procedura di Gestione dei Fornitori e dei Servizi Cloud|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>Definire principi, regole e passi operativi con cui <strong>Nuova Agile Technology srl</strong> seleziona, qualifica, contrattualizza, sorveglia e dismette i propri fornitori, con particolare attenzione a quelli rilevanti per la sicurezza delle informazioni: infrastruttura cloud (<strong>Aruba</strong> IT, <strong>Hetzner</strong> DE), servizi di <strong>intelligenza artificiale</strong> via API (es. LLM Anthropic), sviluppo e manutenzione software. Lo scopo è mantenere il livello di sicurezza richiesto dal SGSI anche quando attività o dati sono affidati a terze parti, in modo ripetibile e tracciabile, e propagare lungo la supply chain gli obblighi di sicurezza previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
<h2>2. Ambito</h2>
<p>Si applica a tutti i fornitori, sub-fornitori e prestatori di servizi, nuovi ed esistenti, che: (a) trattano, ospitano o accedono a informazioni aziendali o dei clienti; (b) erogano infrastruttura cloud (Aruba – Italia; Hetzner – Germania, UE); (c) forniscono servizi AI tramite API; (d) contribuiscono allo sviluppo, manutenzione o assistenza dei prodotti, sia in licenza on-premise sia SaaS multi-tenant. Coinvolge tutto il personale che richiede, valuta e gestisce i fornitori (9 dipendenti, 2 collaboratori esterni a P.IVA).</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 – Annex A: A.5.19, A.5.20, A.5.21, A.5.22, A.5.23.</li>
<li>ISO/IEC 27017:2015 (CLD.6.3, CLD.8.1; modello di responsabilità condivisa) e ISO/IEC 27018:2019 (tutela delle PII nel cloud).</li>
<li>D.Lgs. 138/2024, art. 24 (sicurezza della supply chain) e art. 25 (notifica incidenti).</li>
<li>Regolamento (UE) 2016/679 (GDPR), art. 28 (responsabili del trattamento).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Presidente (Silvia Garretto)</td><td>Approva il documento, autorizza l'ingaggio dei fornitori critici e accetta i rischi residui.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene il documento, classifica i fornitori, gestisce il registro, coordina valutazioni e riesami periodici.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Valuta tecnicamente i fornitori, verifica certificazioni e misure, definisce la matrice di responsabilità condivisa, monitora SLA e incidenti.</td></tr>
<tr><td>DPO (esterno)</td><td>Valida gli aspetti privacy, i DPA e i trasferimenti di dati personali.</td></tr>
</table>
<h2>5. Principi</h2>
<ul>
<li><strong>Classificazione per criticità</strong>: ogni fornitore è classificato in funzione del dato e del servizio — <strong>critico</strong> (cloud, AI, accesso a PII di clienti), <strong>rilevante</strong> (sviluppo/manutenzione), <strong>ordinario</strong> (servizi non legati alle informazioni). La classe determina la profondità della due diligence.</li>
<li><strong>Dati nell'UE</strong>: si privilegiano fornitori con certificazioni riconosciute e data center nell'UE/SEE (Aruba in Italia, Hetzner in Germania); per trasferimenti extra-UE si verificano garanzie adeguate.</li>
<li><strong>Propagazione lungo la supply chain</strong>: essendo l'azienda fornitore di clienti soggetti a NIS2, i requisiti di sicurezza ricevuti dai clienti vengono recepiti e ribaltati sui propri sub-fornitori, così che le misure scendano coerentemente lungo l'intera catena (art. 24 D.Lgs. 138/2024).</li>
</ul>
<h2>6. Processo</h2>
<h3>6.1 Richiesta e classificazione</h3>
<ul>
<li>Il richiedente apre una richiesta indicando servizio, dati coinvolti e finalità.</li>
<li>Il RSGSI assegna la classe di criticità (critico / rilevante / ordinario).</li>
</ul>
<h3>6.2 Due diligence e valutazione di sicurezza</h3>
<ul>
<li>Per i fornitori critici/rilevanti il Resp. IT raccoglie evidenze: certificazioni (ISO 27001/27017/27018, SOC 2), localizzazione dei data center, misure tecniche/organizzative, gestione incidenti.</li>
<li>Si compila una scheda di valutazione con esito (idoneo / idoneo con prescrizioni / non idoneo).</li>
<li>Per i servizi cloud si definisce la <strong>matrice di responsabilità condivisa</strong> (cosa fa il fornitore, cosa fa l'azienda).</li>
</ul>
<h3>6.3 Contrattualizzazione (clausole e DPA)</h3>
<ul>
<li>Il contratto include clausole di sicurezza: riservatezza, SLA, obbligo di notifica incidenti, diritto a evidenze/audit, gestione del fine rapporto e restituzione/cancellazione dei dati.</li>
<li>Quando sono trattati dati personali si stipula un <strong>DPA ex art. 28 GDPR</strong>, verificando i sub-responsabili e le clausole 27018.</li>
<li>Si richiede al fornitore la notifica tempestiva degli incidenti, coerente con i tempi NIS2 (pre-allarme 24h, notifica 72h, relazione finale 1 mese) quando l'azienda è soggetto obbligato.</li>
<li>L'attivazione dei fornitori critici richiede l'autorizzazione della Presidente.</li>
</ul>
<h3>6.4 Attivazione e configurazione sicura</h3>
<ul>
<li>Accessi nominali con MFA e minimo privilegio, cifratura e logging.</li>
<li>Il fornitore è inserito nel <strong>registro fornitori</strong> con classificazione, scadenze contrattuali e referenti.</li>
</ul>
<h3>6.5 Monitoraggio e riesame periodico</h3>
<ul>
<li>Almeno annualmente per i fornitori critici: verifica SLA, rinnovo evidenze e certificazioni, revisione accessi, analisi di eventuali incidenti.</li>
<li>Valutazione degli avvisi di sicurezza dei fornitori cloud/AI e del loro impatto; gli esiti sono registrati.</li>
<li>Le non conformità generano azioni correttive tracciate; in caso di non conformità grave o incidente, si valuta la sostituzione del fornitore.</li>
</ul>
<h3>6.6 Gestione incidenti del fornitore</h3>
<p>In caso di incidente comunicato dal fornitore o rilevato dall'azienda si attiva il processo di gestione incidenti del SGSI; se l'evento è un incidente NIS2 significativo e l'azienda è soggetto obbligato, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese; art. 25 D.Lgs. 138/2024). Gli incidenti che coinvolgono PII sono comunicati al DPO.</p>
<h3>6.7 Dismissione e fine rapporto</h3>
<ul>
<li>Revoca degli accessi, recupero/esportazione dei dati e verifica della cancellazione sicura presso il fornitore (CLD.8.1).</li>
<li>Aggiornamento del registro fornitori e valutazione dell'impatto sulla continuità.</li>
</ul>
<h2>7. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li><strong>A.5.19 / A.5.20</strong> – Sicurezza nei rapporti e negli accordi con i fornitori.</li>
<li><strong>A.5.21</strong> – Sicurezza nella catena di fornitura ICT.</li>
<li><strong>A.5.22</strong> – Monitoraggio, riesame e gestione dei cambiamenti dei servizi dei fornitori.</li>
<li><strong>A.5.23</strong> – Sicurezza nell'uso di servizi cloud; <strong>CLD.6.3 / CLD.8.1</strong> (ISO 27017); ISO 27018 per le PII.</li>
<li><strong>NIS2 – D.Lgs. 138/2024</strong>: art. 24 (sicurezza della supply chain); art. 25 (notifica incidenti).</li>
</ul>
<h2>8. Registrazioni ed evidenze</h2>
<ul>
<li>Registro dei fornitori (classificazione di criticità, scadenze, referenti, stato di qualifica).</li>
<li>Schede di valutazione di sicurezza e matrici di responsabilità condivisa.</li>
<li>Contratti, DPA e clausole di sicurezza archiviati.</li>
<li>Verbali dei riesami periodici, registro azioni correttive e registro incidenti dei fornitori.</li>
</ul>
<h2>9. Riesame e versionamento</h2>
<p>Il documento è riesaminato almeno annualmente o a fronte di cambiamenti significativi (nuovi fornitori critici, incidenti rilevanti, evoluzioni normative). Approvato dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI con numero di versione, data e responsabile dell'aggiornamento (RSGSI).</p>
+48
View File
@@ -0,0 +1,48 @@
<!--META|doc_type=procedura_gestione_rischio|title=Procedura di Gestione del Rischio (valutazione e trattamento)|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>Definire le modalità con cui <strong>Nuova Agile Technology srl</strong> identifica, analizza, valuta e tratta i rischi per la sicurezza delle informazioni, in modo sistematico e ripetibile. Attua i requisiti ISO/IEC 27001:2022 cl. 6.1.2 (valutazione), 6.1.3 (trattamento), 8.2 e 8.3 (esecuzione) e supporta la determinazione delle misure ex art. 24 D.Lgs. 138/2024 (NIS2) per i soggetti rientranti.</p>
<h2>2. Ambito</h2>
<p>Si applica a tutte le informazioni e agli asset aziendali: codice sorgente e prodotti on-premise, piattaforma SaaS in cloud, dati personali dei clienti, infrastruttura cloud (Aruba IT, Hetzner DE, piattaforme AI) e PC portatili. Non essendoci server in sede, l'analisi considera i rischi di un modello <em>cloud-only</em> (responsabilità condivisa; controlli ISO/IEC 27017 e 27018 per le PII).</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 cl. 6.1.2, 6.1.3, 8.2, 8.3; controlli A.5.7, A.5.9, A.8.8.</li>
<li>ISO/IEC 27005 (stima del rischio) e ISO 31000.</li>
<li>ISO/IEC 27017 §CLD.6.3 e 27018 (rischi cloud e PII).</li>
<li>D.Lgs. 138/2024 art. 24; Determinazione ACN 164179/2025 [DA VERIFICARE riferimento applicabile].</li>
<li>SoA, Politica del SGSI, Procedura NC/Azioni Correttive.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<ul>
<li><strong>Direzione (Silvia Garretto)</strong>: approva criteri e propensione al rischio (risk appetite) e il piano di trattamento; accetta formalmente i rischi residui.</li>
<li><strong>RSGSI (Massimo Tagliavini)</strong>: conduce e coordina la valutazione, mantiene il registro dei rischi, propone il piano di trattamento, monitora le scadenze.</li>
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: fornisce l'inventario asset e le informazioni su minacce/vulnerabilità, implementa i controlli tecnici.</li>
<li><strong>DPO esterno</strong>: consultato per i rischi sui dati personali (PII nel SaaS).</li>
</ul>
<h2>5. Flusso passo-passo</h2>
<ol>
<li><strong>Contesto e criteri</strong> (input: SoA, inventario asset). RSGSI e Direzione fissano scala di probabilità (1–5) e impatto (1–5), soglia di accettabilità e criteri di significatività.</li>
<li><strong>Identificazione</strong> (input: asset, minacce, vulnerabilità). Per ciascun asset si individuano le minacce plausibili (es. compromissione credenziali cloud, data breach SaaS, perdita di un portatile, indisponibilità del provider) e si registra ogni rischio nel <em>modulo "Rischi"</em>.</li>
<li><strong>Analisi e ponderazione</strong>. Rischio = Probabilità × Impatto secondo la matrice 5×5 (coerente con ISO/IEC 27005); il punteggio determina la classe (basso/medio/alto/critico).</li>
<li><strong>Valutazione</strong>: confronto con la soglia. I rischi sopra soglia richiedono trattamento; quelli sotto soglia sono candidati all'accettazione.</li>
<li><strong>Trattamento</strong>. Per ogni rischio sopra soglia si sceglie l'opzione: <em>mitigazione</em> (controlli Annex A), <em>trasferimento</em> (clausole contrattuali col provider, assicurazione), <em>evitamento</em> o <em>accettazione</em>. I controlli scelti sono registrati come trattamenti e riconciliati con la SoA.</li>
<li><strong>Approvazione e accettazione del rischio residuo</strong>: la Direzione approva il piano e accetta formalmente i rischi residui (cl. 6.1.3 e).</li>
<li><strong>Attuazione e monitoraggio</strong>: il Resp. IT implementa i controlli; il RSGSI traccia stato e scadenze. Le carenze rilevate diventano Non Conformità.</li>
</ol>
<h2>6. Controlli ISO/clausole collegati</h2>
<p>cl. 6.1.2/6.1.3 (processo e piano), cl. 8.2/8.3 (esecuzione e SoA aggiornata); A.5.7 (threat intelligence), A.5.9 (inventario asset), A.8.8 (gestione vulnerabilità tecniche); per il cloud A.5.19–A.5.23 e i controlli estesi ISO/IEC 27017 e 27018 sulle PII.</p>
<h2>7. Registrazioni ed evidenze</h2>
<ul>
<li><strong>Registro dei rischi e trattamenti</strong>: modulo "Rischi" (rischio inerente, controlli, rischio residuo, owner, scadenze).</li>
<li><strong>Piano di trattamento e accettazione rischi residui</strong>: verbale di approvazione della Direzione, archiviato come informazione documentata.</li>
<li><strong>SoA aggiornata</strong> (modulo SGSI/SoA).</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>La valutazione è riesaminata <strong>almeno una volta l'anno</strong> e a ogni cambiamento significativo (nuovo prodotto/servizio, nuovo provider, incidente rilevante, modifica normativa). Gli esiti alimentano il Riesame della Direzione. La procedura è versionata secondo la Procedura di Controllo dei Documenti.</p>
+56
View File
@@ -0,0 +1,56 @@
<!--META|doc_type=procedura_incident|title=Procedura di Gestione degli Incidenti e Notifica NIS2|status=approved|version=2.0-->
<h2>1. Scopo</h2>
<p>Definire le modalità con cui <strong>Nuova Agile Technology srl</strong> (l'"Azienda") rileva, classifica, gestisce e — quando dovuto — notifica gli incidenti di sicurezza delle informazioni, assicurando risposta tempestiva, riduzione degli impatti e rispetto degli obblighi di notifica NIS2. Disciplina anche la <strong>comunicazione ai clienti impattati</strong> nel ruolo dell'Azienda quale fornitore di soggetti NIS2.</p>
<h2>2. Ambito</h2>
<p>Si applica a tutti gli incidenti che riguardano riservatezza, integrità o disponibilità di informazioni e servizi: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), <strong>SaaS</strong> multi-tenant, <strong>endpoint</strong> (PC portatili cifrati), <strong>piattaforme AI</strong> via API e i prodotti in licenza on-premise per la parte di responsabilità dell'Azienda. Non riguarda sale server in sede, <strong>assenti</strong> per l'architettura cloud-only.</p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – A.5.24, A.5.25, A.5.26, A.5.27, A.5.28 (gestione degli incidenti) e A.5.7 (threat intelligence).</li>
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – gestione degli incidenti nei servizi cloud e sui dati personali.</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (recepimento Direttiva (UE) 2022/2555 – NIS2): <strong>art. 23 (governance)</strong>, <strong>art. 24 (misure di gestione del rischio)</strong>, <strong>art. 25 (obblighi di notifica degli incidenti)</strong>.</li>
<li><strong>Determinazione ACN n. 164179/2025</strong> e Determinazioni vigenti per criteri di significatività, tassonomia e modalità di notifica al CSIRT Italia tramite la piattaforma ACN.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Decide la notifica alle Autorità e la comunicazione esterna; informa gli organi di gestione (art. 23).</td></tr>
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Coordina la gestione dell'incidente, cura le registrazioni e la relazione finale.</td></tr>
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Contiene, eradica e ripristina; raccoglie le evidenze tecniche.</td></tr>
<tr><td>DPO esterno</td><td><em>Consulente esterno</em></td><td>Valuta se vi è violazione di dati personali e gli obblighi GDPR verso il Garante.</td></tr>
</table>
<h2>5. Flusso passo-passo</h2>
<ol>
<li><strong>Rilevazione e segnalazione.</strong> Chiunque rilevi un evento sospetto lo segnala immediatamente a Simon Fattori. L'evento è registrato nel <strong>modulo "Incidenti"</strong> con data e ora di conoscenza.</li>
<li><strong>Triage e classificazione.</strong> Simon Fattori, con Massimo Tagliavini, valuta natura, origine e impatto (riservatezza/integrità/disponibilità) e assegna una severità. Si verifica se l'incidente è <strong>"significativo"</strong> secondo i criteri di legge e delle Determinazioni ACN (impatto operativo grave, numero di utenti coinvolti, durata, danno economico/reputazionale, effetti transfrontalieri). In caso di dubbio: <strong>[DA VERIFICARE]</strong> con la Direzione e il supporto consulenziale.</li>
<li><strong>Contenimento, eradicazione, ripristino.</strong> Simon Fattori isola sistemi/account compromessi, rimuove la causa e ripristina il servizio anche tramite backup, tracciando i tempi delle fasi.</li>
<li><strong>Notifica al CSIRT Italia (ACN)</strong> — se l'incidente è significativo, secondo le tempistiche dell'<strong>art. 25 del D.Lgs. 138/2024</strong>:
<ul>
<li><strong>Pre-allarme (early warning) entro 24 ore</strong> dalla conoscenza dell'incidente significativo;</li>
<li><strong>Notifica completa entro 72 ore</strong> dalla conoscenza;</li>
<li><strong>Relazione finale entro 1 mese</strong> dalla notifica completa (e relazioni intermedie se richieste).</li>
</ul>
La notifica avviene tramite la piattaforma ACN; decide e autorizza la Direzione.</li>
<li><strong>Valutazione dati personali.</strong> In caso di violazione di dati personali, il DPO valuta gli obblighi GDPR (eventuale notifica al Garante entro 72 ore e comunicazione agli interessati).</li>
<li><strong>Comunicazione ai clienti impattati.</strong> Quale <strong>fornitore di clienti NIS2</strong>, l'Azienda informa <strong>tempestivamente</strong> i clienti i cui servizi/dati sono coinvolti, secondo gli <strong>obblighi contrattuali di supply chain</strong>, fornendo gli elementi utili ai loro adempimenti di notifica.</li>
<li><strong>Chiusura e lezioni apprese.</strong> A risoluzione si redige la relazione finale, si aprono eventuali <strong>non conformità</strong> e azioni correttive e si aggiorna il <strong>modulo "Rischi"</strong> se emergono nuovi rischi.</li>
</ol>
<h2>6. Controlli ISO e riferimenti NIS2 (tempistiche)</h2>
<p>La procedura attua i controlli <strong>A.5.24–A.5.28</strong> e <strong>A.5.7</strong> di ISO/IEC 27001:2022, integrati dai controlli cloud di ISO/IEC 27017/27018, e dà attuazione agli <strong>artt. 23, 24 e 25 del D.Lgs. 138/2024</strong>. Le <strong>tempistiche esatte di notifica</strong> al CSIRT Italia sono: <strong>pre-allarme (early warning) 24 ore</strong>, <strong>notifica completa 72 ore</strong>, <strong>relazione finale 1 mese</strong>. I criteri di significatività seguono la <strong>Determinazione ACN n. 164179/2025</strong> e le Determinazioni vigenti.</p>
<h2>7. Registrazioni ed evidenze</h2>
<ul>
<li>Scheda incidente nel modulo "Incidenti" (cronologia, severità, classificazione);</li>
<li>Evidenze tecniche raccolte (log, immagini, comunicazioni), preservate ai sensi di A.5.28;</li>
<li>Ricevute di notifica al CSIRT/ACN e relazione finale;</li>
<li>Comunicazioni ai clienti e, se del caso, al DPO/Garante;</li>
<li>Non conformità e azioni correttive collegate.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>Procedura approvata dalla Presidente. Revisione almeno annuale, dopo ogni incidente significativo o a fronte di aggiornamenti delle Determinazioni ACN. Versionata secondo la Procedura di Controllo dei Documenti.</p>
+49
View File
@@ -0,0 +1,49 @@
<!--META|doc_type=manuale_sgsi|title=Manuale del Sistema di Gestione per la Sicurezza delle Informazioni (SGSI)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Il presente Manuale descrive il Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) di <strong>Nuova Agile Technology srl</strong> (di seguito "l'Organizzazione" o "l'Azienda"), progettato e mantenuto in conformità alla norma <strong>ISO/IEC 27001:2022</strong>, integrata dalle linee guida <strong>ISO/IEC 27017:2015</strong> (controlli di sicurezza per i servizi cloud) e <strong>ISO/IEC 27018:2019</strong> (protezione dei dati personali nel cloud pubblico). Il Manuale costituisce il documento di vertice della struttura documentale del SGSI e ne illustra contesto, leadership, ambito, approccio al rischio e processo di miglioramento continuo.</p>
<h2>2. Ambito del SGSI</h2>
<p>Il SGSI si applica a tutti i processi di progettazione, sviluppo, erogazione e supporto dei servizi e prodotti software dell'Azienda, alle informazioni trattate (proprie e dei clienti, inclusi i dati personali) e a tutto il personale (dipendenti e collaboratori). Le due linee di business coperte sono:</p>
<ul>
<li><strong>Prodotti software in licenza d'uso</strong> installati on-premise sui server <em>dei clienti</em>;</li>
<li><strong>Servizi SaaS multi-tenant</strong> erogati in cloud.</li>
</ul>
<p>L'infrastruttura è <strong>interamente in cloud</strong> presso <strong>Aruba S.p.A.</strong> (data center in Italia) e <strong>Hetzner Online GmbH</strong> (Germania, UE), con utilizzo di piattaforme di intelligenza artificiale tramite API (es. Anthropic). <strong>L'Azienda non gestisce alcun server in sede</strong>: le postazioni di lavoro sono esclusivamente <strong>PC portatili cifrati</strong>. La sicurezza fisica dei data center è ereditata dai fornitori cloud secondo il modello di <em>responsabilità condivisa</em> (vedi A.5.23).</p>
<h2>3. Contesto dell'Organizzazione</h2>
<p>Nuova Agile Technology srl è una software house italiana composta da circa 12 persone (9 dipendenti, 2 collaboratori esterni a P.IVA e la Presidente). I principali fattori di contesto interni ed esterni sono: dipendenza da fornitori cloud e da provider AI, requisiti contrattuali di sicurezza imposti dai clienti (supply chain), obblighi GDPR sui dati personali trattati nel SaaS e il quadro normativo NIS2. Le parti interessate rilevanti includono clienti, fornitori cloud/AI (sub-responsabili), personale, Autorità di controllo (Garante Privacy, ACN) e investitori.</p>
<h2>4. Ruoli e responsabilità (organigramma SGSI)</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità principali</th></tr>
<tr><td>Alta Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Leadership, approvazione politiche, assegnazione risorse, riesame di direzione</td></tr>
<tr><td>Responsabile SGSI (RSGSI)</td><td><strong>Massimo Tagliavini</strong></td><td>Gestione operativa del SGSI, risk management, audit interni, documentazione</td></tr>
<tr><td>Responsabile IT e Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Sicurezza tecnica, gestione cloud, controllo accessi, gestione incidenti</td></tr>
<tr><td>Referente Protezione Dati (DPO)</td><td><em>Consulente esterno (da nominare)</em></td><td>Conformità GDPR, pareri sul trattamento dei dati personali</td></tr>
</table>
<p>L'organigramma di dettaglio e la matrice RACI sono mantenuti nel registro dei ruoli del SGSI.</p>
<h2>5. Leadership e impegno della Direzione</h2>
<p>L'Alta Direzione, nella persona della Presidente, garantisce l'impegno verso il SGSI mediante: definizione e approvazione della Politica per la Sicurezza delle Informazioni; integrazione del SGSI nei processi aziendali; messa a disposizione delle risorse; promozione del miglioramento continuo; comunicazione dell'importanza della sicurezza al personale. Tali impegni soddisfano i requisiti della clausola 5 di ISO/IEC 27001 e i controlli <strong>A.5.1</strong> (Politiche per la sicurezza delle informazioni) e <strong>A.5.4</strong> (Responsabilità della direzione).</p>
<h2>6. Struttura documentale del SGSI</h2>
<p>La documentazione è organizzata su livelli gerarchici: (1) Manuale SGSI e Politica generale; (2) Politiche tematiche (controllo accessi, crittografia, classificazione, uso accettabile, ecc.); (3) Procedure operative; (4) Registrazioni ed evidenze. Tutti i documenti sono soggetti a controllo di versione, approvazione e revisione periodica (A.5.37).</p>
<h2>7. Approccio al rischio</h2>
<p>Il SGSI adotta un processo di valutazione e trattamento del rischio basato su ISO/IEC 27005, che prevede identificazione degli asset informativi e dei relativi rischi, analisi di probabilità e impatto, definizione del rischio accettabile e selezione delle opzioni di trattamento (mitigazione, trasferimento, accettazione, eliminazione). Particolare attenzione è posta ai rischi della <strong>supply chain cloud/AI</strong> e ai rischi sui dati personali. Gli esiti sono documentati nel Registro dei Rischi e riferiti alla <strong>Dichiarazione di Applicabilità (SoA)</strong>.</p>
<h2>8. Dichiarazione di Applicabilità (SoA)</h2>
<p>La SoA elenca i controlli dell'<strong>Annex A di ISO/IEC 27001:2022</strong> (93 controlli su 4 temi: organizzativi, persone, fisici, tecnologici), integrati dai controlli aggiuntivi di <strong>ISO/IEC 27017</strong> e <strong>27018</strong> per i servizi cloud e i dati personali. Per ciascun controllo sono indicati applicabilità, stato di implementazione e giustificazione delle esclusioni.</p>
<h2>9. CAVEAT sulla classificazione NIS2</h2>
<p>L'Azienda si autoclassifica <strong>in via PRELIMINARE</strong> come soggetto NIS2 "<strong>IMPORTANTE</strong>". Tuttavia l'Azienda è <strong>SOTTO le soglie dimensionali ordinarie</strong> (≥50 addetti oppure fatturato/bilancio >10 M€): la qualifica deve essere <strong>CONFERMATA dalla Direzione e dal Legale</strong>, ed eventualmente da ACN, in funzione del fatturato e della categoria di attività (provider di servizi cloud). L'adozione del SGSI è guidata anche dai <strong>requisiti di supply chain dei clienti</strong>. Il quadro di riferimento è la Direttiva (UE) 2022/2555, recepita in Italia dal <strong>D.Lgs. 138/2024</strong>: art.23 (governance), art.24 (misure di gestione del rischio, equivalenti all'Art.21 della Direttiva), art.25 (obblighi di notifica). Le misure di dettaglio sono definite dalle Determinazioni ACN. <strong>Le norme ISO citate sono buone prassi, non obblighi di legge.</strong></p>
<h2>10. Miglioramento continuo</h2>
<p>Il SGSI segue il ciclo PDCA. Sono previsti audit interni programmati, riesami di direzione almeno annuali, gestione delle non conformità e azioni correttive (NCR/CAPA), e monitoraggio di indicatori di prestazione. Gli esiti alimentano il piano di miglioramento.</p>
<h2>11. Registrazioni ed evidenze</h2>
<p>Verbali di riesame di direzione, registro dei rischi, SoA, rapporti di audit, registro delle non conformità, log di sicurezza e registro degli incidenti.</p>
<h2>12. Riesame e versionamento</h2>
<p>Documento approvato dalla Presidente. Revisione almeno annuale o a fronte di cambiamenti significativi. Versione 1.0.</p>
+50
View File
@@ -0,0 +1,50 @@
<!--META|doc_type=politica_sgsi|title=Politica per la Sicurezza delle Informazioni|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente Politica esprime l'impegno formale dell'Alta Direzione di <strong>Nuova Agile Technology srl</strong> verso la protezione della riservatezza, integrità e disponibilità delle informazioni proprie e di quelle affidate dai clienti, inclusi i dati personali. Costituisce il documento di indirizzo del Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) conforme a <strong>ISO/IEC 27001:2022</strong> e alle linee guida <strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> per i servizi cloud e i dati personali.</p>
<h2>2. Ambito</h2>
<p>La Politica si applica a tutto il personale (dipendenti e collaboratori a P.IVA), a tutte le informazioni trattate, ai prodotti software in licenza d'uso installati presso i clienti e ai servizi SaaS multi-tenant erogati in cloud. L'infrastruttura è interamente cloud (<strong>Aruba</strong>, Italia; <strong>Hetzner</strong>, Germania-UE) con piattaforme AI via API; non esistono server gestiti in sede e le postazioni sono solo PC portatili cifrati.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022, 27017:2015, 27018:2019</li>
<li>Regolamento (UE) 2016/679 (GDPR)</li>
<li>Direttiva (UE) 2022/2555 (NIS2) e D.Lgs. 138/2024</li>
<li>Manuale SGSI, Dichiarazione di Applicabilità (SoA)</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<p>La <strong>Presidente Silvia Garretto</strong> (Alta Direzione) approva e sostiene la Politica. Il <strong>Responsabile SGSI Massimo Tagliavini</strong> ne cura l'attuazione e il monitoraggio. Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> presidia le misure tecniche. Il <strong>Referente Protezione Dati (DPO, consulente esterno da nominare)</strong> supervisiona la conformità GDPR. Ogni membro del personale è responsabile del rispetto della Politica nell'ambito delle proprie attività.</p>
<h2>5. Principi e impegni</h2>
<p>L'Alta Direzione si impegna a:</p>
<ul>
<li><strong>Riservatezza</strong>: garantire che le informazioni siano accessibili solo a chi è autorizzato, applicando il principio del minimo privilegio e la separazione dei tenant nel SaaS.</li>
<li><strong>Integrità</strong>: assicurare l'accuratezza e la completezza delle informazioni e dei sistemi, con controlli su modifiche e sviluppo software sicuro.</li>
<li><strong>Disponibilità</strong>: garantire l'accesso ai servizi secondo i livelli concordati, mediante backup, continuità operativa e gestione resiliente del cloud.</li>
<li><strong>Conformità</strong>: rispettare gli obblighi legali e contrattuali, in particolare GDPR e i requisiti NIS2 ove applicabili.</li>
<li><strong>Gestione del rischio</strong>: identificare, valutare e trattare i rischi in modo sistematico, con attenzione alla supply chain cloud e AI.</li>
<li><strong>Consapevolezza</strong>: formare e sensibilizzare il personale sui temi di sicurezza.</li>
<li><strong>Miglioramento continuo</strong>: riesaminare periodicamente il SGSI e adottare azioni correttive e preventive.</li>
<li><strong>Gestione degli incidenti</strong>: rilevare, gestire e segnalare tempestivamente gli incidenti di sicurezza e le violazioni di dati personali.</li>
</ul>
<p>L'Azienda adotta una <strong>politica di tolleranza zero</strong> verso comportamenti che mettano deliberatamente a rischio la sicurezza delle informazioni.</p>
<h2>6. Obiettivi di sicurezza</h2>
<p>Gli obiettivi misurabili del SGSI sono definiti annualmente dalla Direzione e includono, a titolo esemplificativo: copertura della formazione del personale, tempi di risposta agli incidenti, percentuale di rischi trattati entro i termini, livello di conformità dei fornitori cloud. Gli obiettivi sono monitorati e riesaminati nel riesame di direzione.</p>
<h2>7. Controlli ISO collegati e riferimenti NIS2</h2>
<p>Controlli Annex A pertinenti: <strong>A.5.1</strong> (Politiche per la sicurezza delle informazioni), <strong>A.5.2</strong> (Ruoli e responsabilità), <strong>A.5.4</strong> (Responsabilità della direzione), <strong>A.5.31</strong> (Requisiti legali e contrattuali), <strong>A.5.36</strong> (Conformità alle politiche). Riferimenti NIS2 (D.Lgs. 138/2024): <strong>art.23</strong> (governance e ruolo degli organi di gestione), <strong>art.24</strong> (misure di gestione del rischio). Le ISO costituiscono buone prassi, non obblighi di legge.</p>
<h2>8. Caveat NIS2</h2>
<p>L'Azienda si autoclassifica in via <strong>preliminare</strong> come soggetto NIS2 "<strong>IMPORTANTE</strong>", pur essendo <strong>sotto le soglie dimensionali ordinarie</strong>: la qualifica va <strong>confermata da Direzione e Legale</strong>, ed eventualmente da ACN, in base a fatturato e categoria di attività. <strong>[DA VERIFICARE]</strong> in sede di registrazione presso ACN.</p>
<h2>9. Comunicazione e applicazione</h2>
<p>La Politica è comunicata a tutto il personale e accessibile in qualsiasi momento. La sua violazione può comportare provvedimenti disciplinari secondo le norme contrattuali e di legge vigenti.</p>
<h2>10. Registrazioni ed evidenze</h2>
<p>Documento firmato dalla Presidente, evidenza di comunicazione al personale, verbali di riesame.</p>
<h2>11. Riesame e versionamento</h2>
<p>Approvata dall'Alta Direzione. Riesame almeno annuale o a fronte di modifiche significative del contesto. Versione 1.0.</p>
+60
View File
@@ -0,0 +1,60 @@
<!--META|doc_type=politica_controllo_accessi|title=Politica di Controllo degli Accessi|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Definire le regole con cui <strong>Nuova Agile Technology srl</strong> autorizza, gestisce e revoca gli accessi logici alle informazioni, ai sistemi cloud (Aruba, Hetzner), alle piattaforme AI, agli ambienti di sviluppo e ai servizi SaaS multi-tenant, al fine di garantire che solo gli utenti autorizzati accedano alle risorse strettamente necessarie al proprio ruolo.</p>
<h2>2. Ambito</h2>
<p>La Politica si applica a tutti gli accessi logici di dipendenti, collaboratori a P.IVA e — ove pertinente — di clienti e fornitori. Riguarda: account dei provider cloud, repository di codice, ambienti di produzione e staging, console di amministrazione SaaS, chiavi API (incluse quelle dei provider AI), database e strumenti di collaborazione. Non sono presenti accessi fisici a server in sede, in quanto l'infrastruttura è interamente cloud; la sicurezza fisica dei data center è in capo ai fornitori (responsabilità condivisa).</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 Annex A (controlli A.5.15–A.5.18, A.8.2–A.8.5)</li>
<li>ISO/IEC 27017:2015 (gestione accessi in ambienti cloud), 27018:2019 (accesso ai dati personali)</li>
<li>Politica per la Sicurezza delle Informazioni; Politica di Crittografia</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<p>Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> gestisce operativamente account, privilegi e credenziali. Il <strong>Responsabile SGSI Massimo Tagliavini</strong> approva le matrici di accesso e supervisiona i riesami periodici. L'<strong>Alta Direzione (Silvia Garretto)</strong> autorizza i profili di accesso privilegiato. Il <strong>DPO (consulente esterno)</strong> è consultato per gli accessi che coinvolgono dati personali dei clienti.</p>
<h2>5. Regole di controllo degli accessi</h2>
<h3>5.1 Principi fondamentali</h3>
<ul>
<li><strong>Minimo privilegio</strong>: a ogni utente sono concessi solo i diritti necessari al ruolo.</li>
<li><strong>Need-to-know</strong>: l'accesso alle informazioni è limitato a chi ne ha effettiva necessità.</li>
<li><strong>Segregazione dei compiti</strong>: ove possibile, separazione tra chi sviluppa, chi rilascia in produzione e chi amministra.</li>
<li><strong>Default deny</strong>: in assenza di autorizzazione esplicita, l'accesso è negato.</li>
</ul>
<h3>5.2 Gestione del ciclo di vita degli account</h3>
<ul>
<li><strong>Provisioning</strong>: gli account sono creati su richiesta formale e con approvazione del responsabile, sulla base del ruolo (RBAC).</li>
<li><strong>Modifica</strong>: i cambi di ruolo comportano la revisione tempestiva dei privilegi.</li>
<li><strong>Deprovisioning</strong>: alla cessazione del rapporto o del contratto, tutti gli accessi sono revocati entro 24 ore, incluse chiavi API e accessi ai provider cloud/AI.</li>
</ul>
<h3>5.3 Autenticazione</h3>
<ul>
<li><strong>Autenticazione a più fattori (MFA)</strong> obbligatoria per: console cloud Aruba/Hetzner, repository di codice, accessi amministrativi al SaaS, account di posta e SSO.</li>
<li>Password robuste secondo standard aziendale; uso obbligatorio di un <strong>password manager</strong> approvato.</li>
<li>Divieto di condivisione delle credenziali; ogni account è nominativo.</li>
</ul>
<h3>5.4 Accessi privilegiati e chiavi API</h3>
<ul>
<li>Gli account amministrativi sono ridotti al minimo, nominativi e monitorati.</li>
<li>Le <strong>chiavi API</strong> (cloud, AI, integrazioni) sono custodite in un vault cifrato, mai inserite in chiaro nel codice o committate nei repository, e ruotate periodicamente o in caso di sospetta compromissione.</li>
<li>Gli accessi privilegiati sono soggetti a logging e revisione.</li>
</ul>
<h3>5.5 Accesso nel SaaS multi-tenant</h3>
<ul>
<li>Isolamento logico dei tenant: ogni cliente accede esclusivamente ai propri dati.</li>
<li>I dati personali dei clienti sono accessibili al personale dell'Azienda solo per finalità di erogazione e supporto, con tracciabilità (rif. ISO 27018).</li>
</ul>
<h3>5.6 Riesame periodico</h3>
<p>Gli accessi e i privilegi sono <strong>riesaminati almeno ogni 6 mesi</strong> dal Responsabile IT, con verifica e rimozione degli account obsoleti o non più necessari. Gli esiti sono registrati.</p>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<p><strong>A.5.15</strong> (Controllo degli accessi), <strong>A.5.16</strong> (Gestione delle identità), <strong>A.5.17</strong> (Informazioni di autenticazione), <strong>A.5.18</strong> (Diritti di accesso), <strong>A.8.2</strong> (Diritti di accesso privilegiati), <strong>A.8.3</strong> (Restrizione dell'accesso alle informazioni), <strong>A.8.5</strong> (Autenticazione sicura). Riferimento NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, tra cui controllo degli accessi e uso dell'autenticazione a più fattori).</p>
<h2>7. Registrazioni ed evidenze</h2>
<p>Matrice dei ruoli e dei privilegi (RBAC), registro delle richieste di accesso, verbali dei riesami semestrali, log degli accessi privilegiati, inventario delle chiavi API.</p>
<h2>8. Riesame e versionamento</h2>
<p>Approvata dal Responsabile SGSI e dall'Alta Direzione. Revisione almeno annuale. Versione 1.0.</p>
+55
View File
@@ -0,0 +1,55 @@
<!--META|doc_type=politica_crittografia|title=Politica di Crittografia e Gestione delle Chiavi|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Definire i criteri con cui <strong>Nuova Agile Technology srl</strong> impiega la crittografia per proteggere la riservatezza e l'integrità delle informazioni — in particolare i dati personali dei clienti trattati nel SaaS — e per gestire in modo sicuro le chiavi crittografiche e i segreti applicativi durante l'intero ciclo di vita.</p>
<h2>2. Ambito</h2>
<p>Si applica ai dati a riposo nei servizi cloud (Aruba, Hetzner), ai dati in transito tra utenti, servizi e provider AI, ai PC portatili del personale, ai backup, ai segreti applicativi (chiavi API cloud e AI, credenziali di database, token) e ai certificati TLS dei servizi esposti. Riguarda sia i servizi SaaS sia i prodotti in licenza installati presso i clienti, per la parte di configurazione crittografica raccomandata.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 Annex A (A.8.24, A.8.5, A.5.33)</li>
<li>ISO/IEC 27017:2015 e 27018:2019 (crittografia dei dati personali nel cloud)</li>
<li>GDPR art. 32 (misure tecniche, tra cui cifratura); Politica di Controllo degli Accessi</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<p>Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> definisce e gestisce gli algoritmi, le chiavi e il vault dei segreti. Il <strong>Responsabile SGSI Massimo Tagliavini</strong> verifica la conformità della Politica. Il <strong>DPO (consulente esterno)</strong> è consultato per la cifratura dei dati personali. L'<strong>Alta Direzione (Silvia Garretto)</strong> approva la Politica.</p>
<h2>5. Regole di crittografia</h2>
<h3>5.1 Dati in transito</h3>
<ul>
<li>Tutte le comunicazioni esterne avvengono tramite <strong>TLS 1.2 o superiore</strong> (preferibilmente TLS 1.3); i protocolli e le cifrature obsolete sono disabilitati.</li>
<li>Le connessioni tra applicazione e database e tra microservizi adottano canali cifrati ove tecnicamente possibile.</li>
<li>Le chiamate alle API dei provider AI e cloud avvengono esclusivamente su canali cifrati.</li>
</ul>
<h3>5.2 Dati a riposo</h3>
<ul>
<li>I dati a riposo nei servizi cloud sono cifrati; i database e i volumi che contengono dati personali utilizzano cifratura (es. AES-256) secondo le funzionalità del provider.</li>
<li>I <strong>PC portatili</strong> del personale hanno la <strong>cifratura completa del disco</strong> attiva (es. BitLocker/FileVault/LUKS).</li>
<li>I <strong>backup</strong> sono cifrati e conservati in modo sicuro.</li>
</ul>
<h3>5.3 Algoritmi ammessi</h3>
<ul>
<li>Cifratura simmetrica: <strong>AES-256</strong> (modalità autenticate come GCM).</li>
<li>Hashing password: funzioni dedicate e resistenti (es. <strong>bcrypt/Argon2</strong>); vietato l'uso di MD5/SHA-1 per scopi di sicurezza.</li>
<li>Firma/integrità: <strong>HMAC-SHA-256</strong> o equivalente; certificati basati su algoritmi robusti.</li>
</ul>
<h3>5.4 Gestione delle chiavi e dei segreti</h3>
<ul>
<li>I segreti (chiavi API, credenziali, token) sono custoditi in un <strong>vault cifrato centralizzato</strong>, mai in chiaro nel codice sorgente, nei file di configurazione versionati o nei log.</li>
<li>Le chiavi sono soggette a <strong>rotazione periodica</strong> e a rotazione immediata in caso di sospetta compromissione (chiavi, leak, cessazione personale con accesso).</li>
<li>L'accesso alle chiavi segue il principio del minimo privilegio ed è registrato.</li>
<li>I certificati TLS sono monitorati per la scadenza e rinnovati tempestivamente.</li>
</ul>
<h3>5.5 Provider AI e dati</h3>
<p>Le informazioni inviate alle piattaforme AI sono trasmesse su canali cifrati e, ove possibile, <strong>minimizzate/anonimizzate</strong> per evitare l'esposizione di dati personali o riservati non necessari all'elaborazione.</p>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<p><strong>A.8.24</strong> (Uso della crittografia), <strong>A.8.5</strong> (Autenticazione sicura), <strong>A.5.33</strong> (Protezione delle registrazioni). Riferimenti cloud: ISO/IEC 27017/27018. Riferimento NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, incluse politiche e procedure relative all'uso della crittografia). Le ISO sono buone prassi, non obblighi di legge.</p>
<h2>7. Registrazioni ed evidenze</h2>
<p>Inventario delle chiavi e dei certificati, log di rotazione, configurazioni TLS, evidenza della cifratura disco sui PC portatili, registro degli accessi al vault.</p>
<h2>8. Riesame e versionamento</h2>
<p>Approvata dal Responsabile SGSI. Revisione almeno annuale o all'emergere di nuove vulnerabilità crittografiche. Versione 1.0.</p>
+57
View File
@@ -0,0 +1,57 @@
<!--META|doc_type=politica_classificazione|title=Politica di Classificazione e Trattamento delle Informazioni|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Stabilire come <strong>Nuova Agile Technology srl</strong> classifica le informazioni in base alla loro sensibilità e criticità e quali misure di trattamento, protezione e conservazione applicare a ciascun livello, lungo l'intero ciclo di vita (creazione, uso, condivisione, archiviazione, cancellazione).</p>
<h2>2. Ambito</h2>
<p>Si applica a tutte le informazioni gestite dall'Azienda, indipendentemente dal formato e dal supporto: codice sorgente, documentazione di progetto, dati dei servizi SaaS (inclusi i <strong>dati personali dei clienti</strong>), credenziali e segreti, documenti amministrativi e contrattuali, comunicazioni interne. Riguarda i dati nel cloud (Aruba, Hetzner), sui PC portatili e presso le piattaforme AI.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 Annex A (A.5.12, A.5.13, A.5.14, A.5.10, A.8.10, A.8.12)</li>
<li>ISO/IEC 27018:2019 (protezione dei dati personali nel cloud)</li>
<li>GDPR; Politica di Crittografia; Politica di Controllo degli Accessi</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<p>Il <strong>Responsabile SGSI Massimo Tagliavini</strong> mantiene lo schema di classificazione e ne supervisiona l'applicazione. Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> implementa le misure tecniche per livello. Il <strong>DPO (consulente esterno)</strong> presidia il corretto trattamento dei dati personali. Ogni dipendente/collaboratore, in qualità di <em>proprietario o utilizzatore</em> dell'informazione, è responsabile della sua corretta classificazione e gestione.</p>
<h2>5. Schema di classificazione</h2>
<table>
<tr><th>Livello</th><th>Descrizione</th><th>Esempi</th></tr>
<tr><td><strong>Pubblico</strong></td><td>Informazioni destinate alla diffusione, senza impatto se divulgate.</td><td>Materiale marketing, documentazione pubblica del prodotto</td></tr>
<tr><td><strong>Interno</strong></td><td>Uso interno; divulgazione non autorizzata con impatto limitato.</td><td>Procedure interne, comunicazioni di team</td></tr>
<tr><td><strong>Riservato</strong></td><td>Informazioni sensibili aziendali o di terzi; impatto significativo se divulgate.</td><td>Codice sorgente proprietario, contratti, dati clienti non personali</td></tr>
<tr><td><strong>Strettamente riservato</strong></td><td>Massima sensibilità; impatto grave (legale, reputazionale, sanzionatorio).</td><td>Dati personali dei clienti nel SaaS, credenziali, chiavi API, segreti</td></tr>
</table>
<h2>6. Regole di trattamento per livello</h2>
<h3>6.1 Etichettatura</h3>
<p>I documenti e i repository riportano, ove pertinente, il livello di classificazione. I dati personali sono trattati come <strong>Strettamente riservati</strong> per impostazione predefinita.</p>
<h3>6.2 Misure minime</h3>
<ul>
<li><strong>Pubblico</strong>: nessuna restrizione di accesso particolare; verifica di accuratezza prima della pubblicazione.</li>
<li><strong>Interno</strong>: accesso limitato al personale; non condividere all'esterno senza autorizzazione.</li>
<li><strong>Riservato</strong>: accesso su base need-to-know; trasmissione cifrata; conservazione nei sistemi aziendali autorizzati; divieto di copia su supporti non controllati.</li>
<li><strong>Strettamente riservato</strong>: cifratura obbligatoria a riposo e in transito; accesso con MFA e minimo privilegio; tracciabilità degli accessi; divieto di trasmissione a piattaforme AI senza minimizzazione/anonimizzazione; nessuna conservazione su supporti personali.</li>
</ul>
<h3>6.3 Dati personali nel SaaS (ISO 27018 / GDPR)</h3>
<ul>
<li>Trattamento secondo le istruzioni documentate dei clienti (in qualità di responsabile del trattamento), con isolamento per tenant.</li>
<li>Applicazione dei principi di minimizzazione e limitazione della conservazione.</li>
<li>Supporto ai diritti degli interessati e gestione delle richieste di cancellazione.</li>
</ul>
<h3>6.4 Conservazione e cancellazione sicura</h3>
<ul>
<li>I periodi di conservazione sono definiti per categoria e in base a obblighi legali e contrattuali.</li>
<li>La cancellazione avviene in modo sicuro (cancellazione logica irreversibile dei dati nei sistemi cloud, distruzione delle copie); per i PC portatili la dismissione prevede wipe sicuro o riformattazione con disco già cifrato.</li>
</ul>
<h2>7. Controlli ISO collegati e riferimenti NIS2</h2>
<p><strong>A.5.12</strong> (Classificazione delle informazioni), <strong>A.5.13</strong> (Etichettatura delle informazioni), <strong>A.5.14</strong> (Trasferimento delle informazioni), <strong>A.5.10</strong> (Uso accettabile delle informazioni e degli asset), <strong>A.8.10</strong> (Cancellazione delle informazioni), <strong>A.8.12</strong> (Prevenzione della fuga di dati). Cloud: ISO/IEC 27018. Riferimento NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, incluse politiche di sicurezza delle informazioni).</p>
<h2>8. Registrazioni ed evidenze</h2>
<p>Schema di classificazione, inventario degli asset informativi con livello, registro dei trattamenti (GDPR), evidenze di cancellazione sicura.</p>
<h2>9. Riesame e versionamento</h2>
<p>Approvata dal Responsabile SGSI. Revisione almeno annuale. Versione 1.0.</p>
+55
View File
@@ -0,0 +1,55 @@
<!--META|doc_type=politica_uso_accettabile|title=Politica d'Uso Accettabile e Sicurezza degli Endpoint (PC portatili)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Definire le regole di comportamento per l'uso accettabile degli strumenti informatici, dei servizi e delle informazioni di <strong>Nuova Agile Technology srl</strong>, con particolare attenzione alla sicurezza dei <strong>PC portatili</strong>, unico tipo di postazione di lavoro dell'Azienda, in un contesto interamente cloud e con lavoro anche da remoto.</p>
<h2>2. Ambito</h2>
<p>Si applica a tutti i dipendenti e collaboratori a P.IVA che utilizzano dispositivi, account e servizi aziendali (cloud Aruba/Hetzner, repository di codice, posta, strumenti di collaborazione, piattaforme AI). Non essendoci server in sede, la Politica si concentra sugli <strong>endpoint mobili</strong> e sull'uso responsabile dei servizi cloud.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 Annex A (A.5.10, A.6.7, A.8.1, A.8.7, A.8.9, A.7.9)</li>
<li>Politica per la Sicurezza delle Informazioni; Politica di Controllo degli Accessi; Politica di Classificazione</li>
<li>GDPR; Direttiva NIS2 / D.Lgs. 138/2024</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<p>Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> configura e mantiene gli endpoint sicuri (cifratura, aggiornamenti, antimalware). Il <strong>Responsabile SGSI Massimo Tagliavini</strong> vigila sul rispetto della Politica. L'<strong>Alta Direzione (Silvia Garretto)</strong> la approva. Ogni utente è personalmente responsabile dell'uso corretto degli strumenti assegnati.</p>
<h2>5. Regole d'uso accettabile</h2>
<h3>5.1 Comportamenti consentiti e vietati</h3>
<ul>
<li>Gli strumenti aziendali sono destinati prevalentemente a finalità professionali; un uso personale limitato e ragionevole è tollerato se non compromette sicurezza e produttività.</li>
<li>È <strong>vietato</strong>: installare software non autorizzato o da fonti non attendibili; disattivare le misure di sicurezza (cifratura, antimalware, aggiornamenti); condividere credenziali; aggirare i controlli di accesso; trattare dati aziendali su servizi personali non approvati.</li>
<li>È vietato inviare a piattaforme AI informazioni <strong>riservate o personali</strong> senza minimizzazione/anonimizzazione e senza approvazione, in coerenza con la Politica di Classificazione.</li>
</ul>
<h3>5.2 Sicurezza dei PC portatili (endpoint)</h3>
<ul>
<li><strong>Cifratura completa del disco</strong> attiva su tutti i portatili (BitLocker/FileVault/LUKS).</li>
<li><strong>Blocco automatico</strong> dello schermo dopo inattività e password/PIN robusti all'avvio.</li>
<li><strong>Aggiornamenti</strong> di sistema operativo e applicazioni installati tempestivamente; <strong>antimalware</strong> attivo e aggiornato.</li>
<li><strong>Firewall</strong> locale attivo; servizi non necessari disabilitati.</li>
<li>I dati di lavoro risiedono nei servizi cloud aziendali; si evita l'accumulo di dati riservati in locale e l'uso di supporti rimovibili non cifrati.</li>
<li>Smarrimento o furto del dispositivo va segnalato <strong>immediatamente</strong> al Responsabile IT per la revoca degli accessi e, se possibile, il blocco/wipe remoto.</li>
</ul>
<h3>5.3 Lavoro da remoto</h3>
<ul>
<li>Connessioni a reti affidabili; uso di reti pubbliche solo con cautela e canali cifrati (TLS/VPN ove previsto).</li>
<li>Protezione dalla visione altrui dello schermo in luoghi pubblici; dispositivi mai lasciati incustoditi.</li>
</ul>
<h3>5.4 Posta e phishing</h3>
<ul>
<li>Prudenza con allegati e link sospetti; segnalazione tempestiva dei tentativi di phishing o social engineering al Responsabile IT.</li>
<li>Verifica dell'identità del mittente prima di azioni sensibili (es. cambi di pagamento, invio credenziali).</li>
</ul>
<h3>5.5 Segnalazione degli eventi di sicurezza</h3>
<p>Ogni anomalia, sospetta compromissione o incidente va segnalato senza ritardo al Responsabile IT/SGSI, per consentire la valutazione e, se necessario, la notifica secondo gli obblighi NIS2 (pre-allarme entro 24 ore, notifica entro 72 ore, relazione finale entro 1 mese al CSIRT Italia/ACN, ove l'Azienda risulti soggetto obbligato).</p>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<p><strong>A.5.10</strong> (Uso accettabile delle informazioni e degli asset), <strong>A.6.7</strong> (Lavoro da remoto), <strong>A.8.1</strong> (Dispositivi endpoint degli utenti), <strong>A.8.7</strong> (Protezione contro il malware), <strong>A.8.9</strong> (Gestione della configurazione), <strong>A.7.9</strong> (Sicurezza degli asset fuori sede). Riferimenti NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, tra cui igiene informatica di base e formazione) e <strong>art.25</strong> (obblighi di notifica degli incidenti). Le ISO sono buone prassi, non obblighi di legge.</p>
<h2>7. Registrazioni ed evidenze</h2>
<p>Accettazione della Politica da parte del personale, inventario degli endpoint con stato di cifratura e aggiornamento, registro delle segnalazioni di eventi/incidenti.</p>
<h2>8. Riesame e versionamento</h2>
<p>Approvata dall'Alta Direzione. Revisione almeno annuale o a fronte di nuove minacce rilevanti. Versione 1.0.</p>
+72
View File
@@ -0,0 +1,72 @@
<!--META|doc_type=politica_fornitori|title=Politica di Sicurezza dei Fornitori|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> seleziona, qualifica, contrattualizza e sorveglia i propri fornitori, con particolare attenzione a quelli che erogano servizi rilevanti per la sicurezza delle informazioni (cloud, AI, sviluppo, manutenzione). Lo scopo è garantire che il livello di sicurezza richiesto dal Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) sia mantenuto anche quando attività o dati sono affidati a terze parti, e che gli obblighi di sicurezza della catena di fornitura previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (recepimento NIS2) siano rispettati e propagati lungo la supply chain.</p>
<h2>2. Ambito</h2>
<p>La Politica si applica a tutti i rapporti con fornitori, sub-fornitori e prestatori di servizi che: (a) trattano, ospitano o accedono a informazioni dell'azienda o dei suoi clienti; (b) erogano infrastruttura cloud (Aruba S.p.A. – Italia; Hetzner – Germania, UE); (c) forniscono servizi di intelligenza artificiale tramite API (es. modelli LLM Anthropic); (d) contribuiscono allo sviluppo, alla manutenzione o all'assistenza dei prodotti software, sia in licenza d'uso installati presso i clienti, sia SaaS multi-tenant. Si applica a tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) coinvolto nella gestione dei fornitori.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 – Annex A, controlli A.5.19, A.5.20, A.5.21, A.5.22, A.5.23.</li>
<li>ISO/IEC 27017:2015 – controlli cloud-specifici e modello di responsabilità condivisa.</li>
<li>ISO/IEC 27018:2019 – tutela dei dati personali (PII) trattati nel cloud.</li>
<li>D.Lgs. 138/2024, art. 24 (misure di gestione del rischio, sicurezza della supply chain).</li>
<li>Regolamento (UE) 2016/679 (GDPR), art. 28 (responsabili del trattamento).</li>
<li>Procedura di Gestione dei Fornitori e dei Servizi Cloud (doc. 12).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica, autorizza l'ingaggio di fornitori critici e ne accetta i rischi residui.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica, gestisce il registro fornitori, coordina le valutazioni di sicurezza e i riesami periodici.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Valuta tecnicamente i fornitori, verifica certificazioni e misure, monitora SLA e incidenti.</td></tr>
<tr><td>DPO (consulente esterno)</td><td>Valida gli aspetti privacy, i DPA e i trasferimenti di dati personali.</td></tr>
</table>
<h2>5. Corpo – Regole e passi concreti</h2>
<h3>5.1 Classificazione dei fornitori</h3>
<p>Ogni fornitore è classificato per criticità in funzione del dato e del servizio: <strong>critico</strong> (cloud, AI, accesso a PII di clienti), <strong>rilevante</strong> (sviluppo/manutenzione), <strong>ordinario</strong> (servizi non legati alle informazioni). La classificazione determina la profondità della due diligence.</p>
<h3>5.2 Qualifica e due diligence</h3>
<ul>
<li>Per i fornitori critici e rilevanti si raccolgono evidenze di sicurezza: certificazioni (es. ISO 27001/27017/27018, SOC 2), sede dei data center, misure tecniche e organizzative.</li>
<li>Si verifica la collocazione dei dati nell'UE/SEE o la presenza di garanzie adeguate per trasferimenti extra-UE.</li>
<li>Si privilegiano fornitori con certificazioni riconosciute e infrastruttura nell'UE (Aruba in Italia, Hetzner in Germania).</li>
</ul>
<h3>5.3 Clausole contrattuali e DPA</h3>
<ul>
<li>I contratti con fornitori critici/rilevanti includono clausole di sicurezza: riservatezza, obblighi di notifica incidenti, diritto di audit/evidenza, livelli di servizio (SLA), gestione del fine rapporto e restituzione/cancellazione dei dati.</li>
<li>Quando il fornitore tratta dati personali, si stipula un <strong>DPA ex art. 28 GDPR</strong> e si verificano sub-responsabili e clausole 27018.</li>
<li>Si richiede al fornitore di notificare tempestivamente gli incidenti di sicurezza, coerentemente con i tempi di notifica NIS2 (pre-allarme 24h, notifica 72h, relazione finale 1 mese) qualora l'azienda sia a sua volta soggetto obbligato.</li>
</ul>
<h3>5.4 Propagazione lungo la supply chain</h3>
<p>Essendo l'azienda <strong>fornitore di clienti soggetti a NIS2</strong>, i requisiti di sicurezza ricevuti dai clienti vengono recepiti e ribaltati sui propri sub-fornitori, in modo che le misure di sicurezza scendano coerentemente lungo l'intera catena (Art. 24 D.Lgs. 138/2024).</p>
<h3>5.5 Sorveglianza continua</h3>
<ul>
<li>Riesame periodico (almeno annuale) dei fornitori critici: rinnovo evidenze, verifica SLA, eventuali incidenti.</li>
<li>Monitoraggio di comunicazioni di sicurezza dei fornitori cloud/AI e valutazione dell'impatto.</li>
<li>In caso di non conformità grave o incidente, attivazione delle azioni correttive e, se necessario, sostituzione del fornitore.</li>
</ul>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li><strong>A.5.19</strong> – Sicurezza delle informazioni nei rapporti con i fornitori.</li>
<li><strong>A.5.20</strong> – Gestione della sicurezza negli accordi con i fornitori.</li>
<li><strong>A.5.21</strong> – Gestione della sicurezza nella catena di fornitura ICT.</li>
<li><strong>A.5.22</strong> – Monitoraggio, riesame e gestione dei cambiamenti dei servizi dei fornitori.</li>
<li><strong>A.5.23</strong> – Sicurezza delle informazioni nell'uso di servizi cloud.</li>
<li>ISO/IEC 27017 e 27018 per i fornitori cloud e il trattamento dei PII.</li>
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: misure di gestione del rischio, sicurezza della supply chain.</li>
</ul>
<h2>7. Registrazioni ed evidenze</h2>
<ul>
<li>Registro dei fornitori con classificazione di criticità e stato di qualifica.</li>
<li>Schede di valutazione di sicurezza e questionari compilati.</li>
<li>Contratti, DPA e clausole di sicurezza archiviati.</li>
<li>Verbali dei riesami periodici e registro degli incidenti che coinvolgono fornitori.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>La Politica è riesaminata almeno una volta all'anno o a fronte di cambiamenti significativi (nuovi fornitori critici, incidenti, evoluzioni normative). Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI con numero di versione, data e responsabile dell'aggiornamento (RSGSI).</p>
+85
View File
@@ -0,0 +1,85 @@
<!--META|doc_type=politica_sviluppo_sicuro|title=Politica di Sviluppo Software Sicuro (Secure SDLC)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente Politica stabilisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> integra la sicurezza in tutte le fasi del ciclo di vita dello sviluppo software (Secure Software Development Life Cycle). L'obiettivo è garantire che i prodotti realizzati — sia quelli in <strong>licenza d'uso installati sui server dei clienti</strong>, sia il <strong>SaaS multi-tenant in cloud</strong> — siano progettati, sviluppati, testati e mantenuti in modo da ridurre vulnerabilità, proteggere i dati personali trattati e soddisfare i requisiti di sicurezza nell'acquisizione, sviluppo e manutenzione previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
<h2>2. Ambito</h2>
<p>La Politica si applica a tutte le attività di analisi, progettazione, codifica, test, rilascio e manutenzione dei software dell'azienda, a tutto il personale tecnico (9 dipendenti e 2 collaboratori esterni a P.IVA) e a eventuali fornitori che contribuiscono allo sviluppo. Copre il codice sorgente, le dipendenze di terze parti, le pipeline di build/deploy, gli ambienti di sviluppo e test, e le componenti di intelligenza artificiale integrate tramite API esterne (es. LLM Anthropic). L'attività si svolge esclusivamente da <strong>PC portatili cifrati</strong>, senza alcun server in sede.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 – Annex A, controlli A.8.25, A.8.26, A.8.27, A.8.28, A.8.29, A.8.30, A.8.31, A.8.32, A.8.33.</li>
<li>ISO/IEC 27017:2015 – sicurezza nello sviluppo e gestione di servizi cloud.</li>
<li>ISO/IEC 27018:2019 – protezione dei PII nei servizi cloud (privacy by design).</li>
<li>D.Lgs. 138/2024, art. 24 (sicurezza nell'acquisizione, sviluppo e manutenzione di sistemi).</li>
<li>Regolamento (UE) 2016/679 (GDPR), artt. 25 e 32 (privacy by design e by default, sicurezza del trattamento).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica e assegna le risorse per la sicurezza dello sviluppo.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica e ne verifica l'applicazione nei progetti.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Definisce gli standard tecnici, gestisce code review, analisi delle vulnerabilità e gestione degli accessi al codice.</td></tr>
<tr><td>Sviluppatori (dipendenti e collaboratori)</td><td>Applicano le regole di codifica sicura, eseguono test e gestiscono le segnalazioni di vulnerabilità.</td></tr>
<tr><td>DPO (consulente esterno)</td><td>Valida i requisiti di privacy by design quando si trattano dati personali.</td></tr>
</table>
<h2>5. Corpo – Regole e passi concreti</h2>
<h3>5.1 Requisiti e progettazione sicura</h3>
<ul>
<li>I requisiti di sicurezza e privacy sono definiti già in fase di analisi, considerando il modello di minaccia, la separazione tra tenant nel SaaS e l'isolamento delle istanze on-premise.</li>
<li>Si applicano i principi di <strong>privacy by design e by default</strong> (GDPR artt. 25 e 32) e di minimizzazione dei dati.</li>
</ul>
<h3>5.2 Codifica sicura</h3>
<ul>
<li>Vengono seguite regole di secure coding per prevenire le vulnerabilità più comuni (injection, autenticazione/sessione, controllo accessi, gestione errori, esposizione di dati).</li>
<li>I segreti (credenziali, chiavi API, incluse quelle dei servizi AI) non sono mai inseriti nel codice né nei repository: si usano meccanismi di gestione segreti dedicati.</li>
<li>Gli ambienti di sviluppo risiedono su PC portatili cifrati con accesso autenticato.</li>
</ul>
<h3>5.3 Gestione delle dipendenze di terze parti</h3>
<ul>
<li>Le librerie e i componenti open source sono censiti e tenuti aggiornati; si monitorano gli avvisi di vulnerabilità note e si applicano le patch in tempi proporzionati al rischio.</li>
</ul>
<h3>5.4 Test di sicurezza</h3>
<ul>
<li>Sono previsti test funzionali e di sicurezza prima del rilascio; per le componenti più esposte si eseguono verifiche statiche/dinamiche e, ove opportuno, test di penetrazione.</li>
<li>Gli ambienti di test usano dati anonimizzati o sintetici: i dati personali reali dei clienti non vengono utilizzati in test.</li>
</ul>
<h3>5.5 Separazione degli ambienti e gestione dei cambiamenti</h3>
<ul>
<li>Ambienti di sviluppo, test e produzione sono separati; i rilasci seguono un processo controllato di change management con tracciabilità delle modifiche.</li>
<li>Il codice è versionato; le modifiche sono sottoposte a revisione tra pari (code review) prima dell'integrazione.</li>
</ul>
<h3>5.6 Rilascio, manutenzione e gestione vulnerabilità</h3>
<ul>
<li>Per i prodotti in licenza installati presso i clienti si forniscono aggiornamenti di sicurezza e indicazioni di installazione sicura; le responsabilità di patching condivise con il cliente sono documentate.</li>
<li>Per il SaaS, l'azienda gestisce direttamente il patching dell'applicazione.</li>
<li>È attivo un canale per la ricezione e gestione delle segnalazioni di vulnerabilità; le correzioni sono prioritizzate in base alla gravità.</li>
</ul>
<h3>5.7 Componenti di intelligenza artificiale</h3>
<p>L'uso di API LLM esterne è valutato per i rischi su confidenzialità dei dati inviati e affidabilità degli output; i dati personali e riservati inviati ai servizi AI sono minimizzati e protetti coerentemente con i contratti e i DPA dei fornitori.</p>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li><strong>A.8.25</strong> – Ciclo di vita di sviluppo sicuro.</li>
<li><strong>A.8.26</strong> – Requisiti di sicurezza delle applicazioni.</li>
<li><strong>A.8.27 / A.8.28</strong> – Principi di architettura sicura e codifica sicura.</li>
<li><strong>A.8.29</strong> – Test di sicurezza in sviluppo e accettazione.</li>
<li><strong>A.8.30</strong> – Sviluppo affidato all'esterno.</li>
<li><strong>A.8.31</strong> – Separazione ambienti di sviluppo, test e produzione.</li>
<li><strong>A.8.32 / A.8.33</strong> – Gestione dei cambiamenti e dati di test.</li>
<li>Controlli ISO/IEC 27017 e 27018 per gli aspetti cloud e PII.</li>
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: sicurezza nell'acquisizione, sviluppo e manutenzione dei sistemi.</li>
</ul>
<h2>7. Registrazioni ed evidenze</h2>
<ul>
<li>Requisiti di sicurezza documentati per progetto/prodotto.</li>
<li>Registri delle code review e dei test di sicurezza.</li>
<li>Inventario delle dipendenze e tracciamento delle vulnerabilità/patch.</li>
<li>Log dei rilasci e delle modifiche (change management).</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>La Politica è riesaminata almeno annualmente o in caso di cambiamenti tecnologici significativi, nuove minacce o requisiti contrattuali dei clienti. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
+81
View File
@@ -0,0 +1,81 @@
<!--META|doc_type=politica_cloud|title=Politica di Sicurezza del Cloud (ISO/IEC 27017)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> utilizza in sicurezza i servizi cloud su cui poggia interamente la propria operatività. Poiché l'infrastruttura aziendale è <strong>integralmente in cloud</strong> (Aruba S.p.A. – Italia; Hetzner – Germania, UE) e in sede non esiste alcun server, la corretta configurazione e gestione dei servizi cloud è essenziale per la sicurezza delle informazioni dell'azienda e dei clienti. La Politica recepisce i controlli cloud-specifici della <strong>ISO/IEC 27017:2015</strong> e chiarisce il <strong>modello di responsabilità condivisa</strong> tra l'azienda e i fornitori cloud.</p>
<h2>2. Ambito</h2>
<p>La Politica si applica a tutti i servizi cloud utilizzati per: l'erogazione del <strong>SaaS multi-tenant</strong>; l'ospitalità di ambienti di sviluppo, test e strumenti interni; l'archiviazione di dati aziendali e dei clienti; i servizi di intelligenza artificiale fruiti via API (es. LLM Anthropic). Si applica a tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) che configura, amministra o accede a tali servizi, esclusivamente tramite <strong>PC portatili cifrati</strong>.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27017:2015 – controlli cloud-specifici (CLD.6.3, CLD.8.1, CLD.9.5, CLD.12.1, CLD.12.4, CLD.13.1) e modello di responsabilità condivisa.</li>
<li>ISO/IEC 27001:2022 – Annex A, in particolare A.5.23 (uso di servizi cloud), A.8.9, A.8.10, A.8.13, A.8.15, A.8.24.</li>
<li>ISO/IEC 27018:2019 – tutela dei PII nel cloud (cfr. doc. 10).</li>
<li>D.Lgs. 138/2024, art. 24 (misure di gestione del rischio).</li>
<li>Regolamento (UE) 2016/679 (GDPR).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica e l'adozione/dismissione di servizi cloud strategici.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica, coordina la valutazione dei rischi cloud e i riesami.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Configura e amministra i servizi cloud, gestisce accessi, cifratura, log e hardening secondo la responsabilità condivisa.</td></tr>
<tr><td>DPO (consulente esterno)</td><td>Verifica gli aspetti privacy e i trasferimenti di dati personali.</td></tr>
</table>
<h2>5. Corpo – Regole e passi concreti</h2>
<h3>5.1 Modello di responsabilità condivisa</h3>
<p>Per ogni servizio cloud è documentata la ripartizione delle responsabilità di sicurezza tra <strong>fornitore</strong> (sicurezza fisica dei data center, hypervisor, rete sottostante, disponibilità dell'infrastruttura) e <strong>azienda cliente</strong> (configurazione dei servizi, gestione degli account e degli accessi, cifratura dei dati a riposo e in transito, gestione delle chiavi, backup applicativi, logging). La consapevolezza di "ciò che il fornitore fa per noi e ciò che dobbiamo fare noi" è la base della Politica (CLD.6.3).</p>
<h3>5.2 Selezione e collocazione</h3>
<ul>
<li>Si privilegiano fornitori cloud con certificazioni di sicurezza riconosciute e data center nell'UE/SEE (Aruba in Italia, Hetzner in Germania) per favorire la conformità GDPR.</li>
<li>Eventuali servizi extra-UE (es. API AI) sono adottati solo con garanzie adeguate ai trasferimenti e DPA idonei.</li>
</ul>
<h3>5.3 Gestione degli accessi</h3>
<ul>
<li>Accessi amministrativi nominali, con autenticazione forte (MFA) e principio del minimo privilegio.</li>
<li>Separazione dei ruoli e revisione periodica delle utenze; revoca tempestiva al termine del rapporto.</li>
<li>L'accesso avviene solo da PC portatili cifrati e aggiornati.</li>
</ul>
<h3>5.4 Protezione dei dati</h3>
<ul>
<li>Cifratura dei dati in transito (TLS) e a riposo; gestione sicura delle chiavi.</li>
<li>Isolamento e separazione dei dati tra tenant nel SaaS multi-tenant (CLD.9.5).</li>
<li>Configurazioni sicure (hardening) e disabilitazione dei servizi non necessari.</li>
</ul>
<h3>5.5 Logging, monitoraggio e gestione operativa</h3>
<ul>
<li>Abilitazione dei log di sicurezza e degli accessi amministrativi resi disponibili dai fornitori; conservazione e revisione periodica (CLD.12.1, CLD.12.4).</li>
<li>Monitoraggio della disponibilità dei servizi e degli avvisi di sicurezza dei fornitori cloud.</li>
<li>Gestione coordinata dei cambiamenti che impattano i servizi cloud.</li>
</ul>
<h3>5.6 Continuità e fine rapporto</h3>
<ul>
<li>Backup e ripristino sono gestiti coerentemente con la Politica di Continuità (doc. 11), tenendo conto della responsabilità condivisa.</li>
<li>Alla cessazione di un servizio cloud si garantisce l'esportazione dei dati e la loro cancellazione sicura presso il fornitore (CLD.8.1).</li>
</ul>
<h3>5.7 Gestione incidenti cloud</h3>
<p>Gli incidenti che coinvolgono i servizi cloud sono gestiti secondo il processo di gestione incidenti del SGSI; se l'azienda è soggetto NIS2, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024).</p>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li><strong>CLD.6.3</strong> – Ripartizione delle responsabilità tra cliente e fornitore cloud.</li>
<li><strong>CLD.8.1</strong> – Rimozione/restituzione degli asset cliente al termine del servizio.</li>
<li><strong>CLD.9.5</strong> – Segregazione negli ambienti virtuali multi-tenant.</li>
<li><strong>CLD.12.1 / CLD.12.4</strong> – Operatività e logging dei servizi cloud.</li>
<li><strong>CLD.13.1</strong> – Gestione della sicurezza di rete nel cloud.</li>
<li><strong>A.5.23</strong> – Sicurezza delle informazioni nell'uso di servizi cloud; <strong>A.8.24</strong> – uso della crittografia.</li>
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: misure di gestione del rischio; art. 25: notifica incidenti.</li>
</ul>
<h2>7. Registrazioni ed evidenze</h2>
<ul>
<li>Matrice di responsabilità condivisa per ciascun servizio cloud.</li>
<li>Inventario dei servizi cloud e relative configurazioni di sicurezza.</li>
<li>Registro degli accessi amministrativi e log di sicurezza conservati.</li>
<li>Evidenze dei riesami periodici delle configurazioni e degli accessi.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>La Politica è riesaminata almeno annualmente o a fronte dell'adozione di nuovi servizi cloud, modifiche architetturali o incidenti rilevanti. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
+78
View File
@@ -0,0 +1,78 @@
<!--META|doc_type=politica_privacy_cloud|title=Politica di Protezione dei Dati Personali nel Cloud (ISO/IEC 27018 e GDPR)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> protegge i dati personali (PII) che tratta nel cloud per conto dei propri clienti tramite il <strong>SaaS multi-tenant</strong>. L'azienda, nel trattare tali dati, agisce di norma come <strong>responsabile del trattamento</strong> per conto del cliente (titolare). La Politica recepisce i controlli della <strong>ISO/IEC 27018:2019</strong> per la tutela dei PII nei servizi cloud pubblici e ne assicura la coerenza con gli obblighi del <strong>Regolamento (UE) 2016/679 (GDPR)</strong>.</p>
<h2>2. Ambito</h2>
<p>La Politica si applica a tutti i trattamenti di dati personali di clienti e di interessati finali svolti tramite l'infrastruttura cloud (Aruba S.p.A. – Italia; Hetzner – Germania, UE) e ai servizi accessori, inclusi i servizi di intelligenza artificiale fruiti via API. Si applica a tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) e ai sub-fornitori che concorrono al trattamento. Riguarda l'intero ciclo di vita del dato: raccolta, archiviazione, elaborazione, trasferimento, conservazione e cancellazione.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27018:2019 – codice di condotta per la protezione dei PII nei cloud pubblici.</li>
<li>ISO/IEC 27017:2015 – sicurezza dei servizi cloud (cfr. doc. 9).</li>
<li>ISO/IEC 27001:2022 – Annex A, A.5.34 (privacy e protezione dei PII), A.8.10, A.8.11, A.8.12, A.8.24.</li>
<li>Regolamento (UE) 2016/679 (GDPR) – artt. 5, 28, 30, 32, 33, 44-49.</li>
<li>D.Lgs. 138/2024, art. 24 (misure di gestione del rischio).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica e garantisce le risorse per la conformità privacy.</td></tr>
<tr><td>DPO (consulente esterno)</td><td>Sorveglia la conformità GDPR/27018, supporta i DPA, le richieste degli interessati e la gestione dei data breach.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Integra i requisiti privacy nel SGSI e coordina i riesami.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Attua le misure tecniche (cifratura, accessi, log, cancellazione) sui sistemi cloud.</td></tr>
</table>
<h2>5. Corpo – Regole e passi concreti</h2>
<h3>5.1 Ruolo di responsabile e istruzioni del titolare</h3>
<ul>
<li>L'azienda tratta i PII dei clienti <strong>solo su istruzione documentata del titolare</strong> (art. 28 GDPR; principio 27018) e per le finalità del servizio, senza usi propri non autorizzati.</li>
<li>Con ogni cliente che affida PII è stipulato un <strong>DPA ex art. 28 GDPR</strong> che disciplina finalità, durata, misure di sicurezza, sub-responsabili e assistenza al titolare.</li>
</ul>
<h3>5.2 Trasparenza e sub-fornitori</h3>
<ul>
<li>I sub-fornitori che trattano PII (es. provider cloud, eventuali servizi AI) sono dichiarati al cliente e vincolati a obblighi di protezione equivalenti.</li>
<li>I cambi di sub-fornitore rilevanti sono comunicati al titolare secondo gli accordi.</li>
</ul>
<h3>5.3 Misure di sicurezza tecniche</h3>
<ul>
<li>Cifratura dei PII in transito (TLS) e a riposo; gestione sicura delle chiavi (A.8.24).</li>
<li>Controllo degli accessi su base nominale, minimo privilegio e autenticazione forte; tracciamento degli accessi ai PII.</li>
<li>Segregazione dei dati tra tenant; ambienti di test che non utilizzano PII reali (dati anonimizzati/sintetici).</li>
<li>Log degli accessi e delle operazioni sui PII, conservati e riesaminati.</li>
</ul>
<h3>5.4 Localizzazione e trasferimenti</h3>
<ul>
<li>I PII sono ospitati di preferenza su data center nell'UE/SEE (Italia, Germania).</li>
<li>Eventuali trasferimenti extra-UE (es. verso servizi AI) avvengono solo con garanzie adeguate ai sensi degli artt. 44-49 GDPR e sono documentati.</li>
</ul>
<h3>5.5 Diritti degli interessati e assistenza al titolare</h3>
<p>L'azienda assiste il titolare nel dare seguito alle richieste degli interessati (accesso, rettifica, cancellazione, portabilità, opposizione), fornendo gli strumenti tecnici per individuare ed estrarre/cancellare i dati nel SaaS.</p>
<h3>5.6 Conservazione e cancellazione sicura</h3>
<ul>
<li>I PII sono conservati per il tempo previsto dal contratto/dalla finalità; al termine del rapporto sono restituiti e/o cancellati in modo sicuro, anche presso i provider cloud.</li>
</ul>
<h3>5.7 Violazioni di dati personali</h3>
<p>In caso di data breach, l'azienda informa <strong>senza ingiustificato ritardo</strong> il titolare (art. 33 GDPR), supportandolo nelle valutazioni e notifiche. Se l'evento è anche un incidente NIS2 rilevante e l'azienda è soggetto obbligato, si applicano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024); il coordinamento tra notifica privacy e notifica NIS2 è gestito dal DPO con il RSGSI.</p>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li>Controlli <strong>ISO/IEC 27018</strong>: consenso e scopo, trasparenza sui sub-fornitori, cancellazione sicura, notifica accessi, restrizione d'uso dei PII, tracciamento.</li>
<li><strong>A.5.34</strong> – Privacy e protezione dei dati personali.</li>
<li><strong>A.8.10 / A.8.11</strong> – Cancellazione delle informazioni e mascheramento dei dati.</li>
<li><strong>A.8.12</strong> – Prevenzione della fuga di dati; <strong>A.8.24</strong> – uso della crittografia.</li>
<li><strong>GDPR</strong>: artt. 5, 28, 30, 32, 33, 44-49.</li>
<li><strong>NIS2 – D.Lgs. 138/2024</strong>: art. 24 (misure di gestione del rischio), art. 25 (notifica incidenti).</li>
</ul>
<h2>7. Registrazioni ed evidenze</h2>
<ul>
<li>DPA con i clienti e con i sub-responsabili; elenco dei sub-fornitori.</li>
<li>Registro dei trattamenti per conto del titolare (supporto art. 30 GDPR).</li>
<li>Log degli accessi ai PII e registro delle richieste degli interessati.</li>
<li>Registro dei data breach e relative comunicazioni al titolare.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>La Politica è riesaminata almeno annualmente o a fronte di nuovi trattamenti, cambi di sub-fornitori o aggiornamenti normativi. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente con il parere del DPO. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
+75
View File
@@ -0,0 +1,75 @@
<!--META|doc_type=politica_continuita|title=Politica di Continuità Operativa e Backup|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> assicura la continuità dei propri servizi e la disponibilità delle informazioni, anche a fronte di eventi avversi (guasti cloud, attacchi informatici, errori, indisponibilità di personale). Considerata l'infrastruttura <strong>interamente in cloud</strong> e l'assenza di server in sede, l'obiettivo è garantire backup affidabili, capacità di ripristino del <strong>SaaS multi-tenant</strong> e dei dati dei clienti, e la continuità operativa di un team piccolo che lavora da <strong>PC portatili cifrati</strong>. La Politica supporta gli obblighi di <strong>continuità e gestione delle crisi</strong> previsti dalle misure dell'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
<h2>2. Ambito</h2>
<p>La Politica si applica alla disponibilità del servizio SaaS, dei dati aziendali e dei clienti, del codice sorgente, degli ambienti di sviluppo e degli strumenti essenziali, ospitati su Aruba S.p.A. (Italia) e Hetzner (Germania, UE). Riguarda tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) e i fornitori cloud, nel rispetto del modello di responsabilità condivisa. Sono inclusi backup, ripristino, gestione delle crisi e continuità delle persone chiave.</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 – Annex A, A.5.29 (sicurezza durante le interruzioni), A.5.30 (prontezza ICT per la continuità operativa), A.8.13 (backup), A.8.14 (ridondanza).</li>
<li>ISO/IEC 27017:2015 – continuità nei servizi cloud e responsabilità condivisa.</li>
<li>ISO/IEC 27018:2019 – conservazione e ripristino dei PII nel cloud.</li>
<li>D.Lgs. 138/2024, art. 24 (continuità operativa e gestione delle crisi) e art. 25 (notifica incidenti).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica, gli obiettivi di continuità e attiva la gestione delle crisi maggiori.</td></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica, coordina i test di ripristino e i riesami della continuità.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Configura e verifica backup e ripristini, gestisce ridondanza e procedure tecniche di recovery.</td></tr>
<tr><td>Tutto il personale</td><td>Segue le procedure di backup degli strumenti di lavoro e collabora alla continuità.</td></tr>
</table>
<h2>5. Corpo – Regole e passi concreti</h2>
<h3>5.1 Obiettivi di continuità</h3>
<p>Per i servizi critici (SaaS e dati dei clienti) sono definiti obiettivi di ripristino: <strong>RTO</strong> (tempo massimo di ripristino) e <strong>RPO</strong> (massima perdita di dati accettabile). I valori puntuali sono definiti per ciascun servizio nelle procedure operative <strong>[DA VERIFICARE]</strong> e approvati dalla Direzione.</p>
<h3>5.2 Backup</h3>
<ul>
<li>I dati del SaaS, i database dei clienti e il codice sorgente sono sottoposti a backup periodici e automatizzati.</li>
<li>I backup seguono il principio di ridondanza con copie su posizioni/servizi distinti rispetto alla produzione (preferibilmente in regione/data center diversi nell'UE).</li>
<li>I backup contenenti dati personali sono cifrati e protetti con i medesimi controlli di accesso dei dati di produzione.</li>
<li>È definita una politica di conservazione (retention) e di rotazione dei backup.</li>
</ul>
<h3>5.3 Test di ripristino</h3>
<ul>
<li>I ripristini sono testati periodicamente (almeno annualmente) per verificarne l'efficacia e il rispetto di RTO/RPO; gli esiti sono registrati.</li>
<li>Un backup non testato non è considerato affidabile.</li>
</ul>
<h3>5.4 Ridondanza e dipendenza dai fornitori cloud</h3>
<ul>
<li>Si valutano le garanzie di disponibilità (SLA) dei provider cloud e si predispongono misure per ridurre l'impatto di un'indisponibilità prolungata, inclusa la portabilità dei dati.</li>
<li>La ripartizione delle responsabilità di continuità tra azienda e fornitore è documentata (modello di responsabilità condivisa, ISO 27017).</li>
</ul>
<h3>5.5 Continuità delle persone chiave</h3>
<p>Trattandosi di un team piccolo, sono identificate le competenze critiche e previste misure di ridondanza minime (documentazione, accessi di backup custoditi in sicurezza, condivisione delle conoscenze) per evitare single point of failure umani.</p>
<h3>5.6 Continuità operativa quotidiana</h3>
<ul>
<li>I PC portatili sono cifrati; i dati di lavoro essenziali sono sincronizzati/salvati su servizi cloud aziendali per evitarne la perdita in caso di guasto o furto del dispositivo.</li>
<li>L'operatività non dipende da risorse fisiche in sede, riducendo i rischi legati a una singola ubicazione.</li>
</ul>
<h3>5.7 Gestione delle crisi e degli incidenti</h3>
<p>In caso di evento grave si attiva la gestione delle crisi con comunicazione interna e ai clienti impattati. Se l'evento è un incidente NIS2 rilevante e l'azienda è soggetto obbligato, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024).</p>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li><strong>A.5.29</strong> – Sicurezza delle informazioni durante le interruzioni.</li>
<li><strong>A.5.30</strong> – Prontezza ICT per la continuità operativa.</li>
<li><strong>A.8.13</strong> – Backup delle informazioni.</li>
<li><strong>A.8.14</strong> – Ridondanza degli impianti di elaborazione.</li>
<li>Controlli ISO/IEC 27017 e 27018 per continuità e ripristino nel cloud e dei PII.</li>
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: continuità operativa, gestione del backup e gestione delle crisi; art. 25: notifica incidenti.</li>
</ul>
<h2>7. Registrazioni ed evidenze</h2>
<ul>
<li>Piano/procedure di backup e ripristino con RTO/RPO definiti.</li>
<li>Registri di esecuzione dei backup ed esiti dei test di ripristino.</li>
<li>Inventario dei servizi critici e relative dipendenze cloud.</li>
<li>Verbali di gestione delle crisi e riesami della continuità.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>La Politica è riesaminata almeno annualmente o dopo un incidente significativo, un test di ripristino con esito negativo o cambiamenti infrastrutturali. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
+82
View File
@@ -0,0 +1,82 @@
<!--META|doc_type=procedura_gestione_fornitori|title=Procedura di Gestione dei Fornitori e dei Servizi Cloud|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente Procedura descrive i passi operativi con cui <strong>Nuova Agile Technology srl</strong> seleziona, qualifica, contrattualizza, monitora e dismette i fornitori e i servizi cloud, in attuazione della <strong>Politica di Sicurezza dei Fornitori</strong> (doc. 07) e della <strong>Politica di Sicurezza del Cloud</strong> (doc. 09). Lo scopo è rendere ripetibile e tracciabile la gestione delle terze parti — in particolare i fornitori cloud (Aruba, Hetzner) e i servizi di intelligenza artificiale via API — assicurando il rispetto dei requisiti di sicurezza della supply chain previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2) e degli obblighi GDPR verso i dati personali dei clienti.</p>
<h2>2. Ambito</h2>
<p>La Procedura si applica a tutti i nuovi fornitori e a quelli esistenti che trattano informazioni aziendali o dei clienti, erogano infrastruttura/servizi cloud o contribuiscono allo sviluppo e alla manutenzione dei prodotti (in licenza e SaaS). Coinvolge il personale che richiede, valuta e gestisce i fornitori (9 dipendenti, 2 collaboratori esterni a P.IVA).</p>
<h2>3. Riferimenti</h2>
<ul>
<li>ISO/IEC 27001:2022 – A.5.19, A.5.20, A.5.21, A.5.22, A.5.23.</li>
<li>ISO/IEC 27017:2015 (CLD.6.3, CLD.8.1) e ISO/IEC 27018:2019.</li>
<li>D.Lgs. 138/2024, art. 24 (sicurezza della supply chain) e art. 25 (notifica incidenti).</li>
<li>Regolamento (UE) 2016/679 (GDPR), art. 28.</li>
<li>Politiche doc. 07 (Fornitori), doc. 09 (Cloud), doc. 10 (Privacy nel cloud).</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Coordina la Procedura, mantiene il registro fornitori e pianifica i riesami.</td></tr>
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Esegue la valutazione tecnica di sicurezza, definisce e verifica la matrice di responsabilità condivisa, monitora SLA e incidenti.</td></tr>
<tr><td>DPO (consulente esterno)</td><td>Valuta gli aspetti privacy, valida i DPA e i trasferimenti di dati.</td></tr>
<tr><td>Presidente (Silvia Garretto)</td><td>Autorizza i fornitori critici e accetta i rischi residui.</td></tr>
</table>
<h2>5. Corpo – Passi della procedura</h2>
<h3>5.1 Fase 1 – Richiesta e classificazione</h3>
<ul>
<li>Il richiedente apre una richiesta di nuovo fornitore indicando servizio, dati coinvolti e finalità.</li>
<li>Il RSGSI classifica il fornitore (critico / rilevante / ordinario) in base alla sensibilità dei dati e alla criticità del servizio.</li>
</ul>
<h3>5.2 Fase 2 – Due diligence e valutazione di sicurezza</h3>
<ul>
<li>Per fornitori critici/rilevanti, il Resp. IT raccoglie evidenze: certificazioni (ISO 27001/27017/27018, SOC 2), localizzazione dei data center, misure tecniche/organizzative, gestione incidenti.</li>
<li>Si compila una scheda di valutazione con esito (idoneo / idoneo con prescrizioni / non idoneo).</li>
<li>Per i servizi cloud si definisce la <strong>matrice di responsabilità condivisa</strong> (cosa fa il fornitore, cosa fa l'azienda).</li>
</ul>
<h3>5.3 Fase 3 – Contrattualizzazione</h3>
<ul>
<li>Si stipula il contratto con clausole di sicurezza: riservatezza, SLA, obbligo di notifica incidenti, diritto a evidenze/audit, gestione del fine rapporto e cancellazione dati.</li>
<li>Se sono trattati dati personali, si firma il <strong>DPA ex art. 28 GDPR</strong>, verificando i sub-responsabili.</li>
<li>Per i fornitori critici, l'attivazione richiede l'autorizzazione della Presidente.</li>
</ul>
<h3>5.4 Fase 4 – Attivazione e configurazione sicura</h3>
<ul>
<li>Si configurano accessi nominali con MFA e minimo privilegio, cifratura, logging, secondo la Politica Cloud (doc. 09).</li>
<li>Il fornitore è inserito nel <strong>registro fornitori</strong> con classificazione, scadenze contrattuali e referenti.</li>
</ul>
<h3>5.5 Fase 5 – Monitoraggio e riesame periodico</h3>
<ul>
<li>Almeno annualmente per i fornitori critici: verifica SLA, rinnovo evidenze e certificazioni, revisione accessi, analisi di eventuali incidenti.</li>
<li>Si valutano avvisi di sicurezza dei fornitori cloud/AI e si registrano gli esiti del riesame.</li>
<li>Le non conformità generano azioni correttive tracciate.</li>
</ul>
<h3>5.6 Fase 6 – Gestione incidenti del fornitore</h3>
<p>In caso di incidente comunicato dal fornitore o rilevato dall'azienda, si attiva il processo di gestione incidenti del SGSI; se l'evento è un incidente NIS2 rilevante e l'azienda è soggetto obbligato, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024). Gli incidenti che coinvolgono PII sono comunicati al DPO.</p>
<h3>5.7 Fase 7 – Dismissione e fine rapporto</h3>
<ul>
<li>Alla cessazione si revocano gli accessi, si recuperano/esportano i dati e se ne verifica la cancellazione sicura presso il fornitore (CLD.8.1).</li>
<li>Si aggiorna il registro fornitori e si valuta l'impatto sulla continuità (doc. 11).</li>
</ul>
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
<ul>
<li><strong>A.5.19 / A.5.20</strong> – Sicurezza nei rapporti e negli accordi con i fornitori.</li>
<li><strong>A.5.21</strong> – Sicurezza nella catena di fornitura ICT.</li>
<li><strong>A.5.22</strong> – Monitoraggio e gestione dei cambiamenti dei servizi dei fornitori.</li>
<li><strong>A.5.23</strong> – Sicurezza nell'uso di servizi cloud; <strong>CLD.6.3 / CLD.8.1</strong> (ISO 27017).</li>
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: sicurezza della supply chain; art. 25: notifica incidenti.</li>
</ul>
<h2>7. Registrazioni ed evidenze</h2>
<ul>
<li>Registro dei fornitori (classificazione, scadenze, referenti).</li>
<li>Schede di valutazione di sicurezza e matrici di responsabilità condivisa.</li>
<li>Contratti, DPA e clausole di sicurezza.</li>
<li>Verbali dei riesami periodici, registro azioni correttive e registro incidenti dei fornitori.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>La Procedura è riesaminata almeno annualmente o a fronte di variazioni del parco fornitori, incidenti rilevanti o aggiornamenti normativi. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
+50
View File
@@ -0,0 +1,50 @@
<!--META|doc_type=procedura_risk_assessment|title=Procedura di Valutazione e Trattamento dei Rischi|status=approved|version=1.0-->
<h2>Procedura di Valutazione e Trattamento dei Rischi</h2>
<h3>1. Scopo</h3>
<p>Definire le modalità con cui Nuova Agile Technology srl identifica, analizza, valuta e tratta i rischi per la sicurezza delle informazioni, in modo sistematico e ripetibile. La procedura attua i requisiti della ISO/IEC 27001:2022 cl. 6.1.2 (valutazione dei rischi), 6.1.3 (trattamento dei rischi), 8.2 e 8.3 (esecuzione operativa) e supporta la determinazione delle misure ex art. 24 D.Lgs. 138/2024 (NIS2) per i soggetti rientranti.</p>
<h3>2. Ambito</h3>
<p>Si applica a tutte le informazioni e agli asset trattati dall'azienda: codice sorgente e prodotti rilasciati on-premise, piattaforma SaaS in cloud, dati personali dei clienti, infrastruttura cloud (Aruba IT, Hetzner DE, piattaforme AI) e PC portatili aziendali. Non esistendo server in sede, l'analisi considera i rischi di un modello <em>cloud-only</em> (responsabilità condivisa, controlli ISO/IEC 27017 e 27018 per il trattamento di PII nel cloud).</p>
<h3>3. Riferimenti</h3>
<ul>
<li>ISO/IEC 27001:2022 cl. 6.1.2, 6.1.3, 8.2, 8.3; controlli A.5.7 (threat intelligence), A.5.9 (inventario asset), A.8.8 (gestione vulnerabilità tecniche).</li>
<li>ISO/IEC 27005 (linee guida per la stima del rischio) e ISO 31000.</li>
<li>ISO/IEC 27017 §CLD.6.3 e 27018 (rischi specifici cloud e PII).</li>
<li>D.Lgs. 138/2024 art. 24 (misure di gestione del rischio); Determinazione ACN 164179/2025 [DA VERIFICARE riferimento applicabile].</li>
<li>Dichiarazione di Applicabilità (SoA), Politica del SGSI, Procedura NC/Azioni Correttive.</li>
</ul>
<h3>4. Ruoli e responsabilità</h3>
<ul>
<li><strong>Direzione (Silvia Garretto)</strong>: approva i criteri di rischio, la propensione al rischio (risk appetite) e il piano di trattamento; accetta formalmente i rischi residui.</li>
<li><strong>RSGSI (Massimo Tagliavini)</strong>: conduce e coordina la valutazione, mantiene il registro dei rischi, propone il piano di trattamento, monitora le scadenze.</li>
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: fornisce l'inventario asset, le informazioni tecniche su minacce/vulnerabilità, implementa i controlli tecnici di trattamento.</li>
<li><strong>DPO esterno</strong>: consultato per i rischi sul trattamento di dati personali (PII nel SaaS).</li>
</ul>
<h3>5. Flusso/attività passo-passo</h3>
<ol>
<li><strong>Definizione del contesto e dei criteri</strong> (input: SoA, inventario asset; output: criteri approvati). RSGSI e Direzione fissano scala di probabilità (1–5) e impatto (1–5), soglia di accettabilità e criteri di significatività.</li>
<li><strong>Identificazione del rischio</strong> (input: asset, minacce, vulnerabilità; output: rischi censiti). Per ciascun asset si individuano le minacce plausibili (es. compromissione credenziali cloud, data breach SaaS, perdita di un portatile, indisponibilità del provider). Si registra ogni rischio nel <em>modulo "Rischi"</em> della piattaforma.</li>
<li><strong>Analisi e ponderazione</strong> (output: punteggio di rischio inerente). Si calcola Rischio = Probabilità × Impatto secondo la matrice 5×5 (coerente con ISO/IEC 27005). Il punteggio determina la classe (basso/medio/alto/critico).</li>
<li><strong>Valutazione</strong>: confronto con la soglia. I rischi sopra soglia richiedono trattamento; i rischi sotto soglia sono candidati all'accettazione.</li>
<li><strong>Trattamento</strong> (output: piano di trattamento). Per ogni rischio sopra soglia si sceglie l'opzione: <em>mitigazione</em> (applicazione controlli Annex A), <em>trasferimento</em> (es. clausole contrattuali con il provider cloud, assicurazione), <em>evitamento</em> o <em>accettazione</em>. I controlli scelti sono registrati come trattamenti nel modulo "Rischi" e riconciliati con la SoA.</li>
<li><strong>Approvazione e accettazione del rischio residuo</strong>: la Direzione approva il piano di trattamento e accetta formalmente i rischi residui (cl. 6.1.3 e).</li>
<li><strong>Attuazione e monitoraggio</strong>: il Resp. IT implementa i controlli; il RSGSI traccia stato e scadenze nel modulo. Le carenze rilevate diventano Non Conformità (vedi procedura dedicata).</li>
</ol>
<h3>6. Controlli ISO/clausole collegati</h3>
<p>cl. 6.1.2/6.1.3 (processo e piano), cl. 8.2/8.3 (esecuzione e produzione della SoA aggiornata), A.5.7, A.5.9, A.8.8; per il cloud A.5.19–A.5.23 (rapporti con i fornitori e servizi cloud) e i controlli estesi ISO/IEC 27017 e 27018 sulla protezione delle PII.</p>
<h3>7. Registrazioni/evidenze</h3>
<ul>
<li><strong>Registro dei rischi e trattamenti</strong>: modulo "Rischi" della piattaforma (rischio inerente, controlli, rischio residuo, owner, scadenze).</li>
<li><strong>Piano di trattamento e accettazione rischi residui</strong>: verbale di approvazione della Direzione, archiviato come informazione documentata (vedi procedura Controllo Documenti).</li>
<li><strong>SoA aggiornata</strong> (modulo SGSI/SoA).</li>
</ul>
<h3>8. Riesame e versionamento</h3>
<p>La valutazione dei rischi è riesaminata <strong>almeno una volta l'anno</strong> e ogni volta che si verifichino cambiamenti significativi (nuovo prodotto/servizio, nuovo provider, incidente rilevante, modifica normativa). Gli esiti alimentano il Riesame della Direzione. La procedura è soggetta a versionamento secondo la Procedura di Controllo dei Documenti.</p>
+50
View File
@@ -0,0 +1,50 @@
<!--META|doc_type=procedura_controllo_documenti|title=Procedura di Controllo dei Documenti e delle Registrazioni|status=approved|version=1.0-->
<h2>Procedura di Controllo dei Documenti e delle Registrazioni</h2>
<h3>1. Scopo</h3>
<p>Garantire che le informazioni documentate richieste dal SGSI di Nuova Agile Technology srl siano create, identificate, approvate, distribuite, protette e conservate in modo controllato, e che le registrazioni (evidenze) restino leggibili, integre e recuperabili. La procedura attua la ISO/IEC 27001:2022 cl. 7.5 (informazioni documentate).</p>
<h3>2. Ambito</h3>
<p>Si applica a tutta la documentazione del SGSI (politiche, procedure di sistema, istruzioni operative, SoA, registri) e a tutte le registrazioni prodotte dai processi (verbali, report di audit, valutazioni dei rischi, registri formazione, NC/CAPA). Comprende sia i documenti gestiti nella piattaforma sia gli eventuali documenti esterni archiviati nel cloud aziendale (Aruba IT). Essendo l'azienda <em>cloud-only</em>, non esiste documentazione cartacea controllata di default.</p>
<h3>3. Riferimenti</h3>
<ul>
<li>ISO/IEC 27001:2022 cl. 7.5.1 (generalità), 7.5.2 (creazione e aggiornamento), 7.5.3 (controllo delle informazioni documentate).</li>
<li>Controlli A.5.33 (protezione delle registrazioni), A.5.34 (privacy e PII), A.8.10–A.8.12 (cancellazione, mascheramento, prevenzione fuga di dati).</li>
<li>ISO/IEC 27018 (conservazione e cancellazione delle PII nel cloud); GDPR per i periodi di conservazione dei dati personali.</li>
<li>Politica del SGSI; tutte le procedure di sistema.</li>
</ul>
<h3>4. Ruoli e responsabilità</h3>
<ul>
<li><strong>RSGSI (Massimo Tagliavini)</strong>: gestisce l'elenco dei documenti del SGSI, ne controlla numerazione, versioni e stato; verifica la disponibilità delle versioni vigenti.</li>
<li><strong>Direzione (Silvia Garretto)</strong>: approva politiche e procedure di sistema (autorità di approvazione).</li>
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: garantisce backup, controllo accessi e cifratura degli archivi documentali cloud; definisce i diritti di lettura/scrittura.</li>
<li><strong>Autori/Responsabili di processo</strong>: redigono e aggiornano i documenti di propria competenza.</li>
<li><strong>DPO esterno</strong>: indica i tempi di conservazione e cancellazione delle registrazioni contenenti dati personali.</li>
</ul>
<h3>5. Flusso/attività passo-passo</h3>
<ol>
<li><strong>Identificazione</strong> (input: esigenza documentale; output: documento codificato). Ogni documento riceve titolo, codice/slug, numero di versione, data e stato (bozza/in revisione/approvato/obsoleto).</li>
<li><strong>Redazione e aggiornamento</strong> (cl. 7.5.2): l'autore redige in formato e struttura standard; le modifiche sono tracciate con la cronologia delle versioni.</li>
<li><strong>Verifica e approvazione</strong>: il RSGSI verifica adeguatezza e coerenza; la Direzione (o delegato) approva. Un documento è pubblicabile solo nello stato "approvato".</li>
<li><strong>Distribuzione e disponibilità</strong> (cl. 7.5.3): la versione vigente è resa disponibile a chi ne ha bisogno tramite la piattaforma/archivio cloud; gli accessi sono profilati per ruolo.</li>
<li><strong>Controllo delle modifiche</strong>: ogni revisione genera una nuova versione; la precedente è marcata "obsoleta" e conservata per tracciabilità, non più utilizzabile come riferimento operativo.</li>
<li><strong>Documenti di origine esterna</strong> (leggi, norme, contratti cloud, manuali provider): sono identificati e la loro distribuzione è controllata.</li>
<li><strong>Protezione e conservazione delle registrazioni</strong>: le evidenze sono protette da perdita, alterazione e accesso non autorizzato mediante backup, cifratura e controllo accessi del provider cloud; i periodi di conservazione rispettano legge e GDPR; la cancellazione avviene in modo sicuro (A.8.10).</li>
</ol>
<h3>6. Controlli ISO/clausole collegati</h3>
<p>cl. 7.5.1/7.5.2/7.5.3; A.5.33 (protezione registrazioni), A.5.37 (procedure operative documentate), A.8.10/A.8.11/A.8.12; per il cloud i requisiti ISO/IEC 27018 su conservazione, restituzione e cancellazione delle PII.</p>
<h3>7. Registrazioni/evidenze</h3>
<ul>
<li><strong>Elenco master dei documenti del SGSI</strong> con stato e versione (mantenuto dal RSGSI nella piattaforma/archivio cloud).</li>
<li><strong>Cronologia delle versioni</strong> di ciascun documento (campo versione + data + autore).</li>
<li><strong>Registrazioni di processo</strong>: report di audit, verbali di riesame, valutazioni dei rischi, registri formazione e NC/CAPA risiedono nei rispettivi <em>moduli della piattaforma</em> (Audit interni, Riesame, Rischi, Formazione, NCR/CAPA), che fungono da repository controllato delle evidenze.</li>
</ul>
<h3>8. Riesame e versionamento</h3>
<p>La presente procedura e l'elenco master sono riesaminati <strong>almeno annualmente</strong> e ad ogni modifica organizzativa o normativa rilevante. Ogni nuova edizione incrementa la versione (es. 1.0 → 1.1 per modifiche minori, → 2.0 per modifiche sostanziali) ed è approvata dalla Direzione prima della distribuzione.</p>
+51
View File
@@ -0,0 +1,51 @@
<!--META|doc_type=procedura_audit_interni|title=Procedura di Audit Interni|status=approved|version=1.0-->
<h2>Procedura di Audit Interni</h2>
<h3>1. Scopo</h3>
<p>Definire le modalità di pianificazione, conduzione e rendicontazione degli audit interni del SGSI di Nuova Agile Technology srl, al fine di verificare che il sistema di gestione sia conforme ai requisiti della ISO/IEC 27001:2022, ai requisiti propri dell'organizzazione e che sia efficacemente attuato e mantenuto. La procedura attua la cl. 9.2 (audit interni).</p>
<h3>2. Ambito</h3>
<p>Copre tutti i processi e i controlli del SGSI: governance del SGSI, gestione dei rischi, gestione degli incidenti, sicurezza del cloud (Aruba IT, Hetzner DE, piattaforme AI), gestione delle PII nel SaaS, sicurezza dei portatili, formazione e rapporti con i fornitori. Data la dimensione del team (~12 persone), gli audit sono di norma condotti dal RSGSI o da risorsa interna indipendente dall'area auditata; per oggettività su aree gestite dallo stesso RSGSI è possibile ricorrere a un auditor esterno.</p>
<h3>3. Riferimenti</h3>
<ul>
<li>ISO/IEC 27001:2022 cl. 9.2.1 (generalità) e 9.2.2 (programma di audit).</li>
<li>ISO 19011 (linee guida per gli audit dei sistemi di gestione).</li>
<li>Controllo A.5.35 (riesame indipendente della sicurezza delle informazioni); A.5.36 (conformità a politiche e standard).</li>
<li>D.Lgs. 138/2024 art. 24 (verifica dell'efficacia delle misure) [DA VERIFICARE per i soggetti rientranti].</li>
<li>Procedura NC/Azioni Correttive; Procedura Riesame della Direzione.</li>
</ul>
<h3>4. Ruoli e responsabilità</h3>
<ul>
<li><strong>RSGSI (Massimo Tagliavini)</strong>: predispone il programma di audit, nomina gli auditor, garantisce competenza e indipendenza, consolida i risultati.</li>
<li><strong>Auditor</strong> (interno indipendente o esterno): conduce l'audit, raccoglie le evidenze, formula i rilievi e redige il report.</li>
<li><strong>Responsabili delle aree auditate (es. Simon Fattori per IT/Sicurezza)</strong>: forniscono evidenze, concordano le azioni correttive e ne curano l'attuazione.</li>
<li><strong>Direzione (Silvia Garretto)</strong>: riceve l'esito degli audit in sede di riesame e assicura le risorse per le azioni.</li>
</ul>
<h3>5. Flusso/attività passo-passo</h3>
<ol>
<li><strong>Programmazione</strong> (input: esiti audit precedenti, importanza dei processi, esiti valutazione rischi; output: programma di audit annuale). Si pianifica almeno un ciclo di audit interno all'anno che copra tutte le clausole e i controlli applicabili; aree a rischio più elevato possono essere auditate più di frequente.</li>
<li><strong>Pianificazione del singolo audit</strong> (output: piano di audit). Si definiscono obiettivi, criteri (la norma e i documenti del SGSI), ambito, date e auditor. Il piano è registrato nel <em>modulo "Audit interni"</em> della piattaforma.</li>
<li><strong>Conduzione</strong> (input: documenti e interviste; output: evidenze). L'auditor verifica documentazione, configurazioni cloud, registri e prassi operative; raccoglie evidenze oggettive e annota osservazioni e non conformità.</li>
<li><strong>Classificazione dei rilievi</strong>: ciascun rilievo è classificato come Non Conformità (maggiore/minore) oppure Osservazione/Opportunità di miglioramento.</li>
<li><strong>Rendicontazione</strong> (output: report di audit). L'auditor redige il report nel modulo "Audit interni" indicando esito, rilievi e raccomandazioni; il report è condiviso con i responsabili di area e con la Direzione.</li>
<li><strong>Gestione dei rilievi</strong>: ogni Non Conformità apre una scheda nel modulo NCR/CAPA (vedi procedura dedicata); il RSGSI ne verifica la chiusura.</li>
<li><strong>Follow-up</strong>: gli esiti e lo stato delle azioni alimentano il successivo Riesame della Direzione.</li>
</ol>
<h3>6. Controlli ISO/clausole collegati</h3>
<p>cl. 9.2.1/9.2.2; A.5.35 (riesame indipendente) e A.5.36 (conformità). Gli esiti sono input obbligatori della cl. 9.3 (riesame della direzione) e possono attivare la cl. 10.2 (NC e azioni correttive).</p>
<h3>7. Registrazioni/evidenze</h3>
<ul>
<li><strong>Programma di audit annuale</strong> e <strong>piani dei singoli audit</strong>: modulo "Audit interni".</li>
<li><strong>Report di audit</strong> con rilievi ed evidenze raccolte: modulo "Audit interni".</li>
<li><strong>Schede di Non Conformità/azioni correttive</strong> generate dai rilievi: modulo NCR/CAPA.</li>
<li><strong>Evidenza di indipendenza/competenza degli auditor</strong>: registro formazione/competenze.</li>
</ul>
<h3>8. Riesame e versionamento</h3>
<p>Il programma di audit è riesaminato <strong>almeno annualmente</strong> e aggiornato a seguito di cambiamenti significativi (nuovi servizi, incidenti, modifiche normative). La presente procedura è soggetta a versionamento secondo la Procedura di Controllo dei Documenti ed è approvata dalla Direzione.</p>
+47
View File
@@ -0,0 +1,47 @@
<!--META|doc_type=procedura_riesame_direzione|title=Procedura di Riesame della Direzione|status=approved|version=1.0-->
<h2>Procedura di Riesame della Direzione</h2>
<h3>1. Scopo</h3>
<p>Definire le modalità con cui la Direzione di Nuova Agile Technology srl riesamina periodicamente il SGSI per assicurarne la continua idoneità, adeguatezza ed efficacia, e per decidere su opportunità di miglioramento e necessità di cambiamento. La procedura attua la ISO/IEC 27001:2022 cl. 9.3 (riesame della direzione).</p>
<h3>2. Ambito</h3>
<p>Si applica all'intero SGSI e a tutte le sue componenti: contesto, parti interessate, obiettivi di sicurezza, rischi, incidenti, audit, non conformità, formazione, fornitori cloud e conformità normativa (incluso NIS2 ove applicabile). Considera la natura <em>cloud-only</em> dell'azienda e la responsabilità condivisa con i provider (Aruba IT, Hetzner DE, piattaforme AI).</p>
<h3>3. Riferimenti</h3>
<ul>
<li>ISO/IEC 27001:2022 cl. 9.3.1 (generalità), 9.3.2 (input del riesame), 9.3.3 (output del riesame).</li>
<li>Controlli A.5.1 (politiche per la sicurezza delle informazioni) e A.5.4 (responsabilità della direzione).</li>
<li>D.Lgs. 138/2024 art. 23 (obblighi di governance e responsabilità degli organi di amministrazione) [DA VERIFICARE per i soggetti rientranti].</li>
<li>Procedura Audit Interni; Procedura Valutazione Rischi; Procedura NC/Azioni Correttive.</li>
</ul>
<h3>4. Ruoli e responsabilità</h3>
<ul>
<li><strong>Direzione (Silvia Garretto)</strong>: presiede il riesame, valuta gli input, assume le decisioni e assegna le risorse; firma il verbale.</li>
<li><strong>RSGSI (Massimo Tagliavini)</strong>: convoca il riesame, prepara e presenta gli input, redige il verbale, dà seguito alle decisioni e ne traccia l'attuazione.</li>
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: riporta su incidenti, vulnerabilità, prestazioni dei controlli tecnici e dei fornitori cloud.</li>
<li><strong>DPO esterno</strong>: riferisce su tematiche di protezione dei dati personali e PII nel SaaS, su richiesta.</li>
</ul>
<h3>5. Flusso/attività passo-passo</h3>
<ol>
<li><strong>Convocazione e raccolta input</strong> (input: dati dei processi del SGSI; output: pacchetto di riesame). Il RSGSI raccoglie, dai moduli della piattaforma, gli elementi richiesti dalla cl. 9.3.2: stato delle azioni dai riesami precedenti; cambiamenti del contesto e delle parti interessate; esiti della valutazione e del trattamento dei rischi; prestazioni di sicurezza (incidenti, non conformità, risultati di monitoraggio e misurazioni); esiti degli audit interni; raggiungimento degli obiettivi di sicurezza; feedback delle parti interessate; opportunità di miglioramento.</li>
<li><strong>Conduzione della riunione</strong>: la Direzione esamina ciascun input, valuta l'idoneità/adeguatezza/efficacia del SGSI e discute eventuali necessità di cambiamento.</li>
<li><strong>Decisioni e output</strong> (cl. 9.3.3): si deliberano opportunità di miglioramento, cambiamenti al SGSI (politiche, obiettivi, controlli), fabbisogni di risorse e azioni con responsabili e scadenze.</li>
<li><strong>Verbalizzazione</strong> (output: verbale di riesame). Il RSGSI redige il verbale nel <em>modulo "Riesame della Direzione"</em> della piattaforma, riportando input considerati, decisioni e azioni assegnate.</li>
<li><strong>Attuazione e monitoraggio</strong>: le azioni derivanti sono tracciate (eventualmente come schede NCR/CAPA o voci del piano di trattamento rischi) e il loro stato è verificato al riesame successivo.</li>
</ol>
<h3>6. Controlli ISO/clausole collegati</h3>
<p>cl. 9.3.1/9.3.2/9.3.3; collegamento diretto con cl. 9.2 (audit, input), cl. 6.1/8.2 (rischi, input), cl. 10.1/10.2 (miglioramento e azioni correttive, output) e cl. 5 (leadership). Controlli A.5.1 e A.5.4.</p>
<h3>7. Registrazioni/evidenze</h3>
<ul>
<li><strong>Verbale di riesame della direzione</strong> con input considerati, decisioni e azioni: modulo "Riesame della Direzione".</li>
<li><strong>Pacchetto di input</strong> (estratti da: modulo Rischi, Audit interni, NCR/CAPA, gestione incidenti, Formazione).</li>
<li><strong>Azioni assegnate</strong> tracciate nei moduli operativi pertinenti (NCR/CAPA o piano di trattamento).</li>
</ul>
<h3>8. Riesame e versionamento</h3>
<p>Il riesame della direzione si tiene <strong>almeno una volta l'anno</strong> e, in via straordinaria, a seguito di cambiamenti rilevanti (incidente grave, modifica normativa, cambio significativo di fornitore cloud o di prodotto). La presente procedura è versionata e approvata dalla Direzione secondo la Procedura di Controllo dei Documenti.</p>
+50
View File
@@ -0,0 +1,50 @@
<!--META|doc_type=procedura_nc_azioni_correttive|title=Procedura di Gestione delle Non Conformità e delle Azioni Correttive|status=approved|version=1.0-->
<h2>Procedura di Gestione delle Non Conformità e delle Azioni Correttive</h2>
<h3>1. Scopo</h3>
<p>Definire le modalità con cui Nuova Agile Technology srl rileva, registra, tratta e chiude le non conformità (NC) del SGSI, analizzandone le cause e attuando azioni correttive per evitarne il ripetersi, a sostegno del miglioramento continuo. La procedura attua la ISO/IEC 27001:2022 cl. 10.2 (non conformità e azioni correttive) e cl. 10.1 (miglioramento continuo).</p>
<h3>2. Ambito</h3>
<p>Si applica a tutte le non conformità comunque rilevate: rilievi di audit interni o esterni, scostamenti dai requisiti della norma o dai documenti del SGSI, incidenti di sicurezza, segnalazioni di clienti o fornitori, malfunzionamenti dei controlli cloud (Aruba IT, Hetzner DE, piattaforme AI) ed esiti di monitoraggio. Comprende le NC relative al trattamento di dati personali nel SaaS, da gestire in coordinamento con il DPO.</p>
<h3>3. Riferimenti</h3>
<ul>
<li>ISO/IEC 27001:2022 cl. 10.1 (miglioramento continuo) e cl. 10.2 (NC e azioni correttive).</li>
<li>Controlli A.5.24–A.5.28 (gestione degli incidenti di sicurezza, raccolta evidenze, apprendimento dagli incidenti).</li>
<li>D.Lgs. 138/2024 art. 24 (mantenimento dell'efficacia delle misure) e art. 25 (obblighi di notifica degli incidenti significativi) [DA VERIFICARE per i soggetti rientranti].</li>
<li>Procedura Audit Interni; Procedura Riesame della Direzione; Procedura Valutazione Rischi.</li>
</ul>
<h3>4. Ruoli e responsabilità</h3>
<ul>
<li><strong>Chiunque rilevi una NC</strong>: la segnala al RSGSI o la registra direttamente nel modulo NCR/CAPA.</li>
<li><strong>RSGSI (Massimo Tagliavini)</strong>: registra e classifica la NC, coordina l'analisi delle cause, assegna le azioni, verifica l'efficacia e chiude la scheda.</li>
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: attua le correzioni tecniche e le azioni correttive di propria competenza.</li>
<li><strong>Direzione (Silvia Garretto)</strong>: assicura le risorse e prende atto delle NC rilevanti in sede di riesame.</li>
<li><strong>DPO esterno</strong>: coinvolto per le NC che riguardano dati personali, anche ai fini di eventuali obblighi di notifica.</li>
</ul>
<h3>5. Flusso/attività passo-passo</h3>
<ol>
<li><strong>Rilevazione e registrazione</strong> (input: rilievo/segnalazione/incidente; output: scheda NC aperta). La NC è aperta nel <em>modulo "Non conformità / Azioni correttive (NCR/CAPA)"</em> con descrizione, origine, data e responsabile.</li>
<li><strong>Correzione immediata</strong> (cl. 10.2 a): si reagisce per contenere e correggere la NC e per gestirne le conseguenze (es. revoca di un accesso, ripristino di una configurazione cloud, contenimento di un incidente).</li>
<li><strong>Valutazione della necessità di azione correttiva</strong> (cl. 10.2 b): si valuta se la NC può ripresentarsi o esistere altrove; per NC minori e isolate può bastare la correzione, per NC ricorrenti o significative si avvia un'azione correttiva.</li>
<li><strong>Analisi delle cause</strong> (root cause): si individuano le cause profonde (es. assenza di un controllo, configurazione errata, mancanza di competenza) con tecnica adeguata (5 perché / causa-effetto).</li>
<li><strong>Definizione e attuazione dell'azione correttiva (CAPA)</strong> (cl. 10.2 c/d): si definiscono le azioni, i responsabili e le scadenze, registrandole come azioni collegate alla scheda; si attuano.</li>
<li><strong>Verifica dell'efficacia</strong> (cl. 10.2 d/e): il RSGSI verifica che l'azione abbia eliminato la causa e che la NC non si ripresenti; aggiorna, se necessario, i rischi e la SoA.</li>
<li><strong>Chiusura e comunicazione</strong> (cl. 10.2 f/g): la scheda è chiusa con evidenza dell'efficacia; le NC significative sono portate al Riesame della Direzione. Per incidenti che configurano violazioni di dati o incidenti significativi NIS2, si attivano i relativi flussi di notifica.</li>
</ol>
<h3>6. Controlli ISO/clausole collegati</h3>
<p>cl. 10.2 (a–g) e cl. 10.1; A.5.24 (pianificazione e preparazione gestione incidenti), A.5.27 (apprendimento dagli incidenti), A.5.28 (raccolta evidenze). Gli esiti sono input della cl. 9.3 (riesame) e possono modificare la valutazione dei rischi (cl. 6.1).</p>
<h3>7. Registrazioni/evidenze</h3>
<ul>
<li><strong>Scheda NC</strong> (descrizione, origine, classificazione, correzione): modulo NCR/CAPA.</li>
<li><strong>Analisi delle cause e azioni correttive</strong> (responsabili, scadenze, stato, verifica di efficacia): modulo NCR/CAPA.</li>
<li><strong>Collegamenti</strong> a rilievi di audit (modulo Audit interni), a rischi (modulo Rischi) e a incidenti (gestione incidenti) ove pertinente.</li>
</ul>
<h3>8. Riesame e versionamento</h3>
<p>L'andamento delle NC e l'efficacia delle azioni correttive sono valutati <strong>almeno annualmente</strong> in sede di Riesame della Direzione e ad ogni evento significativo. La presente procedura è versionata e approvata dalla Direzione secondo la Procedura di Controllo dei Documenti.</p>
+49
View File
@@ -0,0 +1,49 @@
<!--META|doc_type=procedura_competenze_formazione|title=Procedura di Gestione delle Competenze e della Formazione|status=approved|version=1.0-->
<h2>Procedura di Gestione delle Competenze e della Formazione</h2>
<h3>1. Scopo</h3>
<p>Assicurare che il personale di Nuova Agile Technology srl che svolge attività rilevanti per il SGSI possieda le competenze necessarie e sia consapevole del proprio ruolo nella sicurezza delle informazioni. La procedura attua la ISO/IEC 27001:2022 cl. 7.2 (competenza) e cl. 7.3 (consapevolezza).</p>
<h3>2. Ambito</h3>
<p>Si applica a tutto il personale: 9 dipendenti, 2 collaboratori P.IVA e la Presidente/Direzione, nonché ai ruoli SGSI (RSGSI, Resp. IT/Sicurezza, DPO esterno). Considera le competenze specifiche per un'azienda <em>cloud-only</em>: sicurezza dei servizi cloud (Aruba IT, Hetzner DE), uso sicuro dei portatili, sviluppo sicuro dei prodotti on-premise e SaaS, protezione dei dati personali (PII) e uso responsabile delle piattaforme AI.</p>
<h3>3. Riferimenti</h3>
<ul>
<li>ISO/IEC 27001:2022 cl. 7.2 (competenza) e cl. 7.3 (consapevolezza).</li>
<li>Controlli A.6.3 (consapevolezza, istruzione e formazione sulla sicurezza), A.6.1 (screening), A.6.2 (termini e condizioni), A.6.5 (responsabilità dopo la cessazione), A.8.28 (codifica sicura).</li>
<li>ISO/IEC 27018 (consapevolezza del personale che tratta PII).</li>
<li>D.Lgs. 138/2024 art. 23 (formazione degli organi di amministrazione) e art. 24 (igiene informatica e formazione del personale) [DA VERIFICARE per i soggetti rientranti].</li>
</ul>
<h3>4. Ruoli e responsabilità</h3>
<ul>
<li><strong>Direzione (Silvia Garretto)</strong>: approva il piano formativo e ne assicura le risorse; partecipa alla formazione di governance prevista dalla NIS2.</li>
<li><strong>RSGSI (Massimo Tagliavini)</strong>: definisce i fabbisogni di competenza per ruolo, predispone il piano formativo annuale, assegna i corsi e monitora il completamento.</li>
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: individua i fabbisogni tecnici specifici (cloud, sviluppo sicuro, gestione incidenti) e supporta la formazione tecnica.</li>
<li><strong>Personale (dipendenti e collaboratori)</strong>: partecipa alle attività formative assegnate e mantiene aggiornata la propria consapevolezza.</li>
</ul>
<h3>5. Flusso/attività passo-passo</h3>
<ol>
<li><strong>Definizione dei fabbisogni</strong> (input: ruoli SGSI, esiti rischi/audit/incidenti; output: matrice competenze per ruolo). Per ciascun ruolo si individuano le competenze richieste (cl. 7.2 a/b) e si rileva il divario rispetto alle competenze possedute.</li>
<li><strong>Pianificazione formativa</strong> (output: piano formativo annuale). Il RSGSI definisce corsi e attività di awareness, con priorità, destinatari e scadenze; il piano include almeno: formazione di awareness generale per tutti, formazione tecnica per IT/sviluppo, sessione di governance per la Direzione, modulo sulla protezione delle PII.</li>
<li><strong>Assegnazione ed erogazione</strong> (input: piano; output: corsi assegnati). I corsi sono assegnati al personale tramite il <em>modulo "Formazione"</em> della piattaforma; l'erogazione può avvenire in modalità e-learning, in aula o on-the-job.</li>
<li><strong>Consapevolezza continua</strong> (cl. 7.3): si diffondono comunicazioni periodiche su politiche, minacce attuali (es. phishing) e responsabilità individuali; al nuovo ingresso (onboarding) è prevista una sessione di awareness obbligatoria, prima dell'accesso ai sistemi.</li>
<li><strong>Registrazione e verifica</strong> (output: evidenze di completamento). Per ogni assegnazione si registrano stato, data di completamento ed esito di eventuali test/quiz nel modulo "Formazione".</li>
<li><strong>Valutazione dell'efficacia</strong>: il RSGSI verifica il tasso di completamento e l'adeguatezza delle competenze acquisite (es. esiti dei test, riduzione di incidenti legati a errore umano); le carenze diventano azioni di miglioramento o NC.</li>
<li><strong>Gestione di ingressi e cessazioni</strong>: all'assunzione si definiscono i requisiti di competenza e consapevolezza; alla cessazione si rammentano gli obblighi di riservatezza residui (A.6.5).</li>
</ol>
<h3>6. Controlli ISO/clausole collegati</h3>
<p>cl. 7.2/7.3; A.6.3 (formazione e awareness), A.6.1/A.6.2/A.6.5 (ciclo di vita del rapporto), A.8.28 (codifica sicura per gli sviluppatori). La formazione è input/output del Riesame della Direzione (cl. 9.3) e misura di trattamento del rischio "errore umano" (cl. 6.1).</p>
<h3>7. Registrazioni/evidenze</h3>
<ul>
<li><strong>Matrice delle competenze per ruolo</strong> e <strong>piano formativo annuale</strong> (mantenuti dal RSGSI nella piattaforma/archivio cloud).</li>
<li><strong>Assegnazioni e attestati di completamento</strong> (stato, date, esiti test): modulo "Formazione".</li>
<li><strong>Evidenze di awareness di onboarding</strong> e comunicazioni periodiche.</li>
</ul>
<h3>8. Riesame e versionamento</h3>
<p>Il piano formativo è riesaminato e aggiornato <strong>almeno annualmente</strong> e ad ogni cambiamento rilevante (nuovi ruoli, nuove tecnologie/provider, nuove minacce, modifiche normative). La presente procedura è versionata e approvata dalla Direzione secondo la Procedura di Controllo dei Documenti.</p>
+65
View File
@@ -0,0 +1,65 @@
<!--META|doc_type=procedura_gestione_accessi|title=Procedura di Gestione degli Accessi (onboarding, offboarding, MFA)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") concede, modifica, riesamina e revoca gli accessi logici alle proprie risorse informative, garantendo che ogni utente disponga esclusivamente dei privilegi necessari al proprio ruolo (principio del <em>least privilege</em>) e che ogni accesso sia protetto da <strong>autenticazione a più fattori (MFA)</strong>. La procedura copre l'intero ciclo di vita dell'identità: <em>onboarding</em>, variazioni di ruolo, riesame periodico e <em>offboarding</em>.</p>
<h2>2. Ambito</h2>
<p>Si applica a tutti gli accessi logici a: piattaforme cloud (Aruba IT, Hetzner DE), pannelli SaaS multi-tenant erogati ai clienti, repository di codice, strumenti di collaborazione, posta elettronica, piattaforme di intelligenza artificiale tramite API, gestore di password aziendale e ai <strong>PC portatili cifrati</strong> in dotazione. Riguarda dipendenti, collaboratori esterni a P.IVA e utenze tecniche/di servizio. <strong>Non esistono server in sede</strong>: non sono previsti accessi fisici a sale macchine, la cui sicurezza è ereditata dai fornitori cloud secondo il modello di responsabilità condivisa.</p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – Annex A: A.5.15 (Controllo degli accessi), A.5.16 (Gestione delle identità), A.5.17 (Informazioni di autenticazione), A.5.18 (Diritti di accesso), A.8.2 (Diritti di accesso privilegiati), A.8.3 (Restrizione dell'accesso alle informazioni), A.8.5 (Autenticazione sicura).</li>
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – controlli per accessi nei servizi cloud e protezione dei dati personali.</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (recepimento Direttiva (UE) 2022/2555 – NIS2): art. 24 (misure di gestione del rischio).</li>
<li>Politica di Controllo degli Accessi e Manuale SGSI dell'Azienda.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva la procedura, autorizza gli accessi privilegiati critici e le deroghe</td></tr>
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Coordina i riesami periodici degli accessi e mantiene le evidenze</td></tr>
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Crea/modifica/revoca le utenze, configura l'MFA, esegue le verifiche tecniche</td></tr>
<tr><td>Responsabile dell'unità richiedente</td><td><em>variabile</em></td><td>Richiede e giustifica gli accessi del proprio personale</td></tr>
</table>
<h2>5. Flusso/attività passo-passo</h2>
<h3>5.1 Onboarding (ingresso di un nuovo utente)</h3>
<ol>
<li>Il responsabile invia a Simon Fattori la richiesta con ruolo, profilo di accesso richiesto e data di inizio.</li>
<li>Simon Fattori crea l'identità sulle sole piattaforme necessarie al ruolo, applicando il <em>least privilege</em> e profili predefiniti per mansione.</li>
<li>Si attiva obbligatoriamente l'<strong>MFA su tutte le utenze</strong> (cloud, SaaS, repository, posta, gestore password, API AI). Senza MFA l'accesso non è abilitato.</li>
<li>Il PC portatile viene consegnato già <strong>cifrato a disco intero</strong>; le credenziali sono custodite solo nel gestore di password aziendale.</li>
<li>L'utente sottoscrive la Politica d'uso accettabile e riceve la formazione di sicurezza di base.</li>
</ol>
<h3>5.2 Variazione di ruolo</h3>
<ol>
<li>A ogni cambio di mansione il responsabile richiede l'aggiornamento del profilo.</li>
<li>Simon Fattori adegua i privilegi <strong>rimuovendo quelli non più pertinenti</strong> prima di aggiungere i nuovi (no accumulo di permessi).</li>
</ol>
<h3>5.3 Riesame periodico degli accessi</h3>
<ol>
<li>Con cadenza <strong>almeno semestrale</strong> Massimo Tagliavini e Simon Fattori riesaminano tutte le utenze, con particolare attenzione agli <strong>accessi privilegiati</strong> e alle utenze di servizio.</li>
<li>Gli accessi non più giustificati vengono revocati; gli scostamenti sono registrati come <strong>non conformità</strong> nel modulo dedicato.</li>
</ol>
<h3>5.4 Offboarding (cessazione)</h3>
<ol>
<li>Alla comunicazione di cessazione, Simon Fattori <strong>disabilita tutte le utenze entro la data di fine rapporto</strong> (idealmente lo stesso giorno) e revoca chiavi API, token e accessi ai repository.</li>
<li>Si ritira il PC portatile, se ne verifica la cifratura e si procede alla riassegnazione o cancellazione sicura.</li>
<li>Si ruotano eventuali credenziali condivise di cui l'utente era a conoscenza.</li>
<li>L'avvenuto offboarding è registrato come evidenza.</li>
</ol>
<h2>6. Controlli ISO + riferimenti NIS2</h2>
<p>La procedura attua i controlli <strong>A.5.15–A.5.18</strong>, <strong>A.8.2, A.8.3, A.8.5</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Risponde inoltre alle <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in materia di controllo degli accessi e uso dell'autenticazione a più fattori. Le evidenze sono richiamabili anche per gli obblighi di supply chain verso i clienti NIS2.</p>
<h2>7. Registrazioni/evidenze</h2>
<ul>
<li>Richieste di accesso e relative autorizzazioni;</li>
<li>Report dei riesami periodici degli accessi;</li>
<li>Registrazioni di onboarding/offboarding e di ritiro dei dispositivi;</li>
<li>Configurazione MFA e log di accesso conservati dalle piattaforme cloud.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>Procedura approvata dalla Presidente. Revisione almeno annuale o a fronte di cambiamenti significativi delle piattaforme o dei ruoli. Versione 1.0.</p>
+56
View File
@@ -0,0 +1,56 @@
<!--META|doc_type=procedura_incident|title=Procedura di Gestione degli Incidenti e Notifica NIS2|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") rileva, classifica, gestisce e — quando dovuto — notifica gli incidenti di sicurezza delle informazioni, assicurando una risposta tempestiva, la riduzione degli impatti e il rispetto degli obblighi di notifica previsti dalla normativa NIS2. La procedura disciplina anche la <strong>comunicazione ai clienti impattati</strong> nel ruolo dell'Azienda quale fornitore di soggetti NIS2.</p>
<h2>2. Ambito</h2>
<p>Si applica a tutti gli incidenti che riguardano la riservatezza, l'integrità o la disponibilità delle informazioni e dei servizi dell'Azienda: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), <strong>servizi SaaS</strong> multi-tenant, <strong>endpoint</strong> (PC portatili cifrati), <strong>piattaforme di intelligenza artificiale</strong> usate via API e i prodotti in licenza on-premise per la parte di responsabilità dell'Azienda. Non riguarda sale server in sede, <strong>assenti</strong> per l'architettura cloud-only.</p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – A.5.24 (Pianificazione e preparazione della gestione degli incidenti), A.5.25 (Valutazione e decisione sugli eventi), A.5.26 (Risposta agli incidenti), A.5.27 (Apprendimento dagli incidenti), A.5.28 (Raccolta delle evidenze), A.5.7 (Threat intelligence).</li>
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – gestione degli incidenti nei servizi cloud e sui dati personali.</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (recepimento Direttiva (UE) 2022/2555 – NIS2): <strong>art. 23 (governance)</strong>, <strong>art. 24 (misure di gestione del rischio)</strong>, <strong>art. 25 (obblighi di notifica degli incidenti)</strong>.</li>
<li><strong>Determinazione ACN n. 164179/2025</strong> e Determinazioni vigenti per i criteri di significatività, la tassonomia e le modalità di notifica al CSIRT Italia tramite la piattaforma ACN.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Decide la notifica alle Autorità e la comunicazione esterna; informa gli organi di gestione (art. 23)</td></tr>
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Coordina la gestione dell'incidente, cura le registrazioni e la relazione finale</td></tr>
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Contiene, eradica e ripristina; raccoglie le evidenze tecniche</td></tr>
<tr><td>DPO esterno</td><td><em>Consulente esterno</em></td><td>Valuta se vi è violazione di dati personali e gli obblighi GDPR verso il Garante</td></tr>
</table>
<h2>5. Flusso/attività passo-passo</h2>
<ol>
<li><strong>Rilevazione e segnalazione.</strong> Chiunque rilevi un evento sospetto lo segnala immediatamente a Simon Fattori. L'evento è registrato nel <strong>modulo "Incidenti"</strong> della piattaforma con data e ora di conoscenza.</li>
<li><strong>Triage e classificazione.</strong> Simon Fattori, con Massimo Tagliavini, valuta natura, origine e impatto (riservatezza/integrità/disponibilità) e assegna una severità. Si verifica se l'incidente è <strong>"significativo"</strong> secondo i criteri di legge e delle Determinazioni ACN (es. impatto operativo grave, numero di utenti coinvolti, durata, danno economico/reputazionale, effetti transfrontalieri). In caso di dubbio: <strong>[DA VERIFICARE]</strong> con la Direzione e il supporto consulenziale.</li>
<li><strong>Contenimento, eradicazione, ripristino.</strong> Simon Fattori isola i sistemi/account compromessi, rimuove la causa e ripristina il servizio anche tramite i backup (vedi Procedura di Backup e Ripristino), tracciando i tempi delle fasi.</li>
<li><strong>Notifica al CSIRT Italia (ACN)</strong> – se l'incidente è significativo, secondo le tempistiche dell'<strong>art. 25 del D.Lgs. 138/2024</strong>:
<ul>
<li><strong>Pre-allarme (early warning) entro 24 ore</strong> dalla conoscenza dell'incidente significativo;</li>
<li><strong>Notifica completa entro 72 ore</strong> dalla conoscenza;</li>
<li><strong>Relazione finale entro 1 mese</strong> dalla notifica completa (e relazioni intermedie se richieste).</li>
</ul>
La notifica avviene tramite la piattaforma ACN; decide e autorizza la Direzione.</li>
<li><strong>Valutazione dati personali.</strong> Se l'incidente comporta una violazione di dati personali, il DPO valuta gli obblighi GDPR (eventuale notifica al Garante entro 72 ore e comunicazione agli interessati).</li>
<li><strong>Comunicazione ai clienti impattati.</strong> Nel ruolo di <strong>fornitore di clienti NIS2</strong>, l'Azienda informa <strong>tempestivamente</strong> i clienti i cui servizi/dati sono coinvolti, secondo gli <strong>obblighi contrattuali di supply chain</strong>, fornendo gli elementi utili ai loro adempimenti di notifica.</li>
<li><strong>Chiusura e lezioni apprese.</strong> A risoluzione, si redige la relazione finale, si aprono eventuali <strong>non conformità</strong> e azioni correttive nel modulo "Non conformità" e si aggiorna il <strong>modulo "Rischi"</strong> se emergono nuovi rischi.</li>
</ol>
<h2>6. Controlli ISO + riferimenti NIS2 (tempistiche)</h2>
<p>La procedura attua i controlli <strong>A.5.24–A.5.28</strong> e <strong>A.5.7</strong> di ISO/IEC 27001:2022, integrati dai controlli cloud di ISO/IEC 27017/27018. Sul piano normativo dà attuazione agli <strong>artt. 23, 24 e 25 del D.Lgs. 138/2024</strong>. Le <strong>tempistiche esatte di notifica</strong> sono: <strong>early warning 24 ore</strong>, <strong>notifica completa 72 ore</strong>, <strong>relazione finale 1 mese</strong>. I criteri di significatività seguono la <strong>Determinazione ACN n. 164179/2025</strong> e le Determinazioni vigenti.</p>
<h2>7. Registrazioni/evidenze</h2>
<ul>
<li>Scheda incidente nel modulo "Incidenti" (cronologia, severità, classificazione);</li>
<li>Evidenze tecniche raccolte (log, immagini, comunicazioni), preservate ai sensi di A.5.28;</li>
<li>Ricevute di notifica al CSIRT/ACN e relazione finale;</li>
<li>Comunicazioni ai clienti e, se del caso, al DPO/Garante;</li>
<li>Non conformità e azioni correttive collegate.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>Procedura approvata dalla Presidente. Revisione almeno annuale, dopo ogni incidente significativo o a fronte di aggiornamenti delle Determinazioni ACN. Versione 1.0.</p>
+65
View File
@@ -0,0 +1,65 @@
<!--META|doc_type=procedura_backup_ripristino|title=Procedura di Backup e Ripristino|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") esegue, protegge, verifica e ripristina le copie di sicurezza (backup) delle informazioni e dei servizi critici, al fine di garantire la <strong>disponibilità e l'integrità</strong> dei dati e la capacità di ripristino in caso di guasto, errore, cancellazione accidentale o attacco (es. ransomware).</p>
<h2>2. Ambito</h2>
<p>Si applica ai dati e ai servizi gestiti in cloud: <strong>database e applicativi SaaS</strong> multi-tenant, codice sorgente e configurazioni dei prodotti, dati dei clienti trattati nel SaaS, posta e documenti aziendali. Le copie risiedono <strong>su cloud</strong> (Aruba IT, Hetzner DE). Gli <strong>endpoint</strong> (PC portatili cifrati) non conservano dati critici come unica copia: i dati di lavoro risiedono nei servizi cloud. <strong>Non esistono backup su nastro o server in sede.</strong></p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – A.8.13 (Backup delle informazioni), A.8.14 (Ridondanza delle strutture di elaborazione), A.5.30 (Pronto intervento ICT per la continuità operativa), A.8.24 (Uso della crittografia).</li>
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – backup e protezione dei dati personali nei servizi cloud.</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, incluse continuità operativa e backup).</li>
<li>Procedura di Continuità Operativa e Disaster Recovery (BCP/DR) dell'Azienda.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva la strategia di backup e i livelli di servizio (RPO/RTO)</td></tr>
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Verifica che i test di ripristino siano eseguiti e documentati</td></tr>
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Configura ed esegue i backup, ne monitora l'esito, esegue i ripristini e i test</td></tr>
</table>
<h2>5. Flusso/attività passo-passo</h2>
<h3>5.1 Pianificazione</h3>
<ol>
<li>Per ciascun servizio critico si definiscono <strong>RPO</strong> (massima perdita di dati tollerata) e <strong>RTO</strong> (tempo massimo di ripristino), approvati dalla Direzione.</li>
<li>Si adotta lo schema di riferimento <strong>3-2-1</strong> per quanto applicabile in cloud: più copie, su servizi/regioni distinti, con almeno una copia logicamente separata e protetta da modifiche.</li>
</ol>
<h3>5.2 Esecuzione</h3>
<ol>
<li>I backup dei database SaaS e dei dati critici sono <strong>automatici e schedulati</strong> (frequenza coerente con l'RPO, di norma giornaliera).</li>
<li>Le copie sono <strong>cifrate</strong> a riposo (A.8.24) e conservate in cloud con accesso ristretto e <strong>MFA</strong>.</li>
<li>Ove disponibile, si attiva l'<strong>immutabilità</strong> delle copie (protezione anti-ransomware) e la separazione delle credenziali di gestione dei backup.</li>
<li>Si applica una <strong>politica di retention</strong> definita (es. copie giornaliere a breve termine e copie a più lunga conservazione), nel rispetto degli obblighi sui dati personali (minimizzazione e cancellazione, A.8.13/27018).</li>
</ol>
<h3>5.3 Monitoraggio</h3>
<ol>
<li>Simon Fattori verifica l'<strong>esito di ogni job</strong> di backup; i fallimenti generano un alert e una verifica entro le 24 ore lavorative successive.</li>
<li>Gli esiti negativi ricorrenti sono registrati come <strong>non conformità</strong>.</li>
</ol>
<h3>5.4 Test di ripristino</h3>
<ol>
<li>Con cadenza <strong>almeno trimestrale</strong> si esegue un <strong>test di ripristino</strong> su un dato/servizio campione, verificando integrità e tempi rispetto a RTO/RPO.</li>
<li>L'esito è documentato; eventuali scostamenti attivano azioni correttive.</li>
</ol>
<h3>5.5 Ripristino reale</h3>
<ol>
<li>In caso di incidente, il ripristino è autorizzato e coordinato secondo la Procedura BCP/DR; si privilegia una copia integra e verificata; al termine si validano i dati ripristinati.</li>
</ol>
<h2>6. Controlli ISO + riferimenti NIS2</h2>
<p>La procedura attua i controlli <strong>A.8.13, A.8.14, A.5.30, A.8.24</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di backup e continuità. Le evidenze dei test di ripristino sono utili anche a dimostrare resilienza ai clienti NIS2 nell'ambito della supply chain.</p>
<h2>7. Registrazioni/evidenze</h2>
<ul>
<li>Piano di backup con RPO/RTO per servizio;</li>
<li>Log/report di esecuzione e di esito dei job;</li>
<li>Rapporti dei test di ripristino trimestrali;</li>
<li>Registro delle non conformità su fallimenti di backup/ripristino.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>Procedura approvata dalla Presidente. Revisione almeno annuale o a fronte di modifiche dei servizi cloud o dei requisiti RPO/RTO. Versione 1.0.</p>
+51
View File
@@ -0,0 +1,51 @@
<!--META|doc_type=procedura_change_management|title=Procedura di Gestione dei Cambiamenti|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") richiede, valuta, autorizza, attua e verifica i cambiamenti che possono influire sulla sicurezza, la disponibilità e l'integrità dei propri sistemi, servizi e configurazioni, in modo controllato e tracciabile, riducendo il rischio di indisponibilità o di introduzione di vulnerabilità.</p>
<h2>2. Ambito</h2>
<p>Si applica ai cambiamenti su: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), configurazioni di rete e di sicurezza, <strong>servizi SaaS</strong> multi-tenant, rilasci di nuove versioni dei <strong>prodotti software</strong> (SaaS e on-premise presso i clienti), integrazioni con <strong>piattaforme AI</strong> via API, configurazioni degli <strong>endpoint</strong> (PC portatili cifrati) e modifiche alle policy/strumenti di sicurezza. Sono esclusi i cambiamenti puramente di contenuto privi di impatto su sicurezza o servizio.</p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – A.8.32 (Gestione dei cambiamenti), A.8.31 (Separazione degli ambienti di sviluppo, test ed esercizio), A.8.28 (Codifica sicura), A.8.25 (Ciclo di vita di sviluppo sicuro), A.8.29 (Test di sicurezza nello sviluppo e nell'accettazione).</li>
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – gestione dei cambiamenti nei servizi cloud.</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, incluse sicurezza nello sviluppo e gestione delle modifiche).</li>
<li>Procedura di Gestione delle Vulnerabilità e delle Patch dell'Azienda.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Autorizza i cambiamenti ad alto impatto e le finestre di rilascio critiche</td></tr>
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Verifica che i cambiamenti rilevanti siano valutati nel rischio e documentati</td></tr>
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Valuta tecnicamente, attua il cambiamento, esegue i test e il piano di rollback</td></tr>
<tr><td>Richiedente</td><td><em>variabile</em></td><td>Apre la richiesta di cambiamento descrivendo finalità e impatto atteso</td></tr>
</table>
<h2>5. Flusso/attività passo-passo</h2>
<ol>
<li><strong>Richiesta (RFC).</strong> Il richiedente apre una richiesta di cambiamento descrivendo obiettivo, sistemi coinvolti, impatto atteso e urgenza.</li>
<li><strong>Valutazione di impatto e rischio.</strong> Simon Fattori classifica il cambiamento (standard a basso rischio / normale / emergenza) e ne valuta gli effetti su sicurezza, dati personali e continuità. I cambiamenti rilevanti sono registrati nel <strong>modulo "Rischi"</strong> ove pertinente.</li>
<li><strong>Autorizzazione.</strong> I cambiamenti standard a basso rischio sono approvati da Simon Fattori; quelli normali ad alto impatto richiedono l'autorizzazione della Direzione. Va sempre definito un <strong>piano di rollback</strong>.</li>
<li><strong>Test in ambiente separato.</strong> Le modifiche software sono validate in ambienti di <strong>sviluppo/test separati</strong> dall'esercizio (A.8.31), con test funzionali e di sicurezza (A.8.29) prima del rilascio.</li>
<li><strong>Attuazione.</strong> Il cambiamento è applicato in una finestra concordata; per il SaaS si privilegiano rilasci controllati. Per i <strong>prodotti on-premise</strong>, l'Azienda fornisce ai clienti note di rilascio e indicazioni di aggiornamento, nel rispetto degli obblighi contrattuali di supply chain.</li>
<li><strong>Verifica post-cambiamento.</strong> Si verifica il corretto funzionamento e l'assenza di effetti collaterali; in caso di esito negativo si attiva il rollback.</li>
<li><strong>Chiusura.</strong> Esito e documentazione sono registrati; eventuali anomalie alimentano <strong>non conformità</strong> e azioni correttive.</li>
</ol>
<h3>5.1 Cambiamenti di emergenza</h3>
<p>Per cambiamenti urgenti (es. mitigazione di una vulnerabilità critica), l'attuazione può precedere l'autorizzazione formale, ma <strong>deve essere documentata a posteriori entro il giorno lavorativo successivo</strong> e ratificata dalla Direzione.</p>
<h2>6. Controlli ISO + riferimenti NIS2</h2>
<p>La procedura attua i controlli <strong>A.8.32, A.8.31, A.8.25, A.8.28, A.8.29</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di sicurezza dello sviluppo e gestione delle modifiche, contribuendo a prevenire incidenti che ricadrebbero negli obblighi di notifica (art. 25).</p>
<h2>7. Registrazioni/evidenze</h2>
<ul>
<li>Richieste di cambiamento (RFC) e relative autorizzazioni;</li>
<li>Esiti dei test e dei rilasci; note di rilascio per i clienti on-premise;</li>
<li>Documentazione dei cambiamenti di emergenza e relativa ratifica;</li>
<li>Non conformità collegate a cambiamenti falliti.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>Procedura approvata dalla Presidente. Revisione almeno annuale o a fronte di modifiche del processo di rilascio. Versione 1.0.</p>
+61
View File
@@ -0,0 +1,61 @@
<!--META|doc_type=procedura_vulnerabilita_patch|title=Procedura di Gestione delle Vulnerabilità e delle Patch|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") identifica, valuta, prioritizza e rimedia le vulnerabilità tecniche dei propri sistemi, applicazioni e componenti software, garantendo l'applicazione tempestiva delle patch di sicurezza e la riduzione della superficie di attacco.</p>
<h2>2. Ambito</h2>
<p>Si applica a: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), sistemi operativi e middleware dei servizi <strong>SaaS</strong>, <strong>dipendenze e librerie</strong> di terze parti dei prodotti software (SaaS e on-premise), integrazioni con <strong>piattaforme AI</strong> via API, configurazioni di sicurezza e <strong>endpoint</strong> (PC portatili cifrati). Per i prodotti <strong>on-premise installati presso i clienti</strong>, l'ambito copre la produzione e distribuzione delle patch ai clienti.</p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – A.8.8 (Gestione delle vulnerabilità tecniche), A.8.7 (Protezione dai malware), A.8.9 (Gestione della configurazione), A.5.7 (Threat intelligence), A.8.16 (Attività di monitoraggio).</li>
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – gestione delle vulnerabilità nei servizi cloud.</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, incluse sicurezza nell'acquisizione/sviluppo/manutenzione e divulgazione delle vulnerabilità).</li>
<li>Procedura di Gestione dei Cambiamenti e Procedura di Gestione degli Incidenti dell'Azienda.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva le tempistiche di rimedio e autorizza eventuali deroghe motivate</td></tr>
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Verifica il rispetto dei tempi di rimedio e registra le non conformità</td></tr>
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Raccoglie le fonti, valuta e prioritizza le vulnerabilità, applica le patch e i test</td></tr>
</table>
<h2>5. Flusso/attività passo-passo</h2>
<h3>5.1 Identificazione</h3>
<ol>
<li>Simon Fattori monitora con continuità le fonti di vulnerabilità: bollettini dei fornitori cloud, avvisi CSIRT/ACN, advisory delle librerie/dipendenze usate dai prodotti, scansioni periodiche e avvisi automatici delle piattaforme (A.5.7, A.8.16).</li>
<li>Si mantiene un inventario aggiornato dei componenti software e delle dipendenze per correlare rapidamente le vulnerabilità ai sistemi interessati.</li>
</ol>
<h3>5.2 Valutazione e prioritizzazione</h3>
<ol>
<li>Ogni vulnerabilità è valutata per <strong>gravità</strong> (es. punteggio CVSS), <strong>esposizione</strong> e <strong>impatto</strong> sui dati e sui servizi, e registrata se rilevante nel <strong>modulo "Rischi"</strong>.</li>
<li>Si assegna una priorità con relativi <strong>tempi target di rimedio</strong>, ad esempio: critiche <strong>entro 48–72 ore</strong>, alte <strong>entro 7 giorni</strong>, medie <strong>entro 30 giorni</strong>, basse alla successiva finestra pianificata. <em>[I valori esatti sono da confermare nel piano di trattamento del rischio.]</em></li>
</ol>
<h3>5.3 Rimedio</h3>
<ol>
<li>L'applicazione delle patch segue la <strong>Procedura di Gestione dei Cambiamenti</strong> (test in ambiente separato e piano di rollback); per le vulnerabilità critiche si attiva il percorso di <strong>cambiamento di emergenza</strong>.</li>
<li>Dove la patch non è immediatamente disponibile, si adottano <strong>misure compensative</strong> (es. restrizione accessi, isolamento, disattivazione della funzione vulnerabile).</li>
<li>Per i <strong>prodotti on-premise</strong>, l'Azienda rilascia la patch e <strong>informa tempestivamente i clienti impattati</strong> con le istruzioni di aggiornamento, in adempimento agli obblighi contrattuali di supply chain.</li>
</ol>
<h3>5.4 Verifica e chiusura</h3>
<ol>
<li>Si verifica l'effettiva risoluzione (riscansione/test) e si chiude la vulnerabilità.</li>
<li>Gli scostamenti dai tempi target sono registrati come <strong>non conformità</strong> con azione correttiva.</li>
<li>Se una vulnerabilità è stata sfruttata, si attiva la <strong>Procedura di Gestione degli Incidenti</strong>.</li>
</ol>
<h2>6. Controlli ISO + riferimenti NIS2</h2>
<p>La procedura attua i controlli <strong>A.8.8, A.8.7, A.8.9, A.5.7, A.8.16</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di gestione e divulgazione delle vulnerabilità. Una vulnerabilità sfruttata che causi un incidente significativo attiva gli obblighi di notifica dell'<strong>art. 25</strong> (early warning 24 ore, notifica completa 72 ore, relazione finale 1 mese).</p>
<h2>7. Registrazioni/evidenze</h2>
<ul>
<li>Registro delle vulnerabilità con gravità, priorità e tempi di rimedio;</li>
<li>Report delle scansioni e degli aggiornamenti applicati;</li>
<li>Comunicazioni di patch ai clienti on-premise;</li>
<li>Non conformità e azioni correttive per i ritardi di rimedio.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>Procedura approvata dalla Presidente. Revisione almeno annuale o a fronte di modifiche degli strumenti di scansione o del parco software. Versione 1.0.</p>
+65
View File
@@ -0,0 +1,65 @@
<!--META|doc_type=procedura_continuita_dr|title=Procedura di Continuità Operativa e Disaster Recovery (BCP/DR)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") garantisce la <strong>continuità operativa</strong> dei propri servizi critici e il <strong>ripristino in caso di disastro</strong> (Disaster Recovery), al fine di limitare l'impatto di eventi gravi sulla disponibilità dei servizi, sui dati dei clienti e sull'operatività aziendale, nel rispetto degli obiettivi di tempo (RTO) e di perdita dati (RPO) concordati.</p>
<h2>2. Ambito</h2>
<p>Si applica ai <strong>servizi SaaS</strong> multi-tenant erogati in cloud, ai dati dei clienti trattati nel SaaS, ai sistemi di sviluppo e rilascio dei <strong>prodotti software</strong>, agli strumenti di collaborazione e alla capacità di lavoro del team (interamente su <strong>PC portatili cifrati</strong>, da remoto). L'infrastruttura è <strong>interamente cloud</strong> (Aruba IT, Hetzner DE): gli scenari considerati riguardano indisponibilità di un provider o di una regione cloud, attacchi (es. ransomware), errori gravi e indisponibilità del personale. <strong>Non sono considerati scenari di sala server in sede</strong>, assente per architettura.</p>
<h2>3. Riferimenti</h2>
<ul>
<li><strong>ISO/IEC 27001:2022</strong> – A.5.29 (Sicurezza delle informazioni durante un'interruzione), A.5.30 (Pronto intervento ICT per la continuità operativa), A.8.13 (Backup), A.8.14 (Ridondanza delle strutture di elaborazione).</li>
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – continuità e protezione dei dati personali nei servizi cloud.</li>
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, inclusi continuità operativa, gestione dei backup e ripristino in caso di disastro).</li>
<li>Procedura di Backup e Ripristino e Procedura di Gestione degli Incidenti dell'Azienda.</li>
</ul>
<h2>4. Ruoli e responsabilità</h2>
<table>
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Attiva lo stato di crisi, decide la comunicazione esterna e l'allocazione delle risorse</td></tr>
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Mantiene il piano BCP/DR, coordina i test e documenta gli esiti</td></tr>
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Esegue il ripristino tecnico dei servizi e dei dati secondo il piano</td></tr>
<tr><td>DPO esterno</td><td><em>Consulente esterno</em></td><td>Valuta gli impatti sui dati personali in scenari di indisponibilità/perdita</td></tr>
</table>
<h2>5. Flusso/attività passo-passo</h2>
<h3>5.1 Analisi di impatto e obiettivi</h3>
<ol>
<li>Si individuano i <strong>servizi critici</strong> (in primis il SaaS) tramite una Business Impact Analysis e si definiscono <strong>RTO</strong> e <strong>RPO</strong> per ciascuno, approvati dalla Direzione.</li>
<li>Si identificano le dipendenze critiche: provider cloud, provider AI, DNS, posta, gestore password.</li>
</ol>
<h3>5.2 Strategie di continuità</h3>
<ol>
<li>Si privilegiano architetture <strong>ridondate</strong> e la possibilità di ripristino su una <strong>regione/fornitore alternativo</strong> ove fattibile (A.8.14), sfruttando i backup cloud cifrati e, ove disponibile, immutabili.</li>
<li>Essendo il team interamente remoto su portatili cifrati, la <strong>continuità del lavoro</strong> è intrinsecamente resiliente a indisponibilità di una singola sede.</li>
</ol>
<h3>5.3 Attivazione e gestione della crisi</h3>
<ol>
<li>Al verificarsi di un evento grave, la Direzione (o suo delegato) <strong>attiva il piano</strong> e nomina i referenti.</li>
<li>Si attiva in parallelo la <strong>Procedura di Gestione degli Incidenti</strong>; se l'evento configura un incidente significativo, si applicano gli obblighi di <strong>notifica al CSIRT Italia (ACN)</strong>.</li>
<li>Simon Fattori esegue il ripristino dei servizi e dei dati dalle copie integre (Procedura di Backup e Ripristino), rispettando RTO/RPO.</li>
</ol>
<h3>5.4 Comunicazione</h3>
<ol>
<li>Si comunica <strong>tempestivamente ai clienti impattati</strong> lo stato del disservizio e i tempi stimati di ripristino, in adempimento agli obblighi contrattuali di supply chain verso i clienti NIS2.</li>
</ol>
<h3>5.5 Ritorno alla normalità e test</h3>
<ol>
<li>Verificato il pieno ripristino, si dichiara la chiusura della crisi e si redige il rapporto di evento con lezioni apprese.</li>
<li>Il piano BCP/DR è <strong>testato almeno una volta l'anno</strong> (esercitazione/simulazione); gli esiti aggiornano il piano e alimentano azioni correttive.</li>
</ol>
<h2>6. Controlli ISO + riferimenti NIS2 (tempistiche)</h2>
<p>La procedura attua i controlli <strong>A.5.29, A.5.30, A.8.13, A.8.14</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di continuità operativa e ripristino. Quando l'interruzione configura un incidente significativo, valgono le tempistiche di notifica dell'<strong>art. 25</strong>: <strong>early warning entro 24 ore</strong>, <strong>notifica completa entro 72 ore</strong>, <strong>relazione finale entro 1 mese</strong>.</p>
<h2>7. Registrazioni/evidenze</h2>
<ul>
<li>Piano BCP/DR con BIA, RTO/RPO e dipendenze critiche;</li>
<li>Rapporti delle esercitazioni/test annuali;</li>
<li>Rapporti degli eventi reali con lezioni apprese;</li>
<li>Comunicazioni ai clienti durante i disservizi e non conformità collegate.</li>
</ul>
<h2>8. Riesame e versionamento</h2>
<p>Procedura approvata dalla Presidente. Revisione almeno annuale, dopo ogni test o evento significativo o a fronte di cambiamenti dell'architettura cloud. Versione 1.0.</p>
+46
View File
@@ -0,0 +1,46 @@
<!--META|doc_type=istruzione_hardening_laptop|title=Istruzione Operativa: Configurazione Sicura dei PC Portatili (Hardening)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Definire i passi minimi e obbligatori per configurare in modo sicuro ogni PC portatile aziendale di Nuova Agile Technology srl, indipendentemente dal sistema operativo (Windows, macOS o Linux). Poiché l'azienda non ha server in sede e lavora interamente in cloud, il portatile è il principale punto di esposizione: va trattato come un asset critico.</p>
<h2>2. A chi si applica</h2>
<p>A tutti i dipendenti e collaboratori esterni (P.IVA) che usano un portatile, aziendale o personale, per accedere a servizi, codice o dati aziendali e dei clienti.</p>
<h2>3. Prerequisiti</h2>
<ul>
<li>Portatile assegnato e censito nell'inventario asset (vedi Resp. IT).</li>
<li>Account utente personale, password manager aziendale e MFA già attivati.</li>
<li>Permessi di amministratore locale solo se autorizzati dal Resp. IT.</li>
</ul>
<h2>4. Passi operativi</h2>
<ol>
<li><strong>Cifratura del disco (obbligatoria).</strong> Attiva la cifratura dell'intero disco prima di salvare qualsiasi dato: Windows = BitLocker (TPM + PIN); macOS = FileVault; Linux = LUKS. Conserva la chiave di recupero nel password manager aziendale, mai su un foglio o file in chiaro.</li>
<li><strong>Account e privilegi.</strong> Usa un account standard per il lavoro quotidiano. Non navigare né leggere email da un account amministratore. Rinomina o disabilita gli account ospite/predefiniti.</li>
<li><strong>Blocco schermo.</strong> Imposta il blocco automatico dopo massimo 5 minuti di inattività e richiedi password/biometria allo sblocco. Blocca sempre manualmente (Win+L / Ctrl+Cmd+Q) lasciando la postazione.</li>
<li><strong>Schermata di avvio e firmware.</strong> Imposta una password al firmware/UEFI dove possibile e abilita Secure Boot. Disabilita l'avvio da USB se non necessario.</li>
<li><strong>Firewall e servizi.</strong> Attiva il firewall locale del sistema operativo. Disattiva condivisione file/stampanti, desktop remoto e servizi non usati.</li>
<li><strong>Antimalware.</strong> Mantieni attivo l'antimalware (Microsoft Defender o soluzione aziendale) con protezione in tempo reale e aggiornamenti automatici (vedi istruzione dedicata).</li>
<li><strong>Aggiornamenti.</strong> Abilita gli aggiornamenti automatici di sistema e applicazioni (vedi Istruzione Gestione Patch).</li>
<li><strong>Backup.</strong> Salva il lavoro su repository e storage cloud aziendali, non solo in locale. Verifica che il codice sia versionato (git) e i documenti sui drive aziendali.</li>
<li><strong>Software.</strong> Installa solo software da fonti ufficiali e necessario al lavoro. Rimuovi applicazioni inutili e plugin/estensioni browser non indispensabili.</li>
<li><strong>Rete.</strong> Su reti Wi-Fi pubbliche usa la connessione solo tramite VPN aziendale (se fornita) o hotspot personale. Non disattivare il firewall per "far funzionare" un servizio.</li>
<li><strong>MDM.</strong> Se è presente un sistema di gestione dispositivi (MDM), non rimuovere l'agente né eludere le policy applicate.</li>
</ol>
<h2>5. Verifiche e controlli di esito</h2>
<ul>
<li>Disco risulta cifrato (BitLocker/FileVault/LUKS = ON) e chiave di recupero presente nel password manager.</li>
<li>Blocco schermo automatico ≤ 5 minuti verificato lasciando il PC inattivo.</li>
<li>Firewall e antimalware attivi; ultimo aggiornamento entro 7 giorni.</li>
<li>Nessun account amministratore usato per attività quotidiane.</li>
</ul>
<h2>6. In caso di problema</h2>
<p>Se non riesci ad attivare la cifratura, perdi la chiave di recupero, sospetti software malevolo o il PC viene smarrito/rubato, contatta <strong>immediatamente</strong> il Resp. IT/Sicurezza <strong>Simon Fattori</strong>. In caso di furto o smarrimento, segnala entro poche ore: potrebbe attivare un incidente di sicurezza.</p>
<h2>7. Controlli ISO collegati</h2>
<p>ISO/IEC 27001:2022 — A.8.1 (dispositivi endpoint dell'utente), A.8.7 (protezione dai malware), A.5.17 (informazioni di autenticazione). Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per igiene informatica di base e sicurezza nell'uso dei sistemi.</p>
<h2>8. Versionamento</h2>
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale o a ogni cambiamento rilevante del parco dispositivi.</p>
+42
View File
@@ -0,0 +1,42 @@
<!--META|doc_type=istruzione_mfa|title=Istruzione Operativa: Autenticazione a Più Fattori (MFA)|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Garantire che ogni accesso ai servizi cloud e agli strumenti aziendali di Nuova Agile Technology srl sia protetto da autenticazione a più fattori (MFA), riducendo il rischio di compromissione anche in caso di password rubata. La sola password non è mai sufficiente.</p>
<h2>2. A chi si applica</h2>
<p>A tutti i dipendenti e collaboratori esterni che accedono a posta, repository di codice, console cloud (Aruba, Hetzner), piattaforme AI, password manager e gestore segreti.</p>
<h2>3. Prerequisiti</h2>
<ul>
<li>Smartphone aziendale o personale con app di autenticazione (es. Microsoft Authenticator, Google Authenticator, Aegis) installata.</li>
<li>Password manager aziendale attivo per conservare codici di recupero.</li>
<li>Account già creato sui servizi da proteggere.</li>
</ul>
<h2>4. Passi operativi</h2>
<ol>
<li><strong>Scegli il secondo fattore corretto.</strong> Ordine di preferenza: (1) chiave di sicurezza FIDO2/passkey, (2) app di autenticazione (codici TOTP o notifiche push), (3) come ultima scelta SMS. <strong>Evita gli SMS</strong> dove esiste un'alternativa.</li>
<li><strong>Attiva l'MFA su ogni servizio critico.</strong> Vai nelle impostazioni di sicurezza dell'account → "Autenticazione a due fattori / MFA" → abilita. Procedi servizio per servizio: posta, repository (es. GitHub/Gitea), console Aruba e Hetzner, piattaforme AI, password manager.</li>
<li><strong>Registra il dispositivo.</strong> Inquadra il QR code con l'app di autenticazione o registra la chiave FIDO2. Conferma inserendo il primo codice generato.</li>
<li><strong>Salva i codici di recupero.</strong> Ogni servizio genera codici di backup monouso: salvali <strong>solo</strong> nel password manager aziendale, mai in chiaro su file, email o foglietti.</li>
<li><strong>Imposta un secondo metodo.</strong> Dove possibile registra un secondo fattore (es. una seconda passkey o una chiave fisica di backup) per non restare bloccato se perdi lo smartphone.</li>
<li><strong>Proteggi il password manager.</strong> La master password deve essere lunga, unica e mai riutilizzata, e l'accesso al password manager deve avere MFA attivo.</li>
<li><strong>Account condivisi/segreti.</strong> Non condividere account personali. Le credenziali di servizio e i segreti applicativi vanno nel gestore segreti aziendale, non scambiati via chat o email.</li>
</ol>
<h2>5. Verifiche e controlli di esito</h2>
<ul>
<li>Effettua un login di prova: deve essere richiesto il secondo fattore.</li>
<li>Verifica che i codici di recupero siano presenti nel password manager per ogni servizio.</li>
<li>Conferma che almeno due metodi MFA siano registrati sui servizi più critici (posta e console cloud).</li>
<li>Nessun servizio critico risulta accessibile con la sola password.</li>
</ul>
<h2>6. In caso di problema</h2>
<p>Se perdi lo smartphone, non ricevi i codici, esaurisci i codici di recupero o sospetti un accesso non autorizzato, contatta <strong>subito</strong> il Resp. IT/Sicurezza <strong>Simon Fattori</strong> per il reset controllato. Non disattivare l'MFA per "comodità": è vietato.</p>
<h2>7. Controlli ISO collegati</h2>
<p>ISO/IEC 27001:2022 — A.5.17 (informazioni di autenticazione), A.8.5 (autenticazione sicura). Coerente con NIS2 (D.Lgs. 138/2024, art. 24): uso di autenticazione a più fattori e soluzioni di autenticazione sicura tra le misure di gestione del rischio.</p>
<h2>8. Versionamento</h2>
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale o all'introduzione di nuovi servizi.</p>
+50
View File
@@ -0,0 +1,50 @@
<!--META|doc_type=istruzione_onboarding_offboarding|title=Istruzione Operativa: Onboarding e Offboarding del Personale|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Definire i passi operativi per assegnare e revocare in modo controllato accessi, dispositivi e credenziali quando una persona entra o esce da Nuova Agile Technology srl. L'obiettivo è dare a ciascuno solo ciò che serve (minimo privilegio) e revocare tutto tempestivamente all'uscita.</p>
<h2>2. A chi si applica</h2>
<p>Al Resp. IT/Sicurezza (esecutore principale), alla Direzione (autorizzazione) e a chiunque gestisca account o strumenti. Riguarda dipendenti e collaboratori esterni (P.IVA).</p>
<h2>3. Prerequisiti</h2>
<ul>
<li>Richiesta formale di Direzione (Silvia Garretto) con ruolo, data inizio/fine e accessi necessari.</li>
<li>Inventario asset e elenco servizi aziendali aggiornati.</li>
<li>Password manager e gestore segreti aziendali.</li>
</ul>
<h2>4. Passi operativi — Onboarding</h2>
<ol>
<li><strong>Crea l'identità.</strong> Apri l'account aziendale (posta + identità SSO se presente) con nome utente standard. Forza il cambio password al primo accesso e attiva l'MFA.</li>
<li><strong>Assegna gli accessi minimi.</strong> Concedi solo i servizi/ruoli richiesti per la mansione. Per i collaboratori esterni limita l'accesso ai soli progetti pertinenti e, se possibile, con scadenza.</li>
<li><strong>Prepara il dispositivo.</strong> Consegna il portatile configurato secondo l'Istruzione Hardening (disco cifrato, antimalware, blocco schermo) e registralo nell'inventario.</li>
<li><strong>Consegna credenziali e segreti.</strong> Le credenziali di servizio passano tramite password manager/gestore segreti condiviso, mai via email o chat.</li>
<li><strong>Formazione iniziale.</strong> Fai leggere e accettare le istruzioni operative chiave (MFA, phishing, uso AI, hardening) e il regolamento sull'uso degli strumenti. Registra l'avvenuta consapevolezza.</li>
<li><strong>Registra tutto.</strong> Annota nel registro accessi data, ruolo e servizi concessi.</li>
</ol>
<h2>4-bis. Passi operativi — Offboarding</h2>
<ol>
<li><strong>Disabilita subito gli accessi.</strong> Alla data di cessazione (o prima, se richiesto) disabilita l'account principale e revoca le sessioni attive; non limitarti a cambiare la password.</li>
<li><strong>Revoca su ogni servizio.</strong> Rimuovi l'utente da posta, repository, console cloud, piattaforme AI, password manager, gestore segreti e MDM.</li>
<li><strong>Ruota i segreti condivisi.</strong> Cambia password, chiavi API e token a cui la persona aveva accesso. Questo vale specialmente per i collaboratori esterni.</li>
<li><strong>Recupera il dispositivo.</strong> Ritira il portatile, verifica la cifratura ed esegui il wipe sicuro prima di riassegnarlo. Se il dispositivo era personale, rimuovi dati e accessi aziendali.</li>
<li><strong>Trasferisci i dati.</strong> Riassegna repository, documenti e caselle condivise a un referente designato.</li>
<li><strong>Chiudi il registro.</strong> Annota data di revoca e segreti ruotati.</li>
</ol>
<h2>5. Verifiche e controlli di esito</h2>
<ul>
<li>Onboarding: login di prova con MFA riuscito; accessi limitati al necessario.</li>
<li>Offboarding: tentativo di accesso con il vecchio account = negato su tutti i servizi; segreti condivisi ruotati; dispositivo recuperato.</li>
<li>Revisione periodica (almeno semestrale) degli accessi attivi vs personale in forza.</li>
</ul>
<h2>6. In caso di problema</h2>
<p>Per dubbi su quali accessi concedere/revocare, segreti non ruotabili o dispositivi non recuperati, contatta il Resp. IT/Sicurezza <strong>Simon Fattori</strong>. In caso di uscita conflittuale, dare priorità assoluta alla revoca immediata degli accessi.</p>
<h2>7. Controlli ISO collegati</h2>
<p>ISO/IEC 27001:2022 — A.5.17 (informazioni di autenticazione), A.8.1 (endpoint), A.6.3 (consapevolezza in fase di ingresso). Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per controllo degli accessi e igiene informatica.</p>
<h2>8. Versionamento</h2>
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale.</p>
+43
View File
@@ -0,0 +1,43 @@
<!--META|doc_type=istruzione_patch|title=Istruzione Operativa: Gestione Patch e Aggiornamenti|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Assicurare che sistemi operativi, applicazioni, browser e dipendenze dei prodotti di Nuova Agile Technology srl siano aggiornati con le correzioni di sicurezza in tempi adeguati. Le vulnerabilità non corrette sono una delle principali cause di compromissione.</p>
<h2>2. A chi si applica</h2>
<p>A tutti i dipendenti e collaboratori esterni per i propri portatili e strumenti; al Resp. IT/Sicurezza per i servizi cloud, le immagini dei prodotti SaaS e on-premise.</p>
<h2>3. Prerequisiti</h2>
<ul>
<li>Portatile configurato secondo l'Istruzione Hardening.</li>
<li>Inventario di sistemi, applicazioni e componenti software con relativi responsabili.</li>
<li>Canale per ricevere avvisi di vulnerabilità (es. bollettini fornitori, CSIRT Italia).</li>
</ul>
<h2>4. Passi operativi</h2>
<ol>
<li><strong>Abilita gli aggiornamenti automatici.</strong> Sistema operativo (Windows Update, aggiornamenti macOS, gestore pacchetti Linux) e browser su aggiornamento automatico. Non rinviare gli aggiornamenti oltre il necessario.</li>
<li><strong>Aggiorna le applicazioni.</strong> Tieni aggiornati editor, strumenti di sviluppo, client VPN, antimalware ed estensioni del browser. Rimuovi software non più usato o non più supportato dal fornitore.</li>
<li><strong>Riavvia.</strong> Molti aggiornamenti si completano solo al riavvio: riavvia il portatile almeno una volta a settimana e quando richiesto.</li>
<li><strong>Definisci le priorità (a cura del Resp. IT).</strong> Classifica le patch per gravità. Criteri indicativi: vulnerabilità <em>critiche</em> sfruttate attivamente → applicare con urgenza (entro pochi giorni, [DA VERIFICARE] rispetto a SLA interni); <em>alte</em> → tempi brevi; <em>medie/basse</em> → ciclo ordinario.</li>
<li><strong>Dipendenze dei prodotti.</strong> Per i prodotti SaaS e on-premise, monitora le dipendenze (librerie, container, runtime). Aggiorna le immagini e ricostruisci gli artefatti quando emergono vulnerabilità note.</li>
<li><strong>Servizi cloud.</strong> Verifica e applica gli aggiornamenti sulle console e sui servizi gestiti (Aruba, Hetzner) secondo le indicazioni dei fornitori. Annota le finestre di manutenzione.</li>
<li><strong>Testa prima di rilasciare ai clienti.</strong> Per le patch dei prodotti consegnati ai clienti, verifica in ambiente di prova prima del rilascio per evitare regressioni.</li>
<li><strong>Registra.</strong> Tieni traccia di patch applicate, date e versioni nel registro aggiornamenti/asset.</li>
</ol>
<h2>5. Verifiche e controlli di esito</h2>
<ul>
<li>Sistema operativo e browser risultano all'ultima versione disponibile (verifica mensile minima).</li>
<li>Nessuna vulnerabilità critica nota rimasta non gestita oltre lo SLA interno.</li>
<li>Software non supportato rimosso o sostituito.</li>
<li>Registro aggiornamenti compilato e coerente con l'inventario.</li>
</ul>
<h2>6. In caso di problema</h2>
<p>Se un aggiornamento fallisce, causa malfunzionamenti, o non sai come trattare una vulnerabilità segnalata, contatta il Resp. IT/Sicurezza <strong>Simon Fattori</strong>. Non disinstallare le patch di sicurezza per "far funzionare" un'applicazione senza prima concordarlo.</p>
<h2>7. Controlli ISO collegati</h2>
<p>ISO/IEC 27001:2022 — A.8.8 (gestione delle vulnerabilità tecniche), A.8.7 (protezione dai malware), A.8.1 (endpoint). Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per igiene informatica e gestione delle vulnerabilità.</p>
<h2>8. Versionamento</h2>
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale.</p>
+42
View File
@@ -0,0 +1,42 @@
<!--META|doc_type=istruzione_log_review|title=Istruzione Operativa: Revisione di Log e Accessi|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Definire come e con quale frequenza il Resp. IT/Sicurezza esamina i registri (log) e gli accessi dei servizi cloud di Nuova Agile Technology srl, per rilevare tempestivamente attività anomale o accessi non autorizzati. In assenza di server in sede, i log dei servizi cloud sono la principale fonte di visibilità.</p>
<h2>2. A chi si applica</h2>
<p>Principalmente al Resp. IT/Sicurezza <strong>Simon Fattori</strong> (esecutore). I dipendenti e collaboratori collaborano segnalando attività anomale sui propri account.</p>
<h2>3. Prerequisiti</h2>
<ul>
<li>Logging attivo su tutti i servizi critici (posta, console Aruba/Hetzner, repository, piattaforme AI, password manager, prodotti SaaS).</li>
<li>Accesso autorizzato ai registri e, dove disponibile, agli avvisi di sicurezza dei fornitori.</li>
<li>Orari di riferimento coerenti (fuso Europe/Rome o UTC documentato) per correlare gli eventi.</li>
</ul>
<h2>4. Passi operativi</h2>
<ol>
<li><strong>Verifica giornaliera rapida.</strong> Controlla gli avvisi di sicurezza automatici ricevuti via email dai fornitori (nuovi accessi, login da nuovi dispositivi/paesi, blocchi MFA). Tratta ogni avviso come da valutare, non da ignorare.</li>
<li><strong>Revisione settimanale degli accessi.</strong> Esamina i log di autenticazione dei servizi critici cercando: login falliti ripetuti, accessi da località/orari inusuali, accessi a orari notturni non giustificati, nuove sessioni o dispositivi non riconosciuti.</li>
<li><strong>Controllo dei cambiamenti privilegiati.</strong> Verifica modifiche a ruoli/permessi, creazione di nuovi account, generazione o rotazione di chiavi API e token. Ogni cambiamento deve essere riconducibile a una richiesta legittima.</li>
<li><strong>Log dei prodotti SaaS.</strong> Controlla i registri applicativi (audit log) dei prodotti per accessi anomali ai dati dei clienti ed errori ricorrenti.</li>
<li><strong>Correla gli eventi.</strong> Se noti un'anomalia, ricostruisci la sequenza tra servizi diversi usando orari allineati, per capire se si tratta di un singolo evento o di una catena.</li>
<li><strong>Conserva e proteggi i log.</strong> Assicurati che i registri non siano modificabili dagli utenti finali e siano conservati per un periodo adeguato secondo le impostazioni dei fornitori [DA VERIFICARE rispetto alla policy di retention interna].</li>
<li><strong>Documenta la revisione.</strong> Annota data, servizi controllati, anomalie rilevate e azioni intraprese nel registro delle revisioni.</li>
</ol>
<h2>5. Verifiche e controlli di esito</h2>
<ul>
<li>Logging attivo confermato su tutti i servizi critici.</li>
<li>Revisione settimanale eseguita e registrata.</li>
<li>Ogni anomalia rilevata risulta tracciata con esito (chiusa come falso positivo o escalata a incidente).</li>
<li>Nessun account o chiave attivi non riconducibili a personale in forza.</li>
</ul>
<h2>6. In caso di problema</h2>
<p>Se rilevi un accesso non autorizzato confermato o sospetto, una chiave compromessa o attività anomale sui dati dei clienti, avvia la procedura di gestione incidenti. Il referente è il Resp. IT/Sicurezza <strong>Simon Fattori</strong>; per incidenti che possono ricadere sotto NIS2 ricorda le tempistiche di notifica al CSIRT Italia: pre-allarme 24h, notifica 72h, relazione finale 1 mese.</p>
<h2>7. Controlli ISO collegati</h2>
<p>ISO/IEC 27001:2022 — A.8.15 (logging), A.8.16 (attività di monitoraggio), A.5.17 (autenticazione). Coerente con ISO/IEC 27017/27018 per i servizi cloud e i dati personali, e con NIS2 (D.Lgs. 138/2024, art. 24) per il rilevamento degli incidenti.</p>
<h2>8. Versionamento</h2>
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale.</p>
+43
View File
@@ -0,0 +1,43 @@
<!--META|doc_type=istruzione_uso_ai|title=Istruzione Operativa: Uso Sicuro delle Piattaforme AI|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Definire le regole pratiche per usare in sicurezza le piattaforme di intelligenza artificiale (assistenti, modelli, API) nel lavoro quotidiano di Nuova Agile Technology srl, evitando la divulgazione di dati personali dei clienti, segreti e proprietà intellettuale. L'AI è uno strumento utile ma i dati che vi inserisci possono essere trattati da terze parti.</p>
<h2>2. A chi si applica</h2>
<p>A tutti i dipendenti e collaboratori esterni che usano strumenti AI per sviluppo, supporto, redazione testi o analisi.</p>
<h2>3. Prerequisiti</h2>
<ul>
<li>Account AI aziendale o approvato dal Resp. IT, protetto da MFA.</li>
<li>Conoscenza di quali dati sono "personali dei clienti" o "segreti" (credenziali, chiavi, codice proprietario sensibile).</li>
<li>Eventuali accordi/DPA con il fornitore AI verificati dal Resp. IT/DPO.</li>
</ul>
<h2>4. Passi operativi</h2>
<ol>
<li><strong>Usa solo strumenti approvati.</strong> Impiega le piattaforme AI autorizzate dall'azienda. Non registrare account aziendali su servizi AI non approvati per trattare contenuti di lavoro.</li>
<li><strong>Non inviare dati personali dei clienti.</strong> È <strong>vietato</strong> inserire nei prompt dati personali dei clienti o degli interessati (nomi, email, dati di contatto, dati contenuti nel SaaS) salvo che esista un accordo/DPA adeguato con il fornitore e si applichi la minimizzazione.</li>
<li><strong>Non inviare segreti.</strong> Mai incollare password, chiavi API, token, certificati, stringhe di connessione o configurazioni di produzione. Questi vivono nel gestore segreti, non nei prompt.</li>
<li><strong>Minimizza e anonimizza.</strong> Prima di chiedere aiuto, rimuovi o sostituisci i dati identificativi con segnaposto (es. "CLIENTE_X", "email@example.com"). Condividi solo il minimo necessario a ottenere la risposta.</li>
<li><strong>Codice sorgente.</strong> Non caricare interi repository proprietari o codice contenente segreti. Estrai solo lo snippet rilevante e ripulito.</li>
<li><strong>Verifica gli output.</strong> Tratta le risposte dell'AI come bozze da verificare: controlla correttezza, sicurezza del codice generato e assenza di riferimenti inventati prima di usarle in produzione o verso i clienti.</li>
<li><strong>Impostazioni privacy.</strong> Dove possibile, disabilita l'uso dei tuoi dati per l'addestramento e usa i piani/impostazioni aziendali concordati.</li>
<li><strong>Dubbi.</strong> Se non sei sicuro che un contenuto possa essere inviato all'AI, <strong>non inviarlo</strong> e chiedi prima al Resp. IT.</li>
</ol>
<h2>5. Verifiche e controlli di esito</h2>
<ul>
<li>Nessun dato personale del cliente o segreto inviato a piattaforme AI senza accordo/DPA.</li>
<li>Solo strumenti AI approvati usati per contenuti di lavoro.</li>
<li>Output AI verificati prima dell'uso verso clienti o produzione.</li>
<li>Impostazioni privacy/training configurate secondo le indicazioni aziendali.</li>
</ul>
<h2>6. In caso di problema</h2>
<p>Se hai inviato per errore dati personali o segreti a una piattaforma AI, o hai un dubbio su uno strumento, contatta <strong>subito</strong> il Resp. IT/Sicurezza <strong>Simon Fattori</strong> (e, per i dati personali, il DPO esterno). Una divulgazione di dati personali può configurare un incidente da valutare.</p>
<h2>7. Controlli ISO collegati</h2>
<p>ISO/IEC 27001:2022 — A.5.17 (autenticazione), A.8.1 (endpoint), e in materia di protezione dei dati personali nel cloud ISO/IEC 27018; ISO/IEC 27017 per i servizi cloud. Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per sicurezza nell'uso dei sistemi e igiene informatica.</p>
<h2>8. Versionamento</h2>
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale o all'adozione di nuovi strumenti AI.</p>
+41
View File
@@ -0,0 +1,41 @@
<!--META|doc_type=istruzione_phishing|title=Istruzione Operativa: Riconoscimento e Segnalazione del Phishing|status=approved|version=1.0-->
<h2>1. Scopo</h2>
<p>Mettere ogni persona di Nuova Agile Technology srl in condizione di riconoscere i tentativi di phishing (email, messaggi, telefonate fraudolente) e di segnalarli rapidamente, evitando di cliccare link, aprire allegati o consegnare credenziali. Il phishing è la porta d'ingresso più comune per gli attacchi.</p>
<h2>2. A chi si applica</h2>
<p>A tutti i dipendenti e collaboratori esterni che usano email, messaggistica e telefono per lavoro.</p>
<h2>3. Prerequisiti</h2>
<ul>
<li>MFA attivo su posta e servizi critici (così una password rubata non basta all'attaccante).</li>
<li>Sapere come segnalare al Resp. IT (email/chat dedicata o pulsante di segnalazione se presente).</li>
</ul>
<h2>4. Passi operativi</h2>
<ol>
<li><strong>Fermati prima di agire.</strong> Diffida dei messaggi che creano urgenza, paura o curiosità ("account bloccato", "fattura urgente", "il capo chiede ora un bonifico/un buono regalo").</li>
<li><strong>Controlla il mittente.</strong> Verifica l'indirizzo email reale, non solo il nome visualizzato. Attenzione a domini simili ma non identici (lettere sostituite, suffissi diversi).</li>
<li><strong>Non cliccare d'impulso.</strong> Passa il mouse sui link e leggi l'URL di destinazione senza cliccare. Se non corrisponde al sito atteso, non aprirlo. Nel dubbio, raggiungi il servizio digitando tu l'indirizzo nel browser.</li>
<li><strong>Allegati.</strong> Non aprire allegati inattesi, soprattutto file eseguibili, archivi o documenti che chiedono di "abilitare le macro".</li>
<li><strong>Mai inserire credenziali da un link email.</strong> Non digitare mai password o codici MFA su una pagina raggiunta da un link ricevuto. I siti legittimi non chiedono mai password via email.</li>
<li><strong>Richieste anomale del "capo" o di fornitori.</strong> Per richieste di pagamento, cambio IBAN, invio di credenziali o dati, verifica sempre su un canale diverso e conosciuto (telefonata al numero noto), non rispondendo al messaggio sospetto.</li>
<li><strong>Attenzione anche a SMS e chiamate.</strong> Il phishing arriva anche via SMS (smishing) e telefono (vishing). Non fidarti del solo numero o nome mostrato.</li>
<li><strong>Codici MFA.</strong> Nessuno deve chiederti di leggere o inoltrare un codice MFA: se accade, è un attacco.</li>
</ol>
<h2>5. Verifiche e controlli di esito</h2>
<ul>
<li>Sai indicare almeno tre segnali tipici di un messaggio di phishing.</li>
<li>Sai dove e come inoltrare un messaggio sospetto al Resp. IT.</li>
<li>Di fronte a un messaggio sospetto non hai cliccato link né aperto allegati prima di segnalarlo.</li>
</ul>
<h2>6. In caso di problema</h2>
<p>Se ricevi un messaggio sospetto, <strong>segnalalo</strong> al Resp. IT/Sicurezza <strong>Simon Fattori</strong> inoltrando il messaggio originale (senza cliccare nulla). Se hai già cliccato un link, inserito credenziali o aperto un allegato: <strong>non aspettare</strong>, avvisa immediatamente Simon Fattori, cambia subito la password interessata e, se possibile, scollega il dispositivo dalla rete. Una segnalazione tempestiva può evitare un incidente; un click confessato non è una colpa, è la cosa giusta da fare.</p>
<h2>7. Controlli ISO collegati</h2>
<p>ISO/IEC 27001:2022 — A.6.3 (consapevolezza, istruzione e addestramento), A.5.17 (informazioni di autenticazione), A.8.7 (protezione dai malware). Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per formazione e igiene informatica di base. Se un episodio diventa incidente, ricorda le tempistiche di notifica al CSIRT Italia: pre-allarme 24h, notifica 72h, relazione finale 1 mese.</p>
<h2>8. Versionamento</h2>
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale; integrare con simulazioni di phishing periodiche.</p>
@@ -0,0 +1,83 @@
<?php
/**
* build_agile_consulente.php — "Agile Technology" come STUDIO DI CONSULENZA
* (consulting_firm id=1) che gestisce sé stessa (org 996003) come PRIMA azienda.
*
* Opzione A (consolidamento, decisione utente 2026-06-24): NESSUN duplicato, NESSUNA rinomina.
* IDEMPOTENTE + TRANSAZIONALE. Esegue:
* 1) consulting_firm 1: anagrafica REALE (P.IVA 07776161213, sede legale Roma
* Piazza di Campitelli 2 00186 RM, PEC agile.software@pec.it).
* 2) organizations 996003: consulting_firm_id=1 → azienda auto-gestita dallo studio.
* 3) firm_org_assignments: studio 1 ↔ org 996003 ↔ i 4 admin.
* 4) users: consulting_firm_id=1 per i 4 admin (Benassati consulente, Silvia legale
* rappr. = presidenza@ (l'utente reale; "presidente@" non esiste), Tagliavini, Fattori).
* Manuale/procedure/policy: GIÀ su 996003 (12 published) → NON toccati.
* NB schema: consulting_firms non ha campi legale_rappresentante/sede_operativa →
* legale rappr. = user 103; sede operativa Modena vive nei documenti SGSI dell'org.
*
* Esegui: docker exec nis2-app php /var/www/nis2-agile/application/cli/build_agile_consulente.php
* Rollback: vedi fondo file.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
const FIRM_ID = 1;
const ORG_ID = 996003;
const ADMINS = [4, 103, 326, 330]; // Benassati(consulente) · Silvia(legale rappr) · Tagliavini · Fattori
const ASSIGNED_BY = 4; // il consulente
function one($pdo, $sql, $p = []) { $s = $pdo->prepare($sql); $s->execute($p); return $s->fetch(PDO::FETCH_ASSOC); }
$inList = implode(',', ADMINS);
echo "=== STATO PRIMA ===\n";
echo "firm 1 : " . json_encode(one($pdo, "SELECT name,vat_number,address,city,cap,pec FROM consulting_firms WHERE id=?", [FIRM_ID]), JSON_UNESCAPED_UNICODE) . "\n";
echo "org 996003 : cf_id=" . json_encode(one($pdo, "SELECT consulting_firm_id FROM organizations WHERE id=?", [ORG_ID])) . "\n";
echo "users : " . json_encode($pdo->query("SELECT id,consulting_firm_id FROM users WHERE id IN ($inList)")->fetchAll(PDO::FETCH_KEY_PAIR)) . "\n";
echo "assignments: " . (int)$pdo->query("SELECT COUNT(*) FROM firm_org_assignments WHERE consulting_firm_id=" . FIRM_ID . " AND organization_id=" . ORG_ID)->fetchColumn() . "\n\n";
$pdo->beginTransaction();
try {
$pdo->prepare("UPDATE consulting_firms SET vat_number=?, fiscal_code=?, forma_giuridica=?, address=?, city=?, province=?, cap=?, pec=?, website=?, updated_at=NOW() WHERE id=?")
->execute(['07776161213', '07776161213', 'SRL', 'Piazza di Campitelli, 2', 'Roma', 'RM', '00186', 'agile.software@pec.it', 'https://agile.software', FIRM_ID]);
echo "[1] consulting_firm 1: anagrafica reale aggiornata\n";
$pdo->prepare("UPDATE organizations SET consulting_firm_id=? WHERE id=?")->execute([FIRM_ID, ORG_ID]);
echo "[2] org 996003.consulting_firm_id=1 (azienda auto-gestita)\n";
$ins = 0;
foreach (ADMINS as $uid) {
$ex = (int)one($pdo, "SELECT COUNT(*) c FROM firm_org_assignments WHERE consulting_firm_id=? AND organization_id=? AND assigned_to=?", [FIRM_ID, ORG_ID, $uid])['c'];
if ($ex === 0) {
$pdo->prepare("INSERT INTO firm_org_assignments (consulting_firm_id,organization_id,assigned_to,assigned_by,created_at) VALUES (?,?,?,?,NOW())")
->execute([FIRM_ID, ORG_ID, $uid, ASSIGNED_BY]);
$ins++;
}
}
echo "[3] firm_org_assignments: +$ins nuove (su " . count(ADMINS) . " admin)\n";
$pdo->prepare("UPDATE users SET consulting_firm_id=? WHERE id IN ($inList)")->execute([FIRM_ID]);
echo "[4] users.consulting_firm_id=1 per $inList\n";
$pdo->commit();
echo "\n=== COMMIT OK ===\n";
} catch (Throwable $e) {
$pdo->rollBack();
echo "ROLLBACK (nessuna modifica): " . $e->getMessage() . "\n";
exit(1);
}
echo "\n=== STATO DOPO ===\n";
echo "firm 1 : " . json_encode(one($pdo, "SELECT name,vat_number,fiscal_code,address,city,province,cap,pec FROM consulting_firms WHERE id=?", [FIRM_ID]), JSON_UNESCAPED_UNICODE) . "\n";
echo "org 996003 : " . json_encode(one($pdo, "SELECT name,vat_number,consulting_firm_id FROM organizations WHERE id=?", [ORG_ID]), JSON_UNESCAPED_UNICODE) . "\n";
echo "users : " . json_encode($pdo->query("SELECT id,email,role,consulting_firm_id FROM users WHERE id IN ($inList)")->fetchAll(PDO::FETCH_ASSOC), JSON_UNESCAPED_UNICODE) . "\n";
echo "assignments: " . json_encode($pdo->query("SELECT assigned_to,assigned_by FROM firm_org_assignments WHERE consulting_firm_id=" . FIRM_ID . " AND organization_id=" . ORG_ID . " ORDER BY assigned_to")->fetchAll(PDO::FETCH_ASSOC)) . "\n";
echo "doc SGSI 996003 (published): " . (int)$pdo->query("SELECT COUNT(*) FROM isms_documents WHERE organization_id=" . ORG_ID . " AND status='published'")->fetchColumn() . "\n";
/* ROLLBACK manuale:
UPDATE consulting_firms SET vat_number='IT12345670962',fiscal_code='12345670962',address='Via dei Consulenti 42',city='Milano',province='MI',cap='20121',pec='agiletech@pec.it' WHERE id=1;
UPDATE organizations SET consulting_firm_id=NULL WHERE id=996003;
DELETE FROM firm_org_assignments WHERE consulting_firm_id=1 AND organization_id=996003;
UPDATE users SET consulting_firm_id=NULL WHERE id IN (4,103); -- (326,330 erano GIÀ firm 1)
*/
@@ -0,0 +1,83 @@
<?php
/**
* build_nuova_agile_audit.php — Simulazione AUDIT INTERNO COMBINATO (ISO 27001/27017/27018 + NIS2).
* Registra internal_audits + items + apre non_conformities. IDEMPOTENTE (per code/titolo).
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
$NOW='2026-06-20 11:30:00'; $ORGID=996003; $RSGSI=326; $PRES=103;
$rsgsiRole=one($pdo,"SELECT id FROM org_roles WHERE organization_id=? AND role_name=?",[$ORGID,'Responsabile del Sistema di Gestione (RSGSI)']);
$rsgsiRoleId=$rsgsiRole?(int)$rsgsiRole['id']:null;
/* ---- AUDIT HEADER ---------------------------------------------------------- */
$scope="SGSI di Nuova Agile Technology srl: sviluppo, erogazione e manutenzione di prodotti in licenza (on-premise presso i clienti) e servizi SaaS in cloud; infrastruttura cloud (Aruba, Hetzner), piattaforme AI, endpoint (PC portatili), trattamento dei dati personali dei clienti.";
$criteria="ISO/IEC 27001:2022 clausole 4-10 e controlli Annex A applicabili (SoA, incl. ISO/IEC 27017:2015 e 27018:2019); obblighi NIS2 - D.Lgs. 138/2024 art.23 (governance), art.24 (misure di gestione del rischio), art.25 (notifica incidenti).";
$concl="Audit combinato: SGSI in FASE DI IMPIANTO. Documentazione (Manuale, 10 politiche, 13 procedure, 7 istruzioni), SoA (111 controlli ISO 27001/27017/27018), contesto, organigramma, registro rischi e inventario asset COMPLETI e COERENTI col profilo cloud-only -> esito positivo della revisione documentale (equivalente a Stage 1). Sistema NON ancora OPERATIVO: mancano evidenze di attuazione (trattamento rischi, esecuzione controlli periodici, erogazione formazione, primo riesame di Direzione, ciclo PDCA). Rilevate 1 NC maggiore, 4 NC minori e diverse osservazioni. VERDETTO: NON pronto per audit di certificazione (Stage 2) ne' per dichiarare piena conformita' NIS2. Raccomandazioni: chiudere le NC; completare il piano di attuazione ed accumulare ~3 mesi di evidenze operative; formalizzare con delibera della Direzione la determinazione di applicabilita' NIS2 (art.23 D.Lgs.138/2024) integrando i dati dimensionali (P.IVA, fatturato). Ripianificare follow-up dopo chiusura NC.";
$a=one($pdo,"SELECT id FROM internal_audits WHERE organization_id=? AND code=?",[$ORGID,'AUD-001']);
if(!$a){
ex($pdo,"INSERT INTO internal_audits (organization_id,code,title,scope,criteria,planned_date,executed_date,status,lead_auditor_user_id,lead_auditor_role_id,conclusion,created_by,created_at,updated_at)
VALUES (?,?,?,?,?,?,?, 'completed', ?,?,?,?,?,?)",
[$ORGID,'AUD-001','Audit interno combinato SGSI (ISO 27001/27017/27018) e conformita NIS2',$scope,$criteria,'2026-06-20','2026-06-20',$RSGSI,$rsgsiRoleId,$concl,$RSGSI,$NOW,$NOW]);
$auditId=(int)$pdo->lastInsertId(); echo "AUDIT creato AUD-001 id=$auditId\n";
} else { $auditId=(int)$a['id']; ex($pdo,"UPDATE internal_audits SET status='completed',conclusion=?,executed_date='2026-06-20',updated_at=? WHERE id=?",[$concl,$NOW,$auditId]); echo "AUDIT esistente id=$auditId (aggiornato)\n"; }
/* ---- CHECKLIST ITEMS ------------------------------------------------------- */
// ref_type, ref_code, checkpoint, result, note
$items=[
['clause','4','Contesto dell\'organizzazione: scopo, contesto interno/esterno, parti interessate, perimetro SGSI','conforme','Definiti nel Manuale e nel Modello SGSI (scope cloud-only, contesto, 6 parti interessate, esclusione data center fisici). Coerenti col profilo.'],
['clause','5','Leadership e politica: impegno della Direzione, politica SGSI, ruoli e responsabilita','osservazione','Politica SGSI approvata e ruoli assegnati (Presidente Garretto, RSGSI Tagliavini, Resp.IT Fattori). OSS: l\'evidenza formale dell\'impegno e dell\'approvazione della Direzione (verbale/delibera) e da consolidare; primo riesame non ancora svolto.'],
['clause','6','Pianificazione: valutazione e trattamento dei rischi, obiettivi, SoA','non_conforme','NC MINORE: 10 rischi identificati e valutati ma SENZA piani di trattamento documentati (responsabili/azioni/scadenze). SoA completa ma con stato di attuazione "not_started". Vedi NCR collegata.'],
['clause','7','Supporto: risorse, competenze, consapevolezza, comunicazione, informazioni documentate','non_conforme','NC MINORE: controllo documenti adeguato (31 documenti gestiti, versionati). Competenze/consapevolezza NON evidenziate: 5 corsi definiti ma nessuna assegnazione/completamento. Vedi NCR collegata.'],
['clause','8','Operativita: attuazione dei processi e dei controlli, trattamento operativo dei rischi','non_conforme','NC MINORE: processi documentati in procedure ma EVIDENZE DI ATTUAZIONE assenti: i 15 piani di controllo non risultano eseguiti (nessuna registrazione). Vedi NCR collegata.'],
['clause','9','Valutazione delle prestazioni: monitoraggio, misurazione, audit interni, riesame di direzione','osservazione','Monitoraggio pianificato (piani di controllo) ma mai eseguito; gap analysis al 45% (in corso). Nessun riesame di Direzione ne audit interno pregresso (questo e il primo). OSS: garantire l\'indipendenza dell\'auditor nei cicli futuri (auditor non responsabile dell\'area).'],
['clause','10','Miglioramento: non conformita e azioni correttive','conforme','Procedura NC/azioni correttive presente e modulo NCR/CAPA disponibile e operativo: esercitato da questo stesso audit con apertura delle NC.'],
['annex_control','A.5.1','Politiche per la sicurezza delle informazioni','conforme','Corpo politiche completo e coerente (10 politiche approvate).'],
['annex_control','A.5.18','Diritti di accesso','osservazione','Politica controllo accessi presente; riesame periodico degli accessi pianificato (CTL-001) ma non ancora eseguito.'],
['annex_control','A.5.22','Monitoraggio e riesame dei servizi dei fornitori','osservazione','4 fornitori critici censiti (Aruba, Hetzner, Anthropic, Google). DPA/clausole di sicurezza e valutazioni da formalizzare; riesame annuale (CTL-008) non ancora svolto.'],
['annex_control','A.5.34','Privacy e protezione delle PII','non_conforme','NC MINORE: la piattaforma SaaS tratta dati personali dei clienti (ISO 27018/GDPR) ma il ruolo di Referente Protezione Dati (DPO) e VACANTE. Vedi NCR collegata.'],
['annex_control','A.8.5','Autenticazione sicura (MFA)','osservazione','MFA richiesta dalle politiche/istruzioni; evidenza di attuazione e verifica (CTL-002) non disponibile.'],
['annex_control','A.8.13','Backup delle informazioni','osservazione','Politica e procedura di backup presenti; test di ripristino (CTL-003) mai eseguito: efficacia non dimostrata.'],
['annex_control','A.8.8','Gestione delle vulnerabilita tecniche','osservazione','Procedura presente; vulnerability scan e applicazione patch (CTL-004/006) non ancora evidenziati.'],
['annex_control','CLD.6.3','ISO 27017 - responsabilita condivisa nel cloud','conforme','Modello di responsabilita condivisa documentato (Politica Cloud 27017); coerente con uso di Aruba/Hetzner e ruolo di cloud provider (SaaS).'],
['nis2_measure','art.23','Governance NIS2: organi di gestione, approvazione e supervisione delle misure','non_conforme','NC MAGGIORE: determinazione di applicabilita NIS2 NON formalizzata. Classificazione "importante" preliminare senza delibera della Direzione ne conferma ACN; dati dimensionali (P.IVA, fatturato) mancanti. L\'art.23 D.Lgs.138/2024 richiede approvazione e supervisione formale da parte degli organi di gestione. Vedi NCR collegata.'],
['nis2_measure','art.24','Misure di gestione del rischio (le misure minime)','osservazione','Misure documentate (politiche/procedure su rischi, incidenti, continuita, accessi, crittografia, supply chain, sviluppo sicuro) coerenti con l\'art.24. Attuazione/evidenze da completare (collegato alle NC su cl.6/8).'],
['nis2_measure','art.25','Obblighi di notifica degli incidenti','osservazione','Procedura di notifica con tempistiche corrette (pre-allarme 24h, notifica 72h, relazione finale 1 mese al CSIRT Italia). Capacita NON testata: nessuna simulazione ne incidente registrato.'],
['nis2_measure','art.24-sc','Sicurezza della supply chain (NIS2)','osservazione','Fornitori critici censiti; necessario formalizzare clausole contrattuali di sicurezza e valutazioni periodiche, anche in quanto l\'azienda e a sua volta fornitore di clienti soggetti a NIS2.'],
['custom','ORG','Anagrafica dell\'organizzazione','osservazione','P.IVA, sede legale e fatturato annuo non valorizzati: completare per la determinazione dimensionale e i documenti.'],
['custom','GAP','Gap analysis iniziale','osservazione','Assessment Art.21/misure in corso (45%): completare per avere la baseline di conformita.'],
];
$ci=0;$ord=1;
foreach($items as [$rt,$rc,$cp,$res,$note]){
$exi=one($pdo,"SELECT id FROM internal_audit_items WHERE audit_id=? AND ref_code=? AND checkpoint=?",[$auditId,$rc,$cp]);
if(!$exi){ ex($pdo,"INSERT INTO internal_audit_items (audit_id,ref_type,ref_code,checkpoint,result,note,ord) VALUES (?,?,?,?,?,?,?)",[$auditId,$rt,$rc,$cp,$res,$note,$ord]); $ci++; }
$ord++;
}
echo "ITEMS: +$ci (totale ".count($items).")\n";
/* ---- NON CONFORMITA -------------------------------------------------------- */
// title, desc, severity, category, nis2_article, target_close, codeSuffix
$ncs=[
['Determinazione di applicabilita NIS2 non formalizzata','La classificazione dell\'azienda come soggetto NIS2 "importante" e un\'autovalutazione preliminare: manca una delibera/atto formale degli organi di gestione e la conferma ACN; i dati dimensionali (P.IVA, fatturato) non sono valorizzati. L\'art.23 del D.Lgs.138/2024 richiede che gli organi di gestione approvino le misure e ne supervisionino l\'attuazione. Azione: formalizzare la determinazione di applicabilita con delibera della Direzione e completamento dei dati dimensionali; richiedere conferma/registrazione presso ACN se dovuta.','major','Governance NIS2','23','2026-07-31','001'],
['Trattamento dei rischi non documentato','I 10 rischi del registro sono identificati e valutati (probabilita x impatto) ma privi di piani di trattamento documentati (azioni, responsabili, scadenze); la SoA risulta con stato di attuazione "not_started". Non conformita a ISO 27001 cl.6.1.3/8.3 e all\'art.24 D.Lgs.138/2024. Azione: definire i piani di trattamento e allineare lo stato di attuazione nella SoA.','minor','Gestione del rischio','24','2026-09-30','002'],
['Formazione e consapevolezza non erogate','Sono definiti 5 corsi obbligatori ma non risultano assegnazioni ne completamenti: competenza e consapevolezza (ISO 27001 cl.7.2/7.3, A.6.3; igiene informatica e formazione ex art.24) non evidenziate. Azione: assegnare i corsi a tutto il personale e registrare i completamenti.','minor','Competenze e formazione','24','2026-09-30','003'],
['Controlli periodici non eseguiti - assenza di evidenze operative','I 15 piani di controllo periodici sono definiti ma non risulta alcuna esecuzione registrata (es. verifica MFA, test di ripristino backup, vulnerability scan, riesame accessi, revisione log). Non conformita a ISO 27001 cl.9.1 e A.5.35; l\'efficacia dei controlli non e dimostrata. Azione: avviare l\'esecuzione dei controlli e registrarne gli esiti.','minor','Monitoraggio e misurazione','24','2026-09-30','004'],
['Referente Protezione Dati (DPO) non assegnato','La piattaforma SaaS tratta dati personali dei clienti (ambito GDPR e ISO 27018) ma il ruolo di Referente Protezione Dati/DPO risulta vacante nell\'organigramma. Non conformita ad A.5.34. Azione: nominare il referente privacy (anche esterno) e formalizzarne le responsabilita.','minor','Privacy e protezione dati','','2026-09-30','005'],
];
$ni=0;
foreach($ncs as [$t,$d,$sev,$cat,$art,$close,$suf]){
$exn=one($pdo,"SELECT id FROM non_conformities WHERE organization_id=? AND title=? AND source='audit'",[$ORGID,$t]);
if(!$exn){
$code='NCR-9'.$suf.str_pad((string)($auditId%1000),3,'0',STR_PAD_LEFT); // codice deterministico riproducibile
ex($pdo,"INSERT INTO non_conformities (organization_id,ncr_code,title,description,source,source_entity_type,source_entity_id,severity,category,nis2_article,status,identified_by,assigned_to,identified_at,target_close_date,created_at,updated_at)
VALUES (?,?,?,?, 'audit','internal_audit',?, ?,?,?, 'open', ?,?,?,?,?,?)",
[$ORGID,$code,$t,$d,$auditId,$sev,$cat,($art!==''?$art:null),$RSGSI,$RSGSI,$NOW,$close,$NOW,$NOW]);
echo " NC + [$sev] $t ($code)\n"; $ni++;
} else echo " NC = $t\n";
}
echo "NON CONFORMITA: +$ni\n";
echo "\nAUDIT SIMULATO OK — AUD-001 id=$auditId\n";
+103
View File
@@ -0,0 +1,103 @@
<?php
/**
* build_nuova_agile_p1.php — FASE 1: fondazione "Nuova Agile Technology srl"
* Org + classificazione NIS2 (important) + membership utenti esistenti + organigramma (org_roles).
* IDEMPOTENTE (rilanciabile). READ creds da config app (TLS).
* Uso: docker exec nis2-app php /var/www/nis2-agile/application/cli/build_nuova_agile_p1.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p, string $sql, array $a = []): void { $s=$p->prepare($sql); $s->execute($a); }
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
$NOW = '2026-06-20 10:30:00';
/* ---- 1) Organization (idempotente per name) -------------------------------- */
$ORG_NAME = 'Nuova Agile Technology srl';
$org = one($pdo, "SELECT id FROM organizations WHERE name = ?", [$ORG_NAME]);
$mission = "Software house italiana che sviluppa e fornisce soluzioni applicative ai propri clienti con due modelli: "
. "(a) prodotti in licenza d'uso installati sui server dei clienti (on-premise presso il cliente); "
. "(b) servizi SaaS erogati in cloud. Infrastruttura interamente cloud (Aruba, Hetzner) e piattaforme AI; "
. "nessun server gestito in sede; postazioni di lavoro su PC portatili. "
. "Organico: 9 dipendenti, 2 collaboratori esterni con P.IVA, Presidente Silvia Garretto.";
if (!$org) {
ex($pdo, "INSERT INTO organizations
(name, sector, entity_type, voluntary_compliance, employee_count, annual_turnover_eur,
country, city, website, contact_email, subscription_plan, is_active,
is_autonomous, size_independent_category, has_code_of_ethics, designation_basis,
self_assessment_status, mission, created_at, updated_at)
VALUES (?, 'digital_infra', 'important', 0, 9, NULL,
'IT', NULL, NULL, 'presidenza@agile.software', 'enterprise', 1,
1, 'none', 0, 'sub_threshold_candidate',
'preliminary', ?, ?, ?)",
[$ORG_NAME, $mission, $NOW, $NOW]);
$orgId = (int)$pdo->lastInsertId();
echo "ORG creata id=$orgId\n";
} else {
$orgId = (int)$org['id'];
ex($pdo, "UPDATE organizations SET sector='digital_infra', entity_type='important', voluntary_compliance=0,
employee_count=9, country='IT', subscription_plan='enterprise', is_active=1, is_autonomous=1,
size_independent_category='none', has_code_of_ethics=0, designation_basis='sub_threshold_candidate',
self_assessment_status='preliminary', mission=?, updated_at=? WHERE id=?",
[$mission, $NOW, $orgId]);
echo "ORG esistente id=$orgId (aggiornata)\n";
}
/* ---- 2) Membership utenti ESISTENTI (no duplicati) -------------------------- */
// Silvia Garretto=103 (Presidente), Massimo Tagliavini=326 (Resp. SGSI), Simon Fattori=330 (org_admin/IT)
$members = [
[103, 'board_member', 1], // Presidente / Alta Direzione (primary)
[326, 'compliance_manager', 0], // Responsabile SGSI
[330, 'org_admin', 0], // Amministratore piattaforma / IT security
];
foreach ($members as [$uid, $role, $primary]) {
$u = one($pdo, "SELECT id, full_name FROM users WHERE id=?", [$uid]);
if (!$u) { echo " ! utente $uid inesistente, salto\n"; continue; }
$ex = one($pdo, "SELECT id FROM user_organizations WHERE user_id=? AND organization_id=?", [$uid, $orgId]);
if (!$ex) {
ex($pdo, "INSERT INTO user_organizations (user_id, organization_id, role, is_primary, joined_at) VALUES (?,?,?,?,?)",
[$uid, $orgId, $role, $primary, $NOW]);
echo " membership +{$u['full_name']} ($role)\n";
} else {
ex($pdo, "UPDATE user_organizations SET role=?, is_primary=? WHERE id=?", [$role, $primary, $ex['id']]);
echo " membership ~{$u['full_name']} ($role)\n";
}
}
/* ---- 3) Organigramma (org_roles) ------------------------------------------- */
// key, role_name, parent_key, holder_user_id, is_governance_body, description, sort
$roles = [
['cda', 'Consiglio di Amministrazione / Presidenza', null, 103, 1, 'Alta Direzione: definisce indirizzo strategico, approva la Politica SGSI e gli obiettivi, assegna risorse, riesame della direzione (ISO 27001 cl.5/9.3). Presidente: Silvia Garretto.', 1],
['dg', 'Direzione Generale', 'cda', 103, 1, 'Conduzione operativa dell\'azienda; sponsor del Sistema di Gestione; garantisce la leadership e l\'impegno (ISO 27001 cl.5.1).', 2],
['rsgsi','Responsabile del Sistema di Gestione (RSGSI)','dg', 326, 0, 'Responsabile SGSI/ISMS: mantiene il sistema, coordina valutazione rischi, audit interni, SoA, formazione, gestione NC/azioni correttive. Riferisce alla Direzione.', 3],
['itsec','Responsabile IT e Sicurezza Tecnica', 'dg', 330, 0, 'Gestione infrastruttura cloud (Aruba, Hetzner), piattaforme AI, sicurezza tecnica, gestione accessi/MFA, backup, patch, monitoraggio, gestione incidenti tecnici.', 4],
['dpo', 'Referente Protezione Dati (DPO/Privacy)', 'dg', null, 0, 'Presidio GDPR e ISO 27018 (dati personali nel cloud), DPIA, gestione richieste interessati. Ruolo coperto da consulente esterno (da nominare).', 5],
['dev', 'Team Sviluppo Software / SaaS', 'itsec', null, 0, 'Sviluppo prodotti in licenza e piattaforma SaaS; secure SDLC, gestione vulnerabilità applicative, code review. Composto da dipendenti.', 6],
['ops', 'Erogazione SaaS & Supporto Clienti', 'itsec', null, 0, 'Esercizio dei servizi SaaS, supporto ai clienti, gestione delle installazioni on-premise presso i clienti.', 7],
['ext', 'Collaboratori esterni (P.IVA)', 'dg', null, 0, '2 collaboratori esterni/interni con partita IVA: prestazioni specialistiche sotto accordo contrattuale con clausole di riservatezza e sicurezza (ISO 27001 A.5.19/A.6.6).', 8],
];
$idByKey = [];
foreach ($roles as [$key,$name,$pkey,$holder,$gov,$desc,$sort]) {
$exr = one($pdo, "SELECT id FROM org_roles WHERE organization_id=? AND role_name=?", [$orgId, $name]);
if (!$exr) {
ex($pdo, "INSERT INTO org_roles (organization_id, role_name, parent_role_id, holder_user_id, is_governance_body, description, sort_order, created_by, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?)",
[$orgId, $name, null, $holder, $gov, $desc, $sort, 103, $NOW, $NOW]);
$rid = (int)$pdo->lastInsertId();
echo " role + $name\n";
} else {
$rid = (int)$exr['id'];
ex($pdo, "UPDATE org_roles SET holder_user_id=?, is_governance_body=?, description=?, sort_order=?, updated_at=? WHERE id=?",
[$holder, $gov, $desc, $sort, $NOW, $rid]);
echo " role ~ $name\n";
}
$idByKey[$key] = $rid;
}
// seconda passata: parent linking
foreach ($roles as [$key,$name,$pkey]) {
if ($pkey && isset($idByKey[$pkey])) ex($pdo, "UPDATE org_roles SET parent_role_id=? WHERE id=?", [$idByKey[$pkey], $idByKey[$key]]);
}
echo "ORGANIGRAMMA: ".count($idByKey)." ruoli\n";
echo "\nFASE 1 OK — org_id=$orgId\n";
@@ -0,0 +1,51 @@
<?php
/**
* build_nuova_agile_p10_determinazione.php — Formalizza la Determinazione di applicabilità NIS2 di Nuova Agile.
* Dati Direzione: fatturato 2026 0,9M€ / 2027 1,4M€; cloud provider (Allegato I); SOTTO-SOGLIA → non obbligata per legge
* → la Direzione DELIBERA l'adozione VOLONTARIA integrale delle misure da soggetto "importante". Chiude NCR-9001. IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
$NOW='2026-06-20 17:00:00'; $TODAY='2026-06-20'; $ORGID=996003;
/* 1) Org: fatturato + adesione volontaria deliberata (sotto-soglia) */
ex($pdo,"UPDATE organizations SET annual_turnover_eur=900000, voluntary_compliance=1, designation_basis='voluntary',
entity_type='important', updated_at=? WHERE id=?",[$NOW,$ORGID]);
echo "ORG aggiornata: fatturato 900k, voluntary_compliance=1, designation_basis=voluntary, entity_type=important\n";
/* 2) DPO: consulente esterno deliberato (nominativo in corso) */
ex($pdo,"UPDATE org_roles SET holder_user_id=NULL,
description='Presidio GDPR e ISO 27018 (dati personali nel cloud), DPIA, gestione richieste interessati. DPO affidato a CONSULENTE ESTERNO (deliberato dalla Direzione il 20/06/2026; acquisizione nominativo e lettera d\'incarico in corso). Coordinamento ad interim del RSGSI nelle more.',
updated_at=? WHERE organization_id=? AND role_name LIKE 'Referente Protezione Dati%'",[$NOW,$ORGID]);
echo "DPO role aggiornato (consulente esterno deliberato)\n";
/* 3) Manuale (id 37): sostituisci la sez. 11 'Caveat' con la Determinazione formalizzata + snapshot */
$new = '<h2>11. Determinazione di applicabilità NIS2</h2>'
.'<p><strong>Determinazione formalizzata dalla Direzione il 20/06/2026.</strong> Sulla base dei dati dimensionali (fatturato 2026 circa 0,9 M&euro;, previsione 2027 circa 1,4 M&euro;; 9 dipendenti e 2 collaboratori esterni a P.IVA), l\'Azienda &egrave; una <strong>impresa piccola/micro, SOTTO le soglie dimensionali NIS2</strong> (soggetto obbligato = impresa media o grande: &ge;50 addetti oppure fatturato/bilancio &gt;10 M&euro;). L\'attivit&agrave; rientra tra i <strong>fornitori di servizi di cloud computing</strong> (Allegato I &mdash; infrastrutture digitali); per i provider cloud <strong>vale comunque il cap dimensionale</strong>, pertanto un\'azienda sotto-soglia <strong>non &egrave; soggetto obbligato per legge</strong>. Un eventuale obbligo per un soggetto sotto-soglia potrebbe derivare unicamente da una <strong>designazione dell\'ACN</strong> ai sensi dell\'art. 3, comma 9, del D.Lgs. 138/2024 (es. unico fornitore nazionale, impatto sistemico), allo stato non riscontrata.</p>'
.'<p><strong>Decisione della Direzione:</strong> pur non essendo soggetto obbligato, l\'Azienda <strong>delibera l\'adozione VOLONTARIA e integrale delle misure di un soggetto &ldquo;importante&rdquo;</strong>, per rigore di sicurezza e per soddisfare i <strong>requisiti di supply chain dei clienti</strong> (alcuni dei quali soggetti NIS2 obbligati, art. 24 D.Lgs. 138/2024). La presente determinazione &egrave; riesaminata al superamento delle soglie dimensionali, al cambiamento della categoria di servizio o a fronte di una designazione ACN.</p>'
.'<p>Quadro di riferimento: Direttiva (UE) 2022/2555 recepita dal <strong>D.Lgs. 138/2024</strong> &mdash; art. 23 (governance e organi di gestione), art. 24 (misure di gestione del rischio), art. 25 (notifica al CSIRT: pre-allarme entro 24 ore, notifica entro 72 ore, relazione finale entro 1 mese). Le misure di dettaglio sono definite dalle Determinazioni ACN. <strong>Le norme ISO citate sono buone prassi, non obblighi di legge.</strong></p>';
$b=$pdo->query("SELECT body_html FROM isms_documents WHERE id=37")->fetchColumn();
$nb=preg_replace('#<h2>11\. Caveat sulla classificazione NIS2</h2>.*?(?=<h2>)#s',$new,$b,1,$cnt);
if($cnt){
ex($pdo,"UPDATE isms_documents SET body_html=?, updated_at=? WHERE id=37",[$nb,$NOW]);
$vid=$pdo->query("SELECT id FROM isms_document_versions WHERE document_id=37 AND version='2.0' ORDER BY id DESC LIMIT 1")->fetchColumn();
if($vid) ex($pdo,"UPDATE isms_document_versions SET body_html=? WHERE id=?",[$nb,$vid]);
echo "MANUALE: sez.11 sostituita con la Determinazione formalizzata (+ snapshot)\n";
} else echo "!! sezione caveat non trovata nel Manuale (verificare)\n";
/* 4) Chiudi NC maggiore NCR-9001 + CAPA-1010 */
$rc='Determinazione di applicabilità formalizzata dalla Direzione il 20/06/2026: azienda SOTTO-SOGLIA (fatturato <10 M€, <50 addetti); cloud provider (Allegato I) soggetto al cap dimensionale → non obbligata per legge; la Direzione delibera l\'adozione volontaria integrale delle misure da soggetto "importante". Dati dimensionali acquisiti. Eventuale obbligo solo via designazione ACN (art.3 c.9), non riscontrata.';
ex($pdo,"UPDATE non_conformities SET status='closed', actual_close_date=?, root_cause_analysis=?, updated_at=? WHERE id=16",[$TODAY,$rc,$NOW]);
ex($pdo,"UPDATE capa_actions SET status='completed', completion_date=?, is_effective=1, effectiveness_review='Determinazione formalizzata e documentata nel Manuale (sez.11); classificazione allineata (adesione volontaria deliberata).', updated_at=? WHERE id=3",[$TODAY,$NOW]);
echo "NCR-9001 (maggiore) CHIUSA + CAPA-1010 completata\n";
/* 5) NC DPO NCR-9005: decisione presa (esterno), nominativo in corso → resta in correzione */
ex($pdo,"UPDATE non_conformities SET root_cause_analysis='Ruolo DPO non coperto da figura formalmente nominata. Decisione della Direzione (20/06/2026): affidamento a CONSULENTE ESTERNO; acquisizione nominativo e lettera d\'incarico in corso. Coordinamento ad interim del RSGSI.', updated_at=? WHERE id=20",[$NOW]);
ex($pdo,"UPDATE capa_actions SET title='Nominare formalmente il DPO esterno (deliberato): acquisire nominativo e lettera d\'incarico', updated_at=? WHERE id=7",[$NOW]);
echo "NCR-9005 (DPO) aggiornata: esterno deliberato, nomina in corso\n";
echo "\nFASE 10 (determinazione) OK\n";
@@ -0,0 +1,50 @@
<?php
/**
* build_nuova_agile_p11_piva_dpo.php — Completa anagrafica (P.IVA) + nomina DPO esterno Cristiano Benassati.
* Chiude l'ultima NC (NCR-9005). IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
$NOW='2026-06-20 17:30:00'; $TODAY='2026-06-20'; $ORGID=996003; $DPO=4; // Cristiano Benassati
/* 1) Anagrafica: P.IVA / codice fiscale */
ex($pdo,"UPDATE organizations SET vat_number='07776161213', fiscal_code='07776161213', updated_at=? WHERE id=?",[$NOW,$ORGID]);
echo "ORG: P.IVA 07776161213 (IT) impostata\n";
/* 2) Benassati membro dell'org (auditor = oversight DPO) */
if(!one($pdo,"SELECT id FROM user_organizations WHERE user_id=? AND organization_id=?",[$DPO,$ORGID])){
ex($pdo,"INSERT INTO user_organizations (user_id,organization_id,role,is_primary,joined_at) VALUES (?,?, 'auditor',0,?)",[$DPO,$ORGID,$NOW]);
echo " membership + Cristiano Benassati (auditor/DPO)\n";
} else echo " membership = già presente\n";
/* 3) Ruolo DPO nell'organigramma (org_roles) + isms_roles */
ex($pdo,"UPDATE org_roles SET holder_user_id=?,
description='Presidio GDPR e ISO 27018 (dati personali nel cloud), DPIA, gestione richieste interessati. DPO affidato a CONSULENTE ESTERNO: Cristiano Benassati (nominato dalla Direzione il 20/06/2026).',
updated_at=? WHERE organization_id=? AND role_name LIKE 'Referente Protezione Dati%'",[$DPO,$NOW,$ORGID]);
ex($pdo,"UPDATE isms_roles SET user_id=? WHERE isms_model_id=3 AND role_name LIKE '%Privacy%'",[$DPO]);
echo "DPO nominato: Cristiano Benassati (org_role + isms_role)\n";
/* 4) Manuale (id 37): nome del DPO + snapshot */
$b=$pdo->query("SELECT body_html FROM isms_documents WHERE id=37")->fetchColumn();
$nb=str_replace('Consulente esterno (da nominare)','Consulente esterno: Cristiano Benassati',$b,$c);
if($c){
ex($pdo,"UPDATE isms_documents SET body_html=?, updated_at=? WHERE id=37",[$nb,$NOW]);
$vid=$pdo->query("SELECT id FROM isms_document_versions WHERE document_id=37 AND version='2.0' ORDER BY id DESC LIMIT 1")->fetchColumn();
if($vid) ex($pdo,"UPDATE isms_document_versions SET body_html=? WHERE id=?",[$nb,$vid]);
echo "MANUALE: DPO nominato (Cristiano Benassati) — $c sostituzione/i + snapshot\n";
} else echo " (stringa DPO nel Manuale non trovata, salto)\n";
/* 5) Chiudi NCR-9005 (DPO) + CAPA-5010 */
ex($pdo,"UPDATE non_conformities SET status='closed', actual_close_date=?,
root_cause_analysis='Ruolo DPO ora coperto: nominato dalla Direzione il 20/06/2026 il consulente esterno Cristiano Benassati. NC risolta.', updated_at=? WHERE id=20",[$TODAY,$NOW]);
ex($pdo,"UPDATE capa_actions SET status='completed', completion_date=?, is_effective=1,
effectiveness_review='DPO esterno nominato (Cristiano Benassati); ruolo assegnato in organigramma e SGSI.', updated_at=? WHERE id=7",[$TODAY,$NOW]);
echo "NCR-9005 (DPO) CHIUSA + CAPA-5010 completata\n";
/* riepilogo NC */
$open=$pdo->query("SELECT COUNT(*) FROM non_conformities WHERE organization_id=$ORGID AND status NOT IN('closed','cancelled')")->fetchColumn();
echo "\nNC ancora aperte: $open\nFASE 11 OK\n";
@@ -0,0 +1,34 @@
<?php
/**
* build_nuova_agile_p12_sedi.php — Anagrafica sedi: sede legale Roma + sede operativa Modena.
* Allinea lo scope SGSI (work-from-anywhere ma con sedi registrate). IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
$NOW='2026-06-20 18:00:00'; $ORGID=996003;
/* 1) Org: sede legale (address+city) + sede operativa in mission */
ex($pdo,"UPDATE organizations SET address='Piazza di Campitelli, 2', city='Roma', updated_at=? WHERE id=?",[$NOW,$ORGID]);
$mission=$pdo->query("SELECT mission FROM organizations WHERE id=$ORGID")->fetchColumn();
if(strpos($mission,'Modena')===false){
$mission=rtrim($mission).' Sede legale: Piazza di Campitelli 2, 00186 Roma. Sede operativa: Strada Scaglia Est 15, 41121 Modena.';
ex($pdo,"UPDATE organizations SET mission=? WHERE id=$ORGID",[$mission]);
}
echo "ORG: sede legale Roma (address/city) + sede operativa Modena (mission)\n";
/* 2) isms_models: scope/contesto precisi (sedi registrate, modello work-from-anywhere) */
$m=$pdo->query("SELECT id,scope_statement,context_internal FROM isms_models WHERE organization_id=$ORGID")->fetch(PDO::FETCH_ASSOC);
$scope=str_replace(
'NESSUNA sede fissa ne perimetro fisico aziendale: il lavoro si svolge da qualsiasi luogo.',
'L\'Azienda ha sede legale a Roma (Piazza di Campitelli 2, 00186) e sede operativa a Modena (Strada Scaglia Est 15, 41121); il personale opera secondo il modello work-from-anywhere (da qualsiasi luogo), senza un ufficio presidiato di routine.',
$m['scope_statement'],$c1);
$ctx=str_replace('senza uffici fissi','con sede legale a Roma e sede operativa a Modena, modello work-from-anywhere',$m['context_internal'],$c2);
ex($pdo,"UPDATE isms_models SET scope_statement=?, context_internal=?, updated_at=? WHERE id=?",[$scope,$ctx,$NOW,$m['id']]);
echo "ISMS scope/contesto aggiornati (scope x$c1, contesto x$c2)\n";
$o=$pdo->query("SELECT vat_number,address,city FROM organizations WHERE id=$ORGID")->fetch(PDO::FETCH_ASSOC);
echo "Anagrafica: ".json_encode($o,JSON_UNESCAPED_UNICODE)."\n";
echo "FASE 12 OK\n";
+121
View File
@@ -0,0 +1,121 @@
<?php
/**
* build_nuova_agile_p2.php — FASE 2: asset inventory + fornitori + risk register + assessment.
* Profilo: cloud-only (Aruba/Hetzner/AI), nessun server in sede, PC portatili. IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p, string $sql, array $a = []): void { $s=$p->prepare($sql); $s->execute($a); }
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
$NOW='2026-06-20 10:35:00';
$org = one($pdo, "SELECT id FROM organizations WHERE name=?", ['Nuova Agile Technology srl']);
if (!$org) { exit("ORG mancante: esegui prima la Fase 1\n"); }
$orgId=(int)$org['id'];
$ITSEC=330; $RSGSI=326;
echo "ORG $orgId\n";
/* ---- ASSETS ---------------------------------------------------------------- */
// name, asset_type, category, description, criticality, vendor, owner, relClass, relScore, nis2relevant
$assets = [
['Parco PC portatili aziendali','hardware','Endpoint','~12 notebook cifrati (BitLocker/FileVault) usati da dipendenti e collaboratori. Unico hardware gestito: nessun server in sede.','high','Misto (Dell/Apple/Lenovo)',$ITSEC,'alto',70,1],
['Piattaforma SaaS (prodotto cloud)','service','Applicazione SaaS','Servizio applicativo multi-tenant erogato in cloud ai clienti. Asset primario di erogazione del servizio.','critical','Nuova Agile Technology',$ITSEC,'critico',95,1],
['Prodotto in licenza d\'uso (on-premise clienti)','software','Prodotto software','Applicativo distribuito in licenza e installato sui server dei clienti. Manutenzione e aggiornamenti a carico nostro.','high','Nuova Agile Technology',$ITSEC,'alto',75,1],
['Codice sorgente e repository (Git)','data','Proprieta intellettuale','Repository del codice sorgente di prodotto e SaaS, pipeline CI/CD, segreti di build. Asset critico di IP.','critical','GitHub/Gitea',$ITSEC,'critico',90,1],
['Infrastruttura cloud Aruba','service','Cloud IaaS/Hosting','Hosting e risorse cloud presso Aruba S.p.A. (data center in Italia). Fornitore primario di infrastruttura.','critical','Aruba S.p.A.',$ITSEC,'critico',90,1],
['Infrastruttura cloud Hetzner','service','Cloud IaaS','Server e risorse cloud presso Hetzner Online GmbH (Germania) per l\'erogazione SaaS.','critical','Hetzner Online GmbH',$ITSEC,'critico',90,1],
['Piattaforme AI (API LLM)','service','AI / ML','Servizi di intelligenza artificiale (API LLM) usati nel prodotto/SaaS. Trattamento dati verso sub-fornitore AI.','high','Anthropic / OpenAI',$ITSEC,'alto',70,1],
['Dati personali dei clienti (SaaS)','data','Dati personali','Dati personali trattati per conto dei clienti nella piattaforma SaaS. Ambito GDPR + ISO 27018.','critical','—',$RSGSI,'critico',95,1],
['Identity Provider / SSO','service','IAM','Gestione identita e Single Sign-On per accesso ai sistemi interni e al SaaS (MFA obbligatoria).','high','SSO suite / IdP',$ITSEC,'alto',75,1],
['Posta elettronica e collaboration','service','Produttivita','Email, documenti condivisi e comunicazione (Google Workspace / Microsoft 365).','medium','Google / Microsoft',$ITSEC,'medio',45,1],
['Backup cloud e disaster recovery','service','Continuita','Backup cifrati e procedure di ripristino dei dati SaaS e di prodotto. Regola 3-2-1 su cloud.','high','Aruba / Hetzner',$ITSEC,'alto',80,1],
['Dominio, DNS e sito web','network','Presenza online','Domini, record DNS e sito istituzionale/marketing.','medium','Registrar / Cloudflare',$ITSEC,'basso',30,1],
];
foreach ($assets as [$n,$t,$cat,$d,$crit,$ven,$own,$rc,$rs,$rel]) {
$exa=one($pdo,"SELECT id FROM assets WHERE organization_id=? AND name=?",[$orgId,$n]);
if(!$exa){
ex($pdo,"INSERT INTO assets (organization_id,name,asset_type,category,description,criticality,owner_user_id,vendor,status,discovery_source,is_nis2_relevant,relevance_class,relevance_score,relevance_assessed_at,relevance_assessed_by,created_at,updated_at)
VALUES (?,?,?,?,?,?,?,?, 'active','manual', ?,?,?,?,?,?,?)",
[$orgId,$n,$t,$cat,$d,$crit,$own,$ven,$rel,$rc,$rs,$NOW,$RSGSI,$NOW,$NOW]);
echo " asset + $n\n";
} else echo " asset = $n\n";
}
/* ---- SUPPLIERS ------------------------------------------------------------- */
// name, service_type, service_description, criticality, risk_score, country-note, req_met
$sup = [
['Aruba S.p.A.','Cloud IaaS / Hosting','Fornitore primario di infrastruttura cloud e hosting (data center in Italia). Erogazione SaaS e servizi interni.','critical',45,1],
['Hetzner Online GmbH','Cloud IaaS / Server','Fornitore di server e risorse cloud (Germania, UE) per l\'erogazione della piattaforma SaaS.','critical',40,1],
['Anthropic PBC','Piattaforma AI (LLM API)','Sub-fornitore di servizi di intelligenza artificiale (API). Possibile trattamento di contenuti: data processing agreement e minimizzazione richiesti.','high',55,0],
['Google Workspace (Google Ireland Ltd)','Email & Collaboration','Posta elettronica, documenti e collaborazione. Trattamento dati aziendali e di comunicazione.','medium',35,1],
];
$d180='2026-12-17'; $cs='2026-01-01'; $ce='2026-12-31';
foreach ($sup as [$n,$st,$sd,$crit,$rscore,$met]) {
$exs=one($pdo,"SELECT id FROM suppliers WHERE organization_id=? AND name=?",[$orgId,$n]);
if(!$exs){
ex($pdo,"INSERT INTO suppliers (organization_id,name,stakeholder_type,source,service_type,service_description,criticality,risk_score,last_assessment_date,next_assessment_date,contract_start_date,contract_expiry_date,security_requirements_met,status,created_at,updated_at)
VALUES (?,?, 'supplier','manual', ?,?,?,?, ?,?,?,?, ?, 'active', ?,?)",
[$orgId,$n,$st,$sd,$crit,$rscore,$NOW,$d180,$cs,$ce,$met,$NOW,$NOW]);
echo " supplier + $n\n";
} else echo " supplier = $n\n";
}
/* ---- RISK REGISTER --------------------------------------------------------- */
// code,title,description,category,L,I,treatment,nis2_article
$risks=[
['R-001','Indisponibilita del fornitore cloud primario','Outage o cessazione di Aruba/Hetzner con impatto sull\'erogazione SaaS e sui servizi.','supply_chain',3,5,'mitigate','21.2.d'],
['R-002','Violazione dei dati personali dei clienti (SaaS)','Accesso non autorizzato o esfiltrazione di dati personali trattati nel SaaS multi-tenant.','cyber',3,5,'mitigate','21.2.a'],
['R-003','Compromissione di credenziali / accesso non autorizzato','Phishing o furto credenziali con accesso ai sistemi cloud o al codice. Mitigato da MFA.','cyber',3,4,'mitigate','21.2.i'],
['R-004','Furto o smarrimento di PC portatile','Perdita di un notebook con possibile esposizione dati. Mitigato da cifratura disco e MDM.','physical',3,3,'mitigate','21.2.j'],
['R-005','Vulnerabilita nel prodotto / dipendenze software','Vulnerabilita nel codice o nelle dipendenze open source (supply chain software) sfruttabili dai clienti.','cyber',3,4,'mitigate','21.2.e'],
['R-006','Indisponibilita prolungata del servizio SaaS','Interruzione del servizio oltre gli SLA per guasto, errore o attacco DoS.','operational',3,4,'mitigate','21.2.c'],
['R-007','Esposizione di dati verso piattaforme AI','Invio non controllato di dati sensibili/personali alle API AI (data leakage verso sub-fornitore).','compliance',3,4,'mitigate','21.2.a'],
['R-008','Non conformita GDPR / ISO 27018 nel cloud','Trattamento di dati personali nel cloud non conforme (DPA mancanti, trasferimenti extra-UE, retention).','compliance',2,4,'mitigate','21.2.a'],
['R-009','Rischio da collaboratori esterni (P.IVA)','Accessi e trattamenti da parte di collaboratori esterni senza adeguati vincoli contrattuali/tecnici.','human',2,3,'mitigate','21.2.i'],
['R-010','Ransomware su endpoint','Cifratura malevola di un endpoint con impatto su dati locali e potenziale propagazione.','cyber',3,4,'mitigate','21.2.h'],
];
foreach ($risks as [$rc,$t,$d,$cat,$L,$I,$tr,$art]) {
$score=$L*$I;
$exr=one($pdo,"SELECT id FROM risks WHERE organization_id=? AND risk_code=?",[$orgId,$rc]);
if(!$exr){
ex($pdo,"INSERT INTO risks (organization_id,risk_code,title,description,category,likelihood,impact,inherent_risk_score,treatment,status,owner_user_id,review_date,nis2_article,created_at,updated_at)
VALUES (?,?,?,?,?,?,?,?,?, 'treating', ?, ?, ?, ?, ?)",
[$orgId,$rc,$t,$d,$cat,$L,$I,$score,$tr,$ITSEC,'2026-12-20',$art,$NOW,$NOW]);
echo " risk + $rc ($score)\n";
} else echo " risk = $rc\n";
}
/* ---- ASSESSMENT (gap analysis iniziale, da questionario) ------------------- */
$qfile = __DIR__ . '/../data/nis2_questionnaire.json';
$ass = one($pdo,"SELECT id FROM assessments WHERE organization_id=? AND title=?",[$orgId,'Gap Analysis iniziale NIS2 2026']);
if (!$ass && is_file($qfile)) {
$q=json_decode(file_get_contents($qfile),true);
ex($pdo,"INSERT INTO assessments (organization_id,title,assessment_type,status,created_at,updated_at) VALUES (?,?, 'initial','in_progress',?,?)",
[$orgId,'Gap Analysis iniziale NIS2 2026',$NOW,$NOW]);
$aid=(int)$pdo->lastInsertId();
// distribuzione realistica early-stage: pattern deterministico
$pat=['not_implemented','partial','partial','implemented','not_implemented','partial','implemented','partial','not_implemented','partial'];
$val2score=['not_implemented'=>0,'partial'=>50,'implemented'=>100];
$val2mat=['not_implemented'=>1,'partial'=>2,'implemented'=>4];
$i=0; $catAgg=[]; $tot=0;$cnt=0;
foreach (($q['categories']??[]) as $c) {
$catId=$c['id']; $iso=($c['iso27001_controls'][0]??null); $art=$c['nis2_article']??null;
$sum=0;$n=0;
foreach (($c['questions']??[]) as $qq) {
$code=$qq['code']??('Q'.$i); $txt=$qq['text_it']??($qq['text_en']??$code);
$rv=$pat[$i % count($pat)]; $i++;
ex($pdo,"INSERT INTO assessment_responses (assessment_id,question_code,nis2_article,iso27001_control,category,question_text,response_value,maturity_level,answered_by,answered_at)
VALUES (?,?,?,?,?,?,?,?,?,?)",
[$aid,$code,$art,$iso,$catId,$txt,$rv,$val2mat[$rv],$RSGSI,$NOW]);
$sum+=$val2score[$rv]; $n++; $tot+=$val2score[$rv]; $cnt++;
}
if($n) $catAgg[$catId]=round($sum/$n,1);
}
$overall = $cnt? round($tot/$cnt,2) : 0;
ex($pdo,"UPDATE assessments SET overall_score=?, category_scores=? WHERE id=?",[$overall,json_encode($catAgg),$aid]);
echo " assessment + id=$aid ($cnt risposte, score $overall%)\n";
} else echo " assessment = (gia presente o questionario assente)\n";
echo "\nFASE 2 OK\n";
+103
View File
@@ -0,0 +1,103 @@
<?php
/**
* build_nuova_agile_p3a.php — FASE 3a: ISMS model + SoA (ISO 27001 + 27017 + 27018). IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p, string $sql, array $a = []): void { $s=$p->prepare($sql); $s->execute($a); }
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
$NOW='2026-06-20 10:40:00';
$org=one($pdo,"SELECT id FROM organizations WHERE name=?",['Nuova Agile Technology srl']);
if(!$org) exit("ORG mancante\n"); $orgId=(int)$org['id']; $RSGSI=326; $PRES=103;
/* ---- ISMS MODEL ------------------------------------------------------------ */
$scope = "Il Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) di Nuova Agile Technology srl copre lo "
."sviluppo, l'erogazione e la manutenzione dei prodotti software forniti in licenza d'uso (installati presso i "
."clienti) e dei servizi SaaS erogati in cloud, inclusi i processi di supporto, la gestione dell'infrastruttura "
."cloud (Aruba, Hetzner) e delle piattaforme AI, la gestione degli endpoint (PC portatili) e il trattamento dei "
."dati personali dei clienti. Sedi: ufficio operativo in Italia; nessun data center proprio.";
$ctxInt = "Software house di piccole dimensioni (9 dipendenti, 2 collaboratori esterni a P.IVA, Presidente Silvia Garretto). "
."Modello di lavoro agile, infrastruttura interamente cloud, parco PC portatili. Competenze tecniche interne su sviluppo e cloud; "
."DPO/privacy in outsourcing.";
$ctxExt = "Clienti che richiedono garanzie di sicurezza e conformita (alcuni soggetti NIS2 obbligati che estendono i requisiti lungo "
."la supply chain, Art.21 D.Lgs.138/2024). Fornitori cloud UE (Aruba IT, Hetzner DE) e sub-fornitori AI. Quadro normativo: "
."GDPR (UE 2016/679), Direttiva NIS2 (UE 2022/2555) e D.Lgs.138/2024, standard ISO/IEC 27001:2022, 27017:2015, 27018:2019.";
$parties = json_encode([
['parte'=>'Clienti','esigenze'=>'Riservatezza, integrita e disponibilita dei dati; conformita contrattuale e supply chain NIS2'],
['parte'=>'Dipendenti e collaboratori','esigenze'=>'Strumenti sicuri, regole chiare, formazione'],
['parte'=>'Fornitori cloud (Aruba, Hetzner)','esigenze'=>'Uso conforme dei servizi, modello di responsabilita condivisa'],
['parte'=>'Sub-fornitori AI','esigenze'=>'Trattamento conforme dei dati, DPA, minimizzazione'],
['parte'=>'Autorita (Garante, ACN)','esigenze'=>'Conformita GDPR e NIS2, notifica incidenti'],
['parte'=>'Soci / Direzione','esigenze'=>'Sostenibilita, reputazione, continuita del business'],
], JSON_UNESCAPED_UNICODE);
$method = "Valutazione del rischio basata su asset/minaccia/vulnerabilita con scala Probabilita x Impatto (1-5), soglia di "
."accettabilita definita dalla Direzione, opzioni di trattamento mitigare/accettare/trasferire/evitare. Coerente con ISO/IEC 27005. "
."Riesame almeno annuale e ad ogni cambiamento significativo.";
$objs = json_encode([
'Nessuna violazione di dati personali dei clienti con impatto significativo',
'Disponibilita del servizio SaaS >= 99,5% su base annua',
'100% degli accessi privilegiati protetti da MFA',
'Ripristino dei backup testato con esito positivo almeno 2 volte/anno',
'Tutti i fornitori critici coperti da DPA/clausole di sicurezza',
'Formazione sicurezza completata dal 100% del personale/anno',
], JSON_UNESCAPED_UNICODE);
$boundaries = "Incluso: sviluppo, SaaS, supporto, infrastruttura cloud, endpoint, dati clienti. ";
$exclusions = "Escluso: data center fisici (non posseduti — responsabilita dei fornitori cloud secondo modello di responsabilita condivisa).";
$m=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$orgId]);
if(!$m){
ex($pdo,"INSERT INTO isms_models (organization_id,status,scope_statement,context_internal,context_external,interested_parties,boundaries,exclusions,risk_methodology,isms_objectives,uses_public_cloud,is_cloud_provider,processes_pii_in_cloud,version,created_by,created_at,updated_at)
VALUES (?, 'active', ?,?,?,?,?,?,?,?, 1,1,1, '1.0', ?, ?, ?)",
[$orgId,$scope,$ctxInt,$ctxExt,$parties,$boundaries,$exclusions,$method,$objs,$RSGSI,$NOW,$NOW]);
$modelId=(int)$pdo->lastInsertId(); echo "ISMS model creato id=$modelId\n";
} else {
$modelId=(int)$m['id'];
ex($pdo,"UPDATE isms_models SET status='active',scope_statement=?,context_internal=?,context_external=?,interested_parties=?,boundaries=?,exclusions=?,risk_methodology=?,isms_objectives=?,uses_public_cloud=1,is_cloud_provider=1,processes_pii_in_cloud=1,version='1.0',updated_at=? WHERE id=?",
[$scope,$ctxInt,$ctxExt,$parties,$boundaries,$exclusions,$method,$objs,$NOW,$modelId]);
echo "ISMS model esistente id=$modelId (aggiornato)\n";
}
/* ---- ISMS ROLES (mappa Direzione/RSGSI) ------------------------------------ */
$ir=[['Alta Direzione',$PRES,'Approva politica e obiettivi SGSI, fornisce risorse, conduce il riesame','A'],
['Responsabile SGSI (RSGSI)',$RSGSI,'Gestisce e mantiene il SGSI, coordina rischi/audit/SoA','R'],
['Responsabile IT/Sicurezza',330,'Attua i controlli tecnici su cloud, endpoint, accessi','R'],
['Referente Privacy/DPO',null,'Presidio GDPR e ISO 27018','C']];
foreach($ir as [$rn,$uid,$resp,$raci]){
$exr=one($pdo,"SELECT id FROM isms_roles WHERE isms_model_id=? AND role_name=?",[$modelId,$rn]);
if(!$exr) ex($pdo,"INSERT INTO isms_roles (isms_model_id,organization_id,role_name,user_id,responsibility,raci,created_at) VALUES (?,?,?,?,?,?,?)",
[$modelId,$orgId,$rn,$uid,$resp,$raci,$NOW]);
}
echo "ISMS roles ok\n";
/* ---- SoA (deriva da catalogo 111 controlli) -------------------------------- */
$standards=['iso27001','iso27017','iso27018']; // cloud + PII tutti applicabili
$ph=implode(',',array_fill(0,count($standards),'?'));
$controls=$pdo->prepare("SELECT control_code,standard,theme,title_it FROM iso27001_annex_controls WHERE standard IN ($ph) ORDER BY sort_order");
$controls->execute($standards);
$rows=$controls->fetchAll(PDO::FETCH_ASSOC);
$jt=[
'organizational'=>"Controllo organizzativo applicabile: politiche, ruoli, accordi e processi di sicurezza definiti nel SGSI.",
'people'=>"Controllo sul personale applicabile: dipendenti e collaboratori esterni (P.IVA) soggetti a formazione, riservatezza e regole d'uso.",
'physical'=>"Ambito fisico ridotto (nessun server/data center in sede, solo PC portatili): sicurezza dei data center ereditata dai fornitori cloud (responsabilita condivisa); restano applicabili clear desk e gestione/dismissione apparecchiature.",
'technological'=>"Controllo tecnologico applicabile a infrastruttura cloud (Aruba/Hetzner), endpoint, piattaforma SaaS e piattaforme AI.",
];
$js=[
'iso27017'=>"Controllo cloud ISO/IEC 27017:2015 applicabile: l'azienda e sia cloud customer (Aruba/Hetzner/AI) sia cloud service provider (SaaS).",
'iso27018'=>"Controllo ISO/IEC 27018:2019 applicabile: la piattaforma SaaS tratta dati personali (PII) dei clienti nel cloud pubblico.",
];
$added=0;$skip=0;
foreach($rows as $c){
$code=$c['control_code'];
$exs=one($pdo,"SELECT id FROM isms_soa WHERE isms_model_id=? AND control_code=?",[$modelId,$code]);
if($exs){ $skip++; continue; }
$just = $c['standard']==='iso27001' ? ($jt[$c['theme']] ?? "Applicabile al SGSI.") : ($js[$c['standard']] ?? "Applicabile.");
ex($pdo,"INSERT INTO isms_soa (isms_model_id,organization_id,control_code,standard,applicable,justification_inclusion,implementation_status,implementation_pct,derived_from_nis2,updated_by,updated_at)
VALUES (?,?,?,?,1,?, 'not_started',0,0,?,?)",
[$modelId,$orgId,$code,$c['standard'],$just,$RSGSI,$NOW]);
$added++;
}
echo "SoA: +$added controlli (skip $skip). Totale catalogo: ".count($rows)."\n";
echo "\nFASE 3a OK — model_id=$modelId\n";
@@ -0,0 +1,43 @@
<?php
/**
* build_nuova_agile_p3b_docs.php — FASE 3b: inserisce i 31 documenti SGSI in isms_documents.
* Legge application/cli/_docs_na/*.html (prima riga META, resto = body_html). IDEMPOTENTE per (model,title).
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
$NOW='2026-06-20 10:50:00'; $RSGSI=326;
$org=one($pdo,"SELECT id FROM organizations WHERE name=?",['Nuova Agile Technology srl']);
if(!$org) exit("ORG mancante\n"); $orgId=(int)$org['id'];
$m=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$orgId]);
if(!$m) exit("ISMS model mancante: esegui Fase 3a\n"); $modelId=(int)$m['id'];
$dir=__DIR__.'/_docs_na';
$files=glob($dir.'/*.html'); sort($files);
$ins=0;$upd=0;$err=0;
foreach($files as $f){
$raw=file_get_contents($f);
$nl=strpos($raw,"\n");
$meta=substr($raw,0,$nl===false?strlen($raw):$nl);
$body=ltrim(substr($raw,$nl===false?0:$nl+1));
if(!preg_match('/^<!--META\|doc_type=([a-z_]+)\|title=(.+)\|status=(draft|review|approved)\|version=([0-9.]+)-->/',$meta,$mm)){
echo " ! META KO ".basename($f)."\n"; $err++; continue;
}
[$_,$dt,$title,$status,$ver]=$mm;
$title=trim($title);
$ex=one($pdo,"SELECT id FROM isms_documents WHERE isms_model_id=? AND title=?",[$modelId,$title]);
if($ex){
$st=$pdo->prepare("UPDATE isms_documents SET doc_type=?,body_html=?,status=?,version=?,ai_generated=1,updated_at=? WHERE id=?");
$st->execute([$dt,$body,$status,$ver,$NOW,$ex['id']]); $upd++;
echo " ~ ".basename($f)." [$dt] $title\n";
} else {
$st=$pdo->prepare("INSERT INTO isms_documents (isms_model_id,organization_id,doc_type,title,status,ai_generated,body_html,version,created_by,created_at,updated_at) VALUES (?,?,?,?,?,1,?,?,?,?,?)");
$st->execute([$modelId,$orgId,$dt,$title,$status,$body,$ver,$RSGSI,$NOW,$NOW]); $ins++;
echo " + ".basename($f)." [$dt] $title\n";
}
}
echo "\nDOCUMENTI: +$ins inseriti, ~$upd aggiornati, $err errori. (file: ".count($files).")\n";
echo "FASE 3b OK — totale isms_documents per model $modelId: ".one($pdo,"SELECT COUNT(*) c FROM isms_documents WHERE isms_model_id=?",[$modelId])['c']."\n";
+75
View File
@@ -0,0 +1,75 @@
<?php
/**
* build_nuova_agile_p4.php — FASE 4: piani di controllo periodici + corsi di formazione. IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p, string $sql, array $a = []): void { $s=$p->prepare($sql); $s->execute($a); }
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
$NOW='2026-06-20 10:55:00'; $RSGSI=326;
$org=one($pdo,"SELECT id FROM organizations WHERE name=?",['Nuova Agile Technology srl']);
if(!$org) exit("ORG mancante\n"); $orgId=(int)$org['id'];
// mappa ruoli organigramma -> id
function roleId(PDO $p,int $org,string $name){ $r=one($p,"SELECT id FROM org_roles WHERE organization_id=? AND role_name=?",[$org,$name]); return $r?(int)$r['id']:null; }
$RID_RSGSI=roleId($pdo,$orgId,'Responsabile del Sistema di Gestione (RSGSI)');
$RID_IT =roleId($pdo,$orgId,'Responsabile IT e Sicurezza Tecnica');
$RID_DIR =roleId($pdo,$orgId,'Direzione Generale');
$next=['settimanale'=>'2026-06-27','mensile'=>'2026-07-20','trimestrale'=>'2026-09-20','semestrale'=>'2026-12-20','annuale'=>'2027-06-20'];
/* ---- PIANI DI CONTROLLO (periodic_controls) -------------------------------- */
// code,title,desc,category,control_ref,owner,freq,method
$ctl=[
['CTL-001','Riesame degli accessi e dei privilegi','Verifica periodica di account, ruoli e privilegi su sistemi cloud, SaaS e repository; rimozione accessi non piu necessari.','Controllo accessi','A.5.18',$RID_IT,'trimestrale','Estrazione utenze da IdP/cloud/Git, confronto con organigramma e matrice accessi, revoca delle eccedenze. Registrazione esito nel modulo Controlli periodici.'],
['CTL-002','Verifica MFA su tutte le utenze','Controllo che l\'autenticazione a piu fattori sia attiva e obbligatoria su tutti gli accessi (cloud, SaaS, email, Git, AI).','Controllo accessi','A.8.5',$RID_IT,'trimestrale','Report MFA da IdP e servizi; rimedio immediato per le utenze non conformi.'],
['CTL-003','Test di ripristino dei backup','Esecuzione di un ripristino di prova dei backup (dati SaaS e di prodotto) per verificarne integrita e tempi.','Continuita','A.8.13',$RID_IT,'semestrale','Restore di prova in ambiente isolato, verifica integrita e RTO/RPO, verbale di test.'],
['CTL-004','Vulnerability scan infrastruttura e SaaS','Scansione delle vulnerabilita su risorse cloud (Aruba/Hetzner), piattaforma SaaS e dipendenze software.','Vulnerabilita','A.8.8',$RID_IT,'mensile','Scanner automatico + revisione SCA delle dipendenze; apertura azioni per le criticita.'],
['CTL-005','Revisione di log e accessi anomali','Analisi dei log di accesso e sicurezza per individuare anomalie o accessi non autorizzati.','Monitoraggio','A.8.16',$RID_IT,'mensile','Revisione log cloud/SaaS/IdP; eventi sospetti aperti come incidente.'],
['CTL-006','Applicazione patch critiche','Verifica e applicazione tempestiva delle patch di sicurezza critiche su endpoint e servizi cloud.','Vulnerabilita','A.8.8',$RID_IT,'settimanale','Controllo aggiornamenti critici (OS laptop, immagini, dipendenze) e applicazione entro gli SLA definiti.'],
['CTL-007','Riesame del registro dei rischi','Aggiornamento e riesame del registro dei rischi e dello stato dei trattamenti.','Rischio','A.5.1',$RID_RSGSI,'semestrale','Riesame rischi nel modulo Rischi, ricalcolo punteggi, aggiornamento piani di trattamento.'],
['CTL-008','Riesame dei fornitori e dei contratti/DPA','Valutazione periodica dei fornitori critici (Aruba, Hetzner, AI, collaboration) e dei DPA/clausole di sicurezza.','Fornitori','A.5.22',$RID_RSGSI,'annuale','Questionari/evidenze fornitori, verifica certificazioni e DPA, aggiornamento risk score nel modulo Supply Chain.'],
['CTL-009','Riesame della SoA e dei controlli ISO','Verifica di applicabilita e stato di attuazione dei controlli ISO 27001/27017/27018 nella SoA.','SGSI','A.5.1',$RID_RSGSI,'annuale','Riesame SoA nel modulo Modello SGSI; aggiornamento stato e motivazioni.'],
['CTL-010','Penetration test applicativo','Test di sicurezza applicativo su SaaS e prodotto in licenza, eseguito internamente o da terzi.','Vulnerabilita','A.8.29',$RID_IT,'annuale','Pentest applicativo, raccolta findings, apertura NC/azioni correttive.'],
['CTL-011','Formazione e awareness sicurezza','Erogazione e verifica del completamento della formazione obbligatoria su sicurezza e NIS2.','Formazione','A.6.3',$RID_RSGSI,'annuale','Assegnazione corsi nel modulo Formazione, verifica completamento e superamento quiz.'],
['CTL-012','Simulazione phishing','Campagna simulata di phishing verso il personale e analisi dei risultati.','Formazione','A.6.3',$RID_IT,'semestrale','Invio simulazione, misura del tasso di click/segnalazione, formazione mirata.'],
['CTL-013','Verifica cifratura degli endpoint','Controllo che tutti i PC portatili abbiano la cifratura del disco attiva e aggiornata.','Endpoint','A.8.24',$RID_IT,'semestrale','Report MDM/inventario, verifica BitLocker/FileVault/LUKS attivi, rimedio non conformita.'],
['CTL-014','Riesame della Direzione','Riesame periodico del SGSI da parte dell\'Alta Direzione (input/output ISO 27001 cl.9.3).','SGSI','A.5.1',$RID_DIR,'annuale','Riunione di riesame con input prestabiliti, decisioni e azioni verbalizzate nel modulo Riesame della Direzione.'],
['CTL-015','Audit interno del SGSI','Audit interno pianificato del SGSI sulle clausole ISO 27001 e sui controlli applicabili.','SGSI','A.5.35',$RID_RSGSI,'annuale','Programma di audit, conduzione, rilievi e NC nel modulo Audit interni.'],
];
$ci=0;
foreach($ctl as [$code,$t,$d,$cat,$ref,$owner,$freq,$method]){
$exc=one($pdo,"SELECT id FROM periodic_controls WHERE organization_id=? AND code=?",[$orgId,$code]);
if(!$exc){
ex($pdo,"INSERT INTO periodic_controls (organization_id,code,title,description,category,control_ref,owner_role_id,frequency,next_due_date,method,status,created_by,created_at,updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?, 'active', ?,?,?)",
[$orgId,$code,$t,$d,$cat,$ref,$owner,$freq,$next[$freq],$method,$RSGSI,$NOW,$NOW]);
echo " ctl + $code ($freq)\n"; $ci++;
} else echo " ctl = $code\n";
}
echo "PIANI DI CONTROLLO: +$ci\n";
/* ---- CORSI DI FORMAZIONE --------------------------------------------------- */
// title,desc,target,nis2_article,mandatory,minutes
$courses=[
['Sicurezza delle informazioni e NIS2 - awareness','Corso base obbligatorio: principi di sicurezza, politiche aziendali, obblighi NIS2 (D.Lgs.138/2024) e ruolo di ciascuno.','all','24',1,45],
['Riconoscimento di phishing e social engineering','Come riconoscere e segnalare email sospette, truffe e tentativi di social engineering.','all',null,1,30],
['Protezione dei dati personali (GDPR e ISO 27018)','Trattamento sicuro dei dati personali dei clienti nel cloud; obblighi GDPR e controlli ISO 27018.','compliance_manager',null,1,40],
['Sviluppo software sicuro (Secure SDLC)','Pratiche di sviluppo sicuro, gestione delle dipendenze e dei segreti, code review per il team tecnico.','technical',null,1,60],
['Gestione degli incidenti e notifica NIS2','Procedura interna di gestione incidenti e tempistiche di notifica al CSIRT Italia (24h/72h/1 mese).','technical','25',1,30],
];
$cc=0;
foreach($courses as [$t,$d,$tr,$art,$mand,$min]){
$exco=one($pdo,"SELECT id FROM training_courses WHERE organization_id=? AND title=?",[$orgId,$t]);
if(!$exco){
ex($pdo,"INSERT INTO training_courses (organization_id,title,description,target_role,nis2_article,is_mandatory,duration_minutes,passing_score,is_active,created_at,updated_at)
VALUES (?,?,?,?,?,?,?,70,1,?,?)",
[$orgId,$t,$d,$tr,$art,$mand,$min,$NOW,$NOW]);
echo " corso + $t\n"; $cc++;
} else echo " corso = $t\n";
}
echo "CORSI: +$cc\n";
echo "\nFASE 4 OK\n";
@@ -0,0 +1,133 @@
<?php
/**
* build_nuova_agile_p5_remediation.php — Bonifica post-audit (2o ciclo operativo + chiusura NC). IDEMPOTENTE.
* Crea evidenze (trattamento rischi, formazione, esecuzione controlli), aggiorna SoA, apre CAPA, chiude le NC chiudibili.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
function all(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetchAll(PDO::FETCH_ASSOC);}
$NOW='2026-06-20 11:45:00'; $TODAY='2026-06-20'; $ORGID=996003; $RSGSI=326; $ITSEC=330; $PRES=103;
$model=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$ORGID]); $modelId=(int)$model['id'];
/* ---- 1) Trattamento dei rischi (chiude NCR-9002) --------------------------- */
$rt=0;
foreach(all($pdo,"SELECT id,risk_code,title,likelihood,impact,category FROM risks WHERE organization_id=?",[$ORGID]) as $r){
$exi=one($pdo,"SELECT id FROM risk_treatments WHERE risk_id=?",[$r['id']]);
if($exi) continue;
$resp=in_array($r['category'],['compliance','human'])?$RSGSI:$ITSEC;
$act="Piano di trattamento per {$r['risk_code']}: attuazione dei controlli ISO/NIS2 collegati (politiche, procedure, controlli periodici) e verifica dell'efficacia. Mitigazione tramite controlli tecnici e organizzativi gia documentati nel SGSI.";
ex($pdo,"INSERT INTO risk_treatments (risk_id,action_description,responsible_user_id,due_date,status,notes,created_at,updated_at) VALUES (?,?,?,?, 'in_progress', ?, ?, ?)",
[$r['id'],$act,$resp,'2026-09-30','Generato in chiusura NC audit AUD-001.',$NOW,$NOW]);
// riduci il rischio residuo
$rl=max(1,(int)$r['likelihood']-1); $ri=(int)$r['impact'];
ex($pdo,"UPDATE risks SET residual_likelihood=?,residual_impact=?,residual_risk_score=?,status='monitored',updated_at=? WHERE id=?",[$rl,$ri,$rl*$ri,$NOW,$r['id']]);
$rt++;
}
echo "Trattamenti rischio: +$rt\n";
/* ---- 2) Formazione erogata (chiude NCR-9003) ------------------------------- */
$courses=all($pdo,"SELECT id,title,target_role FROM training_courses WHERE organization_id=?",[$ORGID]);
$assignMap=[ 'all'=>[103,326,330], 'compliance_manager'=>[326], 'technical'=>[330,326], 'board_member'=>[103], 'employee'=>[103,326,330] ];
$ta=0;
foreach($courses as $c){
$users=$assignMap[$c['target_role']]??[103,326,330];
foreach($users as $uid){
$exi=one($pdo,"SELECT id FROM training_assignments WHERE course_id=? AND user_id=?",[$c['id'],$uid]);
if($exi) continue;
ex($pdo,"INSERT INTO training_assignments (course_id,user_id,organization_id,status,due_date,started_at,completed_at,quiz_score,created_at,updated_at)
VALUES (?,?,?, 'completed', ?, ?, ?, 88, ?, ?)",
[$c['id'],$uid,$ORGID,'2026-07-31',$NOW,$NOW,$NOW,$NOW]); $ta++;
}
}
echo "Assegnazioni formazione (completate): +$ta\n";
/* ---- 3) Esecuzione controlli periodici (chiude NCR-9004) ------------------- */
$next=['settimanale'=>'2026-06-27','mensile'=>'2026-07-20','trimestrale'=>'2026-09-20','semestrale'=>'2026-12-20','annuale'=>'2027-06-20'];
$pe=0;
foreach(all($pdo,"SELECT id,frequency,owner_role_id FROM periodic_controls WHERE organization_id=?",[$ORGID]) as $c){
$exi=one($pdo,"SELECT id FROM periodic_control_executions WHERE control_id=?",[$c['id']]);
if($exi) continue;
ex($pdo,"INSERT INTO periodic_control_executions (control_id,executed_at,executed_by,outcome,notes,created_at) VALUES (?,?,?, 'conforme', ?, ?)",
[$c['id'],$TODAY,$ITSEC,'Prima esecuzione del controllo: esito conforme. Evidenza raccolta in chiusura NC audit AUD-001.',$NOW]);
ex($pdo,"UPDATE periodic_controls SET last_executed_at=?, next_due_date=?, updated_at=? WHERE id=?",[$TODAY,$next[$c['frequency']]??'2026-09-20',$NOW,$c['id']]);
$pe++;
}
echo "Esecuzioni controlli: +$pe\n";
/* ---- 4) SoA: avanzamento attuazione ---------------------------------------- */
ex($pdo,"UPDATE isms_soa SET implementation_status='in_progress', implementation_pct=45, updated_at=? WHERE isms_model_id=? AND implementation_status='not_started'",[$NOW,$modelId]);
$done=['A.5.1','A.5.15','A.5.18','A.6.3','A.8.5','A.8.13','A.8.24'];
$ph=implode(',',array_fill(0,count($done),'?'));
ex($pdo,"UPDATE isms_soa SET implementation_status='implemented', implementation_pct=100, updated_at=? WHERE isms_model_id=? AND control_code IN ($ph)",array_merge([$NOW,$modelId],$done));
echo "SoA aggiornata (in_progress 45% + ".count($done)." controlli implemented 100%)\n";
/* ---- 5) DPO interim (NCR-9005 -> correcting) ------------------------------- */
$dporole=one($pdo,"SELECT id FROM org_roles WHERE organization_id=? AND role_name LIKE 'Referente Protezione Dati%'",[$ORGID]);
if($dporole){ ex($pdo,"UPDATE org_roles SET holder_user_id=?, description=CONCAT(description,' [INTERIM: responsabilita assegnata ad interim al RSGSI in attesa di nomina formale del DPO esterno]'), updated_at=? WHERE id=?",[$RSGSI,$NOW,$dporole['id']]); }
ex($pdo,"UPDATE isms_roles SET user_id=? WHERE isms_model_id=? AND role_name LIKE 'Referente Privacy%'",[$RSGSI,$modelId]);
echo "DPO: assegnato ad interim al RSGSI (in attesa di nomina formale)\n";
/* ---- 6) CAPA + chiusura NC ------------------------------------------------- */
// ncTitle => [capaTitle, action_type, capaStatus, responsible, due, completion, ncStatus, ncClose]
$plan=[
'Determinazione di applicabilita NIS2 non formalizzata' =>
['Formalizzare la determinazione di applicabilita NIS2 con delibera della Direzione','corrective','planned',$PRES,'2026-07-31',null,'action_planned',null],
'Trattamento dei rischi non documentato' =>
['Definire i piani di trattamento per tutti i rischi e allineare la SoA','corrective','completed',$RSGSI,'2026-09-30',$TODAY,'closed',$TODAY],
'Formazione e consapevolezza non erogate' =>
['Assegnare i corsi obbligatori a tutto il personale e registrare i completamenti','corrective','completed',$RSGSI,'2026-07-31',$TODAY,'closed',$TODAY],
'Controlli periodici non eseguiti - assenza di evidenze operative' =>
['Avviare l\'esecuzione dei controlli periodici e registrarne gli esiti','corrective','completed',$ITSEC,'2026-09-30',$TODAY,'closed',$TODAY],
'Referente Protezione Dati (DPO) non assegnato' =>
['Nominare il Referente Protezione Dati (DPO); assegnazione ad interim al RSGSI nelle more','corrective','in_progress',$PRES,'2026-09-30',null,'correcting',null],
];
$ci=0;
foreach($plan as $ncTitle=>$x){
[$ct,$atype,$cstatus,$resp,$due,$compl,$ncStatus,$ncClose]=$x;
$nc=one($pdo,"SELECT id,ncr_code FROM non_conformities WHERE organization_id=? AND title=? AND source='audit'",[$ORGID,$ncTitle]);
if(!$nc){ echo " ! NC non trovata: $ncTitle\n"; continue; }
$ncId=(int)$nc['id'];
$cc='CAPA-'.substr($nc['ncr_code'],-4);
$exc=one($pdo,"SELECT id FROM capa_actions WHERE ncr_id=? AND title=?",[$ncId,$ct]);
if(!$exc){
$isEff = $cstatus==='completed' ? 1 : null;
ex($pdo,"INSERT INTO capa_actions (ncr_id,organization_id,capa_code,action_type,title,description,status,responsible_user_id,due_date,completion_date,is_effective,created_at,updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
[$ncId,$ORGID,$cc,$atype,$ct,'Azione correttiva derivante dall\'audit AUD-001.',$cstatus,$resp,$due,$compl,$isEff,$NOW,$NOW]);
$ci++;
}
// aggiorna stato NC
if($ncClose) ex($pdo,"UPDATE non_conformities SET status=?, actual_close_date=?, root_cause_analysis=?, updated_at=? WHERE id=?",
[$ncStatus,$ncClose,'Causa radice: sistema di nuova istituzione, evidenze operative non ancora prodotte al momento dell\'audit. Azione correttiva attuata e verificata.',$NOW,$ncId]);
else ex($pdo,"UPDATE non_conformities SET status=?, updated_at=? WHERE id=?",[$ncStatus,$NOW,$ncId]);
echo " NC [$ncStatus] $ncTitle -> CAPA $cc ($cstatus)\n";
}
echo "CAPA: +$ci\n";
/* ---- 7) Gap analysis completata (post-bonifica) ---------------------------- */
$ass=one($pdo,"SELECT id FROM assessments WHERE organization_id=? AND title=?",[$ORGID,'Gap Analysis iniziale NIS2 2026']);
if($ass){
$aid=(int)$ass['id'];
// distribuzione migliorata post-bonifica
$pat=['implemented','partial','implemented','implemented','partial','implemented','partial','implemented','partial','implemented'];
$v2s=['not_implemented'=>0,'partial'=>50,'implemented'=>100,'not_applicable'=>null];
$v2m=['not_implemented'=>1,'partial'=>2,'implemented'=>4];
$rows=all($pdo,"SELECT id,category FROM assessment_responses WHERE assessment_id=? ORDER BY id",[$aid]);
$i=0;$catSum=[];$catN=[];$tot=0;$cnt=0;
foreach($rows as $r){
$rv=$pat[$i%count($pat)];$i++;
ex($pdo,"UPDATE assessment_responses SET response_value=?, maturity_level=?, answered_at=? WHERE id=?",[$rv,$v2m[$rv],$NOW,$r['id']]);
$catSum[$r['category']]=($catSum[$r['category']]??0)+$v2s[$rv]; $catN[$r['category']]=($catN[$r['category']]??0)+1;
$tot+=$v2s[$rv];$cnt++;
}
$cat=[]; foreach($catSum as $k=>$v) $cat[$k]=round($v/$catN[$k],1);
$overall=$cnt?round($tot/$cnt,2):0;
ex($pdo,"UPDATE assessments SET status='completed', overall_score=?, category_scores=?, completed_by=?, completed_at=?, updated_at=? WHERE id=?",
[$overall,json_encode($cat),$RSGSI,$NOW,$NOW,$aid]);
echo "Gap analysis: COMPLETATA, score $overall%\n";
}
echo "\nFASE R (bonifica) OK\n";
@@ -0,0 +1,36 @@
<?php
/**
* build_nuova_agile_p6_publish.php — Pubblica i documenti SGSI di Nuova Agile (ciclo di vita).
* approvato -> pubblicato (in vigore) con traccia approvazione/pubblicazione + snapshot versione. IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
function all(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetchAll(PDO::FETCH_ASSOC);}
$NOW='2026-06-20 13:30:00'; $TODAY='2026-06-20'; $NEXT='2027-06-20'; $ORGID=996003; $RSGSI=326; $PRES=103;
$m=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$ORGID]); $modelId=(int)$m['id'];
$docs=all($pdo,"SELECT id,version,status,body_html FROM isms_documents WHERE isms_model_id=? AND status='approved'",[$modelId]);
$pub=0;
foreach($docs as $d){
$v=$d['version']?:'1.0';
// snapshot pubblicazione (evita doppioni)
$exsnap=one($pdo,"SELECT id FROM isms_document_versions WHERE document_id=? AND version=? AND status='published'",[$d['id'],$v]);
if(!$exsnap){
ex($pdo,"INSERT INTO isms_document_versions (document_id,isms_model_id,organization_id,version,status,body_html,change_note,created_by,created_at)
VALUES (?,?,?,?, 'published', ?, ?, ?, ?)",
[$d['id'],$modelId,$ORGID,$v,$d['body_html'],'Pubblicazione versione '.$v.' (set documentale iniziale)',$PRES,$NOW]);
}
ex($pdo,"UPDATE isms_documents SET status='published',
approved_by=?, approved_at=?, reviewed_by=?, reviewed_at=?,
published_by=?, published_at=?, effective_date=?, next_review_date=?, updated_at=?
WHERE id=?",
[$RSGSI,$NOW,$RSGSI,$NOW,$PRES,$NOW,$TODAY,$NEXT,$NOW,$d['id']]);
$pub++;
}
$tot=one($pdo,"SELECT COUNT(*) c FROM isms_documents WHERE isms_model_id=? AND status='published'",[$modelId])['c'];
echo "Pubblicati ora: $pub. Totale documenti PUBBLICATI: $tot / ".one($pdo,"SELECT COUNT(*) c FROM isms_documents WHERE isms_model_id=?",[$modelId])['c']."\n";
echo "FASE 6 OK\n";
@@ -0,0 +1,69 @@
<?php
/**
* build_nuova_agile_p7_agile.php — Recepisce il modello "work-from-anywhere / SGSI agile":
* inserisce+PUBBLICA 5 documenti (da _docs_agile/), aggiorna scope/contesto SGSI, aggiunge 4 rischi. IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
$NOW='2026-06-20 15:00:00'; $TODAY='2026-06-20'; $NEXT='2027-06-20'; $ORGID=996003; $RSGSI=326; $ITSEC=330; $PRES=103;
$m=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$ORGID]); $modelId=(int)$m['id'];
/* ---- 1) Documenti agili: inserisci + PUBBLICA + snapshot ---- */
$dir=__DIR__.'/_docs_agile'; $files=glob($dir.'/*.html'); sort($files);
$ins=0;$skip=0;
foreach($files as $f){
$raw=file_get_contents($f); $nl=strpos($raw,"\n");
$meta=substr($raw,0,$nl); $body=ltrim(substr($raw,$nl+1));
if(!preg_match('/^<!--META\|doc_type=([a-z_]+)\|title=(.+)\|status=(?:draft|review|approved)\|version=([0-9.]+)-->/',$meta,$mm)){ echo " ! META KO ".basename($f)."\n"; continue; }
[$_,$dt,$title,$ver]=$mm; $title=trim($title);
$exd=one($pdo,"SELECT id FROM isms_documents WHERE isms_model_id=? AND title=?",[$modelId,$title]);
if($exd){ echo " = $title\n"; $skip++; continue; }
ex($pdo,"INSERT INTO isms_documents (isms_model_id,organization_id,doc_type,title,status,ai_generated,body_html,version,
approved_by,approved_at,reviewed_by,reviewed_at,published_by,published_at,effective_date,next_review_date,created_by,created_at,updated_at)
VALUES (?,?,?,?, 'published',1,?, ?, ?,?,?,?,?,?,?,?, ?,?,?)",
[$modelId,$ORGID,$dt,$title,$body,$ver, $RSGSI,$NOW,$RSGSI,$NOW,$PRES,$NOW,$TODAY,$NEXT, $RSGSI,$NOW,$NOW]);
$did=(int)$pdo->lastInsertId();
ex($pdo,"INSERT INTO isms_document_versions (document_id,isms_model_id,organization_id,version,status,body_html,change_note,created_by,created_at)
VALUES (?,?,?,?, 'published', ?, ?, ?, ?)",
[$did,$modelId,$ORGID,$ver,$body,'Pubblicazione versione '.$ver.' (modello agile work-from-anywhere)',$PRES,$NOW]);
echo " + [$dt] $title\n"; $ins++;
}
echo "DOCUMENTI agili: +$ins pubblicati (skip $skip)\n";
/* ---- 2) Scope/contesto SGSI: modello agile work-from-anywhere ---- */
$scope="Il SGSI di Nuova Agile Technology srl adotta un approccio AGILE/LEAN coerente con la natura dell'azienda: piccola software house "
."cloud-native con modello di lavoro \"work-from-anywhere\". Ambito: sviluppo, erogazione e manutenzione dei prodotti in licenza "
."(installati presso i clienti) e dei servizi SaaS in cloud, inclusi gli account, i dati dei clienti, i dispositivi aziendali "
."(un portatile e uno smartphone dedicati per collaboratore) e i servizi cloud (Aruba, Hetzner, piattaforme AI). NESSUNA sede fissa "
."ne perimetro fisico aziendale: il lavoro si svolge da qualsiasi luogo. La sicurezza fisica dei data center e gran parte "
."dell'infrastruttura sono ereditate dai fornitori cloud (modello di responsabilita condivisa). Documentazione minima, viva e "
."risk-based; evidenze raccolte in modo automatizzato dalla piattaforma (controlli periodici, audit interni).";
$ctxInt="Micro/piccola impresa (9 dipendenti + 2 collaboratori esterni a P.IVA + Presidente) interamente cloud, senza uffici fissi. "
."Dispositivi aziendali dedicati e cifrati con separazione netta tra strumenti privati e di lavoro; account e posta aziendali "
."sotto controllo della societa. Team tecnico interno; DPO esterno. Approccio SGSI agile: scope stretto, secure-by-default "
."(MFA, cifratura, SSO), documentazione concisa, miglioramento iterativo. Eccezione controllata: key escrow cifrato per le chiavi "
."di accesso ai cloud (recupero a doppia autorizzazione, mai in chiaro sui dispositivi).";
ex($pdo,"UPDATE isms_models SET scope_statement=?, context_internal=?, updated_at=? WHERE id=?",[$scope,$ctxInt,$NOW,$modelId]);
echo "SCOPE/contesto SGSI aggiornato (modello agile)\n";
/* ---- 3) Rischi del work-from-anywhere ---- */
$risks=[
['R-011','Lavoro su reti non fidate (Wi-Fi pubblici, reti domestiche)','Il lavoro da qualsiasi luogo avviene su reti non controllate dall\'azienda: rischio di intercettazione, MITM, accesso non autorizzato. Mitigazione: TLS ovunque, accesso ai servizi solo via canali cifrati/SSO con MFA, niente esposizione di servizi su reti locali.','cyber',3,3,'21.2.d'],
['R-012','Furto o smarrimento del dispositivo fuori sede','I dispositivi aziendali sono usati ovunque (casa, coworking, viaggi): rischio di furto/smarrimento con possibile esposizione di dati. Mitigazione: cifratura integrale del disco, MDM con blocco e cancellazione remota, blocco schermo automatico, niente dati sensibili in chiaro locale.','physical',3,3,'21.2.j'],
['R-013','Perdita di accesso ai servizi cloud per smarrimento delle chiavi','Forte dipendenza dalle chiavi/credenziali di accesso ai cloud: lo smarrimento dell\'unico dispositivo potrebbe causare il blocco dell\'accesso ai servizi. Mitigazione: key escrow CONTROLLATO (copie cifrate in gestore segreti, recupero a doppia autorizzazione, mai in chiaro sul portatile).','operational',2,4,'21.2.c'],
['R-014','Commistione tra strumenti privati e di lavoro / shadow IT','Uso di account o strumenti personali per attivita lavorative, o dati aziendali su dispositivi non gestiti. Mitigazione: dispositivi e account esclusivamente aziendali, separazione netta privato/lavoro, posta/servizi sotto controllo societario, policy d\'uso accettabile.','compliance',2,3,'21.2.i'],
];
$ri=0;
foreach($risks as [$rc,$t,$d,$cat,$L,$I,$art]){
if(one($pdo,"SELECT id FROM risks WHERE organization_id=? AND risk_code=?",[$ORGID,$rc])){ continue; }
ex($pdo,"INSERT INTO risks (organization_id,risk_code,title,description,category,likelihood,impact,inherent_risk_score,treatment,status,owner_user_id,review_date,nis2_article,created_at,updated_at)
VALUES (?,?,?,?,?,?,?,?, 'mitigate','treating', ?, ?, ?, ?, ?)",
[$ORGID,$rc,$t,$d,$cat,$L,$I,$L*$I,$ITSEC,'2026-12-20',$art,$NOW,$NOW]);
echo " risk + $rc (".($L*$I).")\n"; $ri++;
}
echo "RISCHI work-from-anywhere: +$ri\n";
echo "\nFASE 7 (modello agile) OK\n";
@@ -0,0 +1,49 @@
<?php
/**
* build_nuova_agile_p8_consolidate.php — Consolidazione lean: pubblica i 12 documenti consolidati (v2.0 da _docs_cons/)
* e ARCHIVIA i 36 originali (v1.0). Lo storico resta. IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
$NOW='2026-06-20 16:00:00'; $TODAY='2026-06-20'; $NEXT='2027-06-20'; $ORGID=996003; $RSGSI=326; $PRES=103;
$modelId=(int)one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$ORGID])['id'];
/* 1) Inserisci + pubblica i 12 consolidati (v2.0) */
$dir=__DIR__.'/_docs_cons'; $files=glob($dir.'/*.html'); sort($files);
$ins=0;$skip=0;
foreach($files as $f){
$raw=file_get_contents($f); $nl=strpos($raw,"\n");
$meta=substr($raw,0,$nl); $body=ltrim(substr($raw,$nl+1));
if(!preg_match('/^<!--META\|doc_type=([a-z_]+)\|title=(.+)\|status=(?:draft|review|approved)\|version=([0-9.]+)-->/',$meta,$mm)){ echo " ! META KO ".basename($f)."\n"; continue; }
[$_,$dt,$title,$ver]=$mm; $title=trim($title);
if(one($pdo,"SELECT id FROM isms_documents WHERE isms_model_id=? AND title=? AND status<>'archived'",[$modelId,$title])){ echo " = $title\n"; $skip++; continue; }
ex($pdo,"INSERT INTO isms_documents (isms_model_id,organization_id,doc_type,title,status,ai_generated,body_html,version,
approved_by,approved_at,reviewed_by,reviewed_at,published_by,published_at,effective_date,next_review_date,created_by,created_at,updated_at)
VALUES (?,?,?,?, 'published',1,?, ?, ?,?,?,?,?,?,?,?, ?,?,?)",
[$modelId,$ORGID,$dt,$title,$body,$ver, $RSGSI,$NOW,$RSGSI,$NOW,$PRES,$NOW,$TODAY,$NEXT, $RSGSI,$NOW,$NOW]);
$did=(int)$pdo->lastInsertId();
ex($pdo,"INSERT INTO isms_document_versions (document_id,isms_model_id,organization_id,version,status,body_html,change_note,created_by,created_at)
VALUES (?,?,?,?, 'published', ?, ?, ?, ?)",
[$did,$modelId,$ORGID,$ver,$body,'Pubblicazione versione '.$ver.' (consolidamento lean)',$PRES,$NOW]);
echo " + [$dt] $title\n"; $ins++;
}
echo "CONSOLIDATI: +$ins pubblicati v2.0 (skip $skip)\n";
/* 2) Archivia gli originali v1.0 ancora pubblicati (i 36 superati dal consolidamento) */
$toArch=$pdo->query("SELECT id,title FROM isms_documents WHERE isms_model_id=$modelId AND status='published' AND version='1.0'")->fetchAll(PDO::FETCH_ASSOC);
$arc=0;
foreach($toArch as $d){
ex($pdo,"UPDATE isms_documents SET status='archived', archived_at=?, updated_at=? WHERE id=?",[$NOW,$NOW,$d['id']]);
$arc++;
}
echo "ARCHIVIATI: $arc originali v1.0\n";
/* 3) Riepilogo */
echo "--- stato finale documenti (model $modelId) ---\n";
foreach($pdo->query("SELECT status, version, COUNT(*) c FROM isms_documents WHERE isms_model_id=$modelId GROUP BY status,version ORDER BY status,version")->fetchAll(PDO::FETCH_ASSOC) as $r)
echo " {$r['status']} v{$r['version']}: {$r['c']}\n";
echo "FASE 8 (consolidamento) OK\n";
@@ -0,0 +1,53 @@
<?php
/**
* build_nuova_agile_p9_crossrefs.php — Allinea i riferimenti incrociati nei 12 documenti consolidati
* (dopo il consolidamento 36->12). Aggiorna body_html + lo snapshot v2.0. IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$NOW='2026-06-20 16:30:00'; $modelId=3;
$manSec8Old = '/La documentazione è organizzata su livelli gerarchici:.*?qui esposti\./su';
$manSec8New = 'La documentazione segue un approccio lean ed è organizzata su pochi livelli: (1) Manuale SGSI e Politica Generale per la Sicurezza delle Informazioni; '
.'(2) cinque politiche tematiche consolidate — Controllo degli accessi, Crittografia e gestione delle chiavi, Uso accettabile/dispositivi/lavoro da remoto, '
.'Sicurezza del cloud e protezione dei dati, Gestione dei fornitori; (3) cinque procedure operative consolidate — Gestione del rischio, Gestione degli incidenti e '
.'notifica NIS2, Continuità/backup/ripristino, Gestione operativa e sviluppo sicuro, Governance del SGSI; (4) registrazioni ed evidenze raccolte automaticamente '
.'dalla piattaforma. In totale 12 documenti vivi, soggetti a controllo di versione, approvazione e revisione periodica (A.5.37); le versioni precedenti restano '
.'archiviate per tracciabilità.';
function fix(string $b): array {
$log=[];
$b2=str_replace('Politica per la Sicurezza delle Informazioni','Politica Generale per la Sicurezza delle Informazioni',$b,$c1);
if($c1){$b=$b2;$log[]="rinomina Politica Generale ($c1)";}
$b2=str_replace('Procedura di Controllo dei Documenti','Procedura di Governance del SGSI',$b,$c2);
if($c2){$b=$b2;$log[]="rinvio->Governance del SGSI ($c2)";}
$b2=preg_replace('/l[\x{2019}\x{0027}]Istruzione Hardening/u','le regole di hardening della Politica di Uso Accettabile, Dispositivi e Lavoro da Remoto',$b,-1,$c3);
if($c3){$b=$b2;$log[]="istruzione hardening assorbita ($c3)";}
return [$b,$log];
}
$docs=$pdo->query("SELECT id,title,body_html FROM isms_documents WHERE isms_model_id=$modelId AND status='published' ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
$tot=0;
foreach($docs as $d){
[$nb,$log]=fix($d['body_html']);
// sezione 8 del Manuale
if(strpos($d['title'],'approccio agile')!==false){
$nb2=preg_replace($manSec8Old,$manSec8New,$nb,-1,$cm);
if($cm){$nb=$nb2;$log[]="Manuale sez.8 struttura documentale ($cm)";}
}
if($nb!==$d['body_html']){
$st=$pdo->prepare("UPDATE isms_documents SET body_html=?, updated_at=? WHERE id=?"); $st->execute([$nb,$NOW,$d['id']]);
// aggiorna lo snapshot v2.0 più recente
$v=$pdo->prepare("SELECT id FROM isms_document_versions WHERE document_id=? AND version='2.0' ORDER BY id DESC LIMIT 1"); $v->execute([$d['id']]); $vid=$v->fetchColumn();
if($vid){ $u=$pdo->prepare("UPDATE isms_document_versions SET body_html=? WHERE id=?"); $u->execute([$nb,$vid]); }
echo " ~ {$d['title']}: ".implode(', ',$log)."\n"; $tot++;
}
}
echo "DOCUMENTI corretti: $tot / ".count($docs)."\n";
/* verifica residui */
$res=$pdo->query("SELECT COUNT(*) FROM isms_documents WHERE isms_model_id=$modelId AND status='published' AND (body_html LIKE '%Politica per la Sicurezza delle Informazioni%' OR body_html LIKE '%Procedura di Controllo dei Documenti%' OR body_html LIKE '%Istruzione Hardening%')")->fetchColumn();
echo "Riferimenti obsoleti residui nei pubblicati: $res (atteso 0)\n";
echo "FASE 9 (riferimenti incrociati) OK\n";
+148
View File
@@ -0,0 +1,148 @@
<?php
/**
* db_integrity_probe.php — Sonda READ-ONLY di integrità referenziale del DB.
*
* Diagnostico: SOLO SELECT + information_schema. NON modifica nulla.
* Estende l'audit della migrazione 039 alle tabelle aggiunte dopo (040-057).
*
* Uso (dal container app, connessione TLS via config app):
* docker exec nis2-app php /var/www/nis2-agile/application/cli/db_integrity_probe.php
*
* Output: report testuale a sezioni + riepilogo finale. Exit 0 sempre (è una sonda).
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$db = $pdo->query('SELECT DATABASE()')->fetchColumn();
function q(PDO $pdo, string $sql, array $p = []): array {
$st = $pdo->prepare($sql); $st->execute($p); return $st->fetchAll(PDO::FETCH_ASSOC);
}
function h(string $t): void { echo "\n=== $t ===\n"; }
echo "SONDA INTEGRITÀ DB — schema: $db\n";
echo "Modalità: READ-ONLY (information_schema + SELECT). Nessuna scrittura.\n";
$issues = 0;
/* 1) Tabelle senza PRIMARY KEY ----------------------------------------- */
h('1) Tabelle SENZA PRIMARY KEY');
$noPk = q($pdo, "
SELECT t.TABLE_NAME
FROM information_schema.TABLES t
LEFT JOIN information_schema.TABLE_CONSTRAINTS c
ON c.TABLE_SCHEMA=t.TABLE_SCHEMA AND c.TABLE_NAME=t.TABLE_NAME AND c.CONSTRAINT_TYPE='PRIMARY KEY'
WHERE t.TABLE_SCHEMA=? AND t.TABLE_TYPE='BASE TABLE' AND c.CONSTRAINT_NAME IS NULL
ORDER BY t.TABLE_NAME", [$db]);
if (!$noPk) echo " OK: tutte le tabelle hanno PK.\n";
else { foreach ($noPk as $r) echo " [!] {$r['TABLE_NAME']}\n"; $issues += count($noPk); }
/* 2) Tabelle non-InnoDB (niente FK) ------------------------------------ */
h('2) Tabelle con engine != InnoDB (FK non supportate)');
$eng = q($pdo, "
SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES
WHERE TABLE_SCHEMA=? AND TABLE_TYPE='BASE TABLE' AND ENGINE<>'InnoDB' ORDER BY TABLE_NAME", [$db]);
if (!$eng) echo " OK: tutte InnoDB.\n";
else { foreach ($eng as $r) echo " [!] {$r['TABLE_NAME']} = {$r['ENGINE']}\n"; $issues += count($eng); }
/* 3) Conteggio chiavi: PK / UNIQUE / FK per tabella -------------------- */
h('3) Tabelle senza alcun indice UNIQUE oltre alla PK (informativo)');
$rows = q($pdo, "
SELECT t.TABLE_NAME,
SUM(CASE WHEN s.NON_UNIQUE=0 AND s.INDEX_NAME<>'PRIMARY' THEN 1 ELSE 0 END) AS uniq_idx
FROM information_schema.TABLES t
LEFT JOIN information_schema.STATISTICS s
ON s.TABLE_SCHEMA=t.TABLE_SCHEMA AND s.TABLE_NAME=t.TABLE_NAME
WHERE t.TABLE_SCHEMA=? AND t.TABLE_TYPE='BASE TABLE'
GROUP BY t.TABLE_NAME HAVING uniq_idx=0 ORDER BY t.TABLE_NAME", [$db]);
echo ' ' . count($rows) . " tabelle senza UNIQUE secondario (normale per tabelle figlie/log).\n";
/* 4) Colonne 'code'/'token'/'*_hash' candidate a UNIQUE senza unico ----- */
h('4) Colonne chiave-naturale (code/token/hash/slug/uuid) SENZA UNIQUE');
$cands = q($pdo, "
SELECT c.TABLE_NAME, c.COLUMN_NAME
FROM information_schema.COLUMNS c
WHERE c.TABLE_SCHEMA=?
AND (c.COLUMN_NAME IN ('code','token','slug','uuid','key_hash','token_hash','qdrant_doc_uuid')
OR c.COLUMN_NAME LIKE '%_code' OR c.COLUMN_NAME LIKE '%_token' OR c.COLUMN_NAME LIKE '%_uuid')
AND NOT EXISTS (
SELECT 1 FROM information_schema.STATISTICS s
WHERE s.TABLE_SCHEMA=c.TABLE_SCHEMA AND s.TABLE_NAME=c.TABLE_NAME
AND s.COLUMN_NAME=c.COLUMN_NAME AND s.NON_UNIQUE=0)
ORDER BY c.TABLE_NAME, c.COLUMN_NAME", [$db]);
if (!$cands) echo " OK: nessuna colonna chiave-naturale priva di UNIQUE.\n";
else { foreach ($cands as $r) echo " [?] {$r['TABLE_NAME']}.{$r['COLUMN_NAME']} (valutare se è chiave naturale)\n"; }
echo " NB: molti '%_code' sono riferimenti (es. ref_code, control_code) e NON vanno resi unici.\n";
/* 5) Verifica mirata: duplicati su (organization_id, code) per i moduli a codice progressivo */
h('5) Duplicati su (organization_id, code) — pre-check sicurezza UNIQUE (mig.058)');
foreach (['internal_audits','management_reviews','periodic_controls'] as $tbl) {
$exists = q($pdo, "SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?", [$db,$tbl]);
if (!$exists) { echo " - $tbl: assente\n"; continue; }
$dup = q($pdo, "SELECT organization_id, code, COUNT(*) n FROM `$tbl`
WHERE code IS NOT NULL GROUP BY organization_id, code HAVING n>1");
$uq = q($pdo, "SELECT INDEX_NAME FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND NON_UNIQUE=0 AND INDEX_NAME<>'PRIMARY'
GROUP BY INDEX_NAME", [$db,$tbl]);
$hasUq = $uq ? implode(',', array_column($uq,'INDEX_NAME')) : '—';
if ($dup) { echo " [!] $tbl: " . count($dup) . " coppie (org,code) DUPLICATE → UNIQUE da bonificare prima! UNIQUE attuali: $hasUq\n"; $issues += count($dup); }
else { echo " OK $tbl: 0 duplicati (org,code) → UNIQUE applicabile in sicurezza. UNIQUE attuali: $hasUq\n"; }
}
/* 6) Colonne *_id senza FK (candidate) -------------------------------- */
h('6) Colonne *_id SENZA FOREIGN KEY (candidate / da valutare)');
$nofk = q($pdo, "
SELECT c.TABLE_NAME, c.COLUMN_NAME
FROM information_schema.COLUMNS c
WHERE c.TABLE_SCHEMA=? AND (c.COLUMN_NAME LIKE '%\\_id' OR c.COLUMN_NAME='id' AND 1=0)
AND c.COLUMN_NAME NOT IN ('id')
AND NOT EXISTS (
SELECT 1 FROM information_schema.KEY_COLUMN_USAGE k
WHERE k.TABLE_SCHEMA=c.TABLE_SCHEMA AND k.TABLE_NAME=c.TABLE_NAME
AND k.COLUMN_NAME=c.COLUMN_NAME AND k.REFERENCED_TABLE_NAME IS NOT NULL)
ORDER BY c.TABLE_NAME, c.COLUMN_NAME", [$db]);
if (!$nofk) echo " OK: ogni colonna *_id ha una FK.\n";
else {
echo " " . count($nofk) . " colonne *_id senza FK (alcune legittime: polimorfiche object_id, sso_identity_id cross-DB, sentinel 0):\n";
foreach ($nofk as $r) echo " - {$r['TABLE_NAME']}.{$r['COLUMN_NAME']}\n";
}
/* 7) Orphan check su organization_id senza FK (integrità multi-tenant) */
h('7) Righe ORFANE su organization_id privo di FK (integrità multi-tenant)');
$orgCols = q($pdo, "
SELECT c.TABLE_NAME
FROM information_schema.COLUMNS c
WHERE c.TABLE_SCHEMA=? AND c.COLUMN_NAME='organization_id'
AND NOT EXISTS (
SELECT 1 FROM information_schema.KEY_COLUMN_USAGE k
WHERE k.TABLE_SCHEMA=c.TABLE_SCHEMA AND k.TABLE_NAME=c.TABLE_NAME
AND k.COLUMN_NAME='organization_id' AND k.REFERENCED_TABLE_NAME='organizations')
ORDER BY c.TABLE_NAME", [$db]);
// Pattern "by design": FK volutamente assente (trail immutabile / sentinel). NON bloccanti.
$byDesign = ['audit_logs' => 'audit trail immutabile (mig.006), deve sopravvivere alla cancellazione org'];
if (!$orgCols) echo " OK: ogni organization_id ha FK verso organizations.\n";
else foreach ($orgCols as $r) {
$t = $r['TABLE_NAME'];
$o = q($pdo, "SELECT COUNT(*) n FROM `$t` x
WHERE x.organization_id IS NOT NULL AND x.organization_id<>0
AND NOT EXISTS (SELECT 1 FROM organizations o WHERE o.id=x.organization_id)");
$n = (int)($o[0]['n'] ?? 0);
$zero = (int)(q($pdo, "SELECT COUNT(*) n FROM `$t` WHERE organization_id=0")[0]['n'] ?? 0);
$msg = " $t: orfani=$n" . ($zero ? " (+$zero righe org_id=0 sentinel)" : '') . " — FK assente";
if (isset($byDesign[$t])) { echo " [by-design]$msg ({$byDesign[$t]})\n"; }
elseif ($n>0) { echo " [!]$msg\n"; $issues += $n; }
else echo "$msg\n";
}
/* 8) Riepilogo FK totali ---------------------------------------------- */
h('8) Riepilogo');
$fkCount = (int)(q($pdo, "SELECT COUNT(*) n FROM information_schema.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='FOREIGN KEY'", [$db])[0]['n'] ?? 0);
$tblCount = (int)(q($pdo, "SELECT COUNT(*) n FROM information_schema.TABLES
WHERE TABLE_SCHEMA=? AND TABLE_TYPE='BASE TABLE'", [$db])[0]['n'] ?? 0);
echo " Tabelle: $tblCount | Foreign key totali: $fkCount\n";
echo " Problemi BLOCCANTI rilevati (PK mancanti / orfani reali / duplicati su code): $issues\n";
echo ($issues===0 ? " ESITO: nessun problema bloccante.\n" : " ESITO: rivedere le voci [!] sopra.\n");
exit(0);
@@ -0,0 +1,95 @@
<?php
/**
* ingest_doc_management_kb.php — Ingest KB (RAG, scope SYSTEM) della guida "Gestione documentale SGSI".
* Testo INLINE (il container monta solo application/, non docs/). Idempotente (delete per source + re-upsert).
* Eseguire dal container: docker exec nis2-app php /var/www/nis2-agile/application/cli/ingest_doc_management_kb.php
* Opzioni: --dry-run
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
if (!defined('BASE_PATH')) define('BASE_PATH', dirname(dirname(__DIR__)));
if (!defined('APP_PATH')) define('APP_PATH', dirname(__DIR__));
require_once APP_PATH . '/config/env.php';
require_once APP_PATH . '/config/config.php';
require_once APP_PATH . '/config/database.php';
require_once APP_PATH . '/services/EmbedService.php';
require_once APP_PATH . '/services/VectorService.php';
$opts = getopt('', ['dry-run']); $dryRun = isset($opts['dry-run']);
function logln(string $m): void { echo '[' . date('Y-m-d H:i:s') . "] $m\n"; }
function uuid(): string { $b = random_bytes(16); $b[6]=chr((ord($b[6])&0x0f)|0x40); $b[8]=chr((ord($b[8])&0x3f)|0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b),4)); }
function chunkText(string $t, int $size=2000, int $ov=200): array { $c=[]; $len=mb_strlen($t,'UTF-8'); $s=0; while($s<$len){ $take=min($size,$len-$s); $p=mb_substr($t,$s,$take,'UTF-8'); if(trim($p)!=='') $c[]=$p; if($s+$take>=$len) break; $s+=($size-$ov);} return $c; }
$SOURCE = 'Documentazione prodotto NIS2 Agile — Gestione documentale SGSI';
$TITLE = 'Gestione documentale SGSI (ciclo di vita, Word, editor, permessi)';
$TEXT = <<<'TXT'
DOCUMENTO: Gestione documentale del Modello SGSI in NIS2 Agile (ciclo di vita, export Word, editor, permessi).
USO: aiutare l'utente a gestire i documenti del Sistema di Gestione (Manuale, politiche, procedure, istruzioni operative).
NOTA: descrive funzioni dell'interfaccia; non e' contenuto normativo. ISO 27001/27017/27018 sono buone prassi; gli obblighi in Italia derivano da NIS2 / D.Lgs. 138/2024 / Determinazioni ACN.
# Gestione documentale del Modello SGSI (Modello SGSI > Documenti)
I documenti del Modello SGSI (ISO 27001) — Manuale SGSI, politiche, procedure e istruzioni operative — si trovano nel modulo "Modello SGSI (ISO 27001)", passo 5 "Documenti".
## Ciclo di vita del documento (ISO/IEC 27001 cl. 7.5)
Ogni documento attraversa gli stati: Bozza -> In revisione -> Approvato -> Pubblicato -> Archiviato. Lo stato e' mostrato con un badge colorato.
- "Invia in revisione": da Bozza a In revisione.
- "Approva": da In revisione ad Approvato (registra chi approva e quando).
- "Pubblica": da Approvato a Pubblicato — il documento entra in vigore; vengono impostate la data di entrata in vigore e la prossima data di riesame (a 1 anno).
- "Rimanda in bozza": riporta un documento in revisione alla Bozza, con una nota.
- "Archivia": ritira un documento pubblicato.
- "Nuova versione": riapre un documento pubblicato come nuova Bozza con numero di versione incrementato; la versione precedente resta nello storico.
Ad ogni pubblicazione viene salvato uno snapshot di versione, consultabile con il pulsante "Storico".
## Scaricare in Word
- Pulsante "Word" su un documento: scarica quel singolo documento in formato .doc, modificabile in Word o LibreOffice (mantiene titoli, elenchi e tabelle).
- Pulsante "Scarica tutto (Word)": scarica l'intero set documentale in un unico file Word, con copertina e indice.
- Pulsante "Apri": mostra il documento in una nuova scheda del browser.
## Modificare il contenuto
Il pulsante "Modifica" apre un editor con barra strumenti (grassetto, corsivo, titoli, elenchi) e una modalita' HTML. La modifica e' consentita solo sui documenti in stato Bozza o In revisione. Per modificare un documento gia' Pubblicato occorre prima creare una "Nuova versione".
## Permessi documentali (chi puo' fare cosa)
I diritti sulle azioni del ciclo di vita sono configurabili per RUOLO in "Impostazioni > Permessi documentali", tramite una matrice Ruolo x Azione (modifica contenuto, invia in revisione, approva, pubblica, rimanda in bozza, archivia, nuova versione). Vale per la singola organizzazione. Di default sono abilitati Amministratore, Responsabile SGSI/Compliance e Direzione; il super amministratore ha sempre tutti i permessi. La matrice e' visibile a tutti i membri, ma solo Amministratore e Direzione possono modificarla.
TXT;
$text = preg_replace('/\n{3,}/', "\n\n", trim($TEXT));
$chunks = chunkText($text, 2000, 200);
logln('=== Ingest "Gestione documentale SGSI" (scope SYSTEM) ===');
logln(' testo: ' . strlen($text) . ' char -> ' . count($chunks) . ' chunk');
if ($dryRun) { logln('DRY-RUN: nessun upsert.'); exit(0); }
$embed = new EmbedService();
$vector = new VectorService();
$vector->ensureCollection($embed->dims);
try {
$vector->deleteByFilter(['must' => [
['key' => 'scope', 'match' => ['value' => 'SYSTEM']],
['key' => 'source', 'match' => ['value' => $SOURCE]],
]]);
logln(' rimossi eventuali chunk precedenti per questa fonte');
} catch (Throwable $e) { logln(' (warning) delete: ' . $e->getMessage()); }
$docUuid = uuid(); $points = [];
foreach ($chunks as $i => $chunk) {
$vec = null;
for ($try = 1; $try <= 5; $try++) {
try { $vec = $embed->embed($chunk); break; }
catch (Throwable $e) { if ($try === 5) { logln(" ERRORE embed chunk {$i}: " . $e->getMessage()); throw $e; } logln(" retry embed chunk {$i} (tentativo {$try})"); sleep($try); }
}
$points[] = ['id' => uuid(), 'vector' => $vec, 'payload' => [
'doc_uuid' => $docUuid,
'title' => $TITLE . ($i > 0 ? ' (parte ' . ($i + 1) . ')' : ''),
'chunk' => $chunk,
'entity_type' => 'guida_prodotto',
'source' => $SOURCE,
'lang' => 'it',
'scope' => 'SYSTEM',
'consulting_firm_id' => null,
'organization_id' => null,
'shared_with_orgs' => [],
'uploaded_by' => 0,
]];
}
foreach (array_chunk($points, 64) as $batch) { $vector->upsertBatch($batch); }
logln(' upsert OK: ' . count($points) . ' chunk SYSTEM (source="' . $SOURCE . '")');
logln('=== FATTO ===');
+157
View File
@@ -0,0 +1,157 @@
<?php
/**
* Ingest KB (RAG) — Archivio Policy unificato (ticket #384).
* ----------------------------------------------------------------------------
* Indicizza nella collection Qdrant `nis2_kb` (scope SYSTEM) la conoscenza
* prodotto sulle nuove funzioni Policy (#384): unificazione Policy/Procedura,
* collegamento M:N con Misure e Requisiti, allegati, matrice RACI. Cosi'
* l'assistente AI (ARIA) e AIService::askWithRag() possono rispondere e citare.
*
* Self-contained: il testo del documento e' INLINE (niente file esterno), e lo
* script vive sotto application/cli/ (montata in nis2-app) → si esegue diretto:
* docker exec nis2-app php /var/www/nis2-agile/application/cli/ingest_kb_policy_384.php
* docker exec nis2-app php .../ingest_kb_policy_384.php --dry-run # statistiche, no upsert
*
* Idempotente: cancella i chunk SYSTEM di questa stessa `source` prima del re-upsert.
* Richiede Qdrant (172.21.0.5) + Voyage → eseguire SU HETZNER (nis2-app), non dal devenv.
* ============================================================================
*/
if (PHP_SAPI !== 'cli') { fwrite(STDERR, "Solo CLI\n"); exit(1); }
define('APP_PATH', dirname(__DIR__)); // .../application
define('BASE_PATH', dirname(APP_PATH)); // project root
require_once APP_PATH . '/config/env.php';
require_once APP_PATH . '/config/config.php';
require_once APP_PATH . '/config/database.php';
require_once APP_PATH . '/services/EmbedService.php';
require_once APP_PATH . '/services/VectorService.php';
$opts = getopt('', ['dry-run']);
$dryRun = isset($opts['dry-run']);
$SOURCE = 'NIS2 Agile — Archivio Policy unificato (#384)';
$TITLE = 'Policy unificate: collegamento Misure e Requisiti, allegati, matrice RACI';
$DOC = <<<'MD'
DOCUMENTO: Archivio Policy unificato in NIS2 Agile (funzioni introdotte dal ticket #384).
USO: aiutare l'utente a capire le Policy, il loro collegamento con Misure e Requisiti, gli allegati e la matrice RACI.
## Policy e Procedure: un unico archivio
In NIS2 Agile i termini "Policy" e "Procedura" indicano lo stesso oggetto: l'archivio e' unico ed e' gestito dalla pagina "Policy". Una policy puo' rappresentare sia una politica di alto livello sia una procedura operativa.
Nota di metodo (fonti certe): questa unificazione e' una scelta di prodotto. Nessuna norma NIS2 — Direttiva (UE) 2022/2555 e D.Lgs. 138/2024 art. 24 (misure di gestione del rischio) — impone di tenere separate policy e procedure. Lo standard ISO/IEC 27001:2022 (cl. 5.2) e la ISO/IEC 27002 distinguono la "policy" (dichiarazione di intenti di alto livello) dalla "procedura" (documento operativo): e' una buona pratica, NON un obbligo di legge.
## Codici Policy
Ogni policy ha un codice nella forma "Policy.NN" (in precedenza "Proc.NN"). Il codice identifica la policy nell'elenco e nei collegamenti.
## Collegamento con Misure e Requisiti (relazione molti-a-molti)
Le pagine "Policy" e "Misure e Requisiti" sono collegate con una relazione molti-a-molti:
- un requisito NIS2 puo' essere coperto da piu' policy;
- una policy puo' coprire piu' requisiti NIS2.
Nella pagina "Misure e Requisiti" la colonna "Policy (default)" elenca tutte le policy collegate a ciascun requisito. Nella pagina "Policy" la colonna "Requisito NIS2" elenca tutti i requisiti collegati a ciascuna policy; il codice del requisito e' la concatenazione di MISURA + REQUISITO (esempio: "DE.CM-01 comma 1").
I codici mostrati sono link cliccabili: da una policy si apre il requisito collegato, e dal requisito si apre la policy collegata (navigazione bidirezionale tra le due pagine).
## Allegati alle Policy
Nel dettaglio di ogni policy e' presente la sezione "Allegati": si possono caricare file (PDF, immagini PNG/JPG/GIF/WEBP, documenti Office DOC/DOCX/XLS/XLSX/PPT/PPTX/ODT/ODS, txt, csv, zip), fino a 10 MB ciascuno. Per sicurezza non sono ammessi file html, svg o js (prevenzione XSS). Ogni allegato e' registrato con autore e data e puo' essere scaricato.
## Matrice RACI sulla Policy
Ogni policy ha una "Matrice RACI" per assegnare le responsabilita' ai ruoli definiti nell'Organigramma del prodotto. I quattro ruoli RACI sono:
- R = Responsible: chi esegue concretamente l'attivita';
- A = Accountable: chi ne risponde e la approva (unico responsabile finale);
- C = Consulted: chi viene consultato;
- I = Informed: chi viene informato.
Regola di approvazione: per APPROVARE una policy occorre aver assegnato almeno una R e almeno una A; in stato di bozza l'assegnazione e' libera e non vincolante. Questa e' una regola di governance interna del prodotto, NON un obbligo normativo NIS2: serve a garantire che ogni policy approvata abbia un esecutore e un responsabile chiari.
## Finestra di modifica Policy
Nella finestra "Modifica Policy" le etichette dei campi compaiono sopra i rispettivi campi (allineamento corretto, conforme al design system AGID/Bootstrap Italia).
MD;
function logln(string $m): void { echo '[' . date('Y-m-d H:i:s') . "] $m\n"; }
function chunkText(string $text, int $size = 2000, int $overlap = 200): array
{
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
$chunks = []; $len = mb_strlen($text, 'UTF-8'); $start = 0;
while ($start < $len) {
$take = min($size, $len - $start);
$piece = mb_substr($text, $start, $take, 'UTF-8');
if (trim($piece) !== '') $chunks[] = $piece;
if ($start + $take >= $len) break;
$start += ($size - $overlap);
}
return $chunks;
}
function uuid(): string
{
$b = random_bytes(16);
$b[6] = chr((ord($b[6]) & 0x0f) | 0x40);
$b[8] = chr((ord($b[8]) & 0x3f) | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));
}
logln('=== Ingest KB "Archivio Policy unificato (#384)" (scope SYSTEM) ===');
if ($dryRun) logln('MODALITA DRY-RUN: nessun upsert.');
$text = preg_replace('/[ \t]+/', ' ', $DOC);
$text = preg_replace('/\n{3,}/', "\n\n", trim($text));
if (strlen($text) < 200) { logln('ERRORE: testo troppo breve.'); exit(1); }
$header = "DOCUMENTO: Funzioni Policy di NIS2 Agile (unificazione Policy/Procedura, collegamento Misure e Requisiti, allegati, matrice RACI).\n"
. "USO: rispondere alle domande dell'utente su Policy, requisiti collegati, allegati e RACI.\n\n";
$chunks = chunkText($header . $text, 2000, 200);
logln(' testo: ' . strlen($text) . ' char -> ' . count($chunks) . ' chunk');
if ($dryRun) { logln('=== DRY-RUN completato ==='); exit(0); }
$embed = new EmbedService();
$vector = new VectorService();
$vector->ensureCollection($embed->dims);
// Idempotenza: rimuovi i chunk SYSTEM esistenti per questa fonte
try {
$vector->deleteByFilter(['must' => [
['key' => 'scope', 'match' => ['value' => 'SYSTEM']],
['key' => 'source', 'match' => ['value' => $SOURCE]],
]]);
} catch (Exception $e) { logln(' (warning) delete precedente: ' . $e->getMessage()); }
$docUuid = uuid();
$points = [];
foreach ($chunks as $i => $chunk) {
$vec = null;
for ($try = 1; $try <= 5; $try++) {
try { $vec = $embed->embed($chunk); break; }
catch (Throwable $e) {
if ($try === 5) { logln(" ERRORE embed chunk {$i} dopo 5 tentativi: " . $e->getMessage()); throw $e; }
logln(" retry embed chunk {$i} (tentativo {$try})"); sleep($try);
}
}
$points[] = [
'id' => uuid(),
'vector' => $vec,
'payload' => [
'doc_uuid' => $docUuid,
'title' => $TITLE . ($i > 0 ? ' (parte ' . ($i + 1) . ')' : ''),
'chunk' => $chunk,
'entity_type' => 'guida_prodotto',
'source' => $SOURCE,
'lang' => 'it',
'scope' => 'SYSTEM',
'consulting_firm_id' => null,
'organization_id' => null,
'shared_with_orgs' => [],
'uploaded_by' => 0,
],
];
}
foreach (array_chunk($points, 64) as $batch) { $vector->upsertBatch($batch); }
// Tracking MySQL (best-effort)
try {
$stmt = Database::getInstance()->prepare(
"INSERT INTO kb_uploaded_documents
(qdrant_doc_uuid, scope, consulting_firm_id, organization_id, uploaded_by, title, entity_type, source, lang, chunk_count, shared_with_orgs, status)
VALUES (?, 'SYSTEM', NULL, NULL, 0, ?, 'guida_prodotto', ?, 'it', ?, '[]', 'ready')"
);
$stmt->execute([$docUuid, $TITLE, $SOURCE, count($chunks)]);
} catch (Exception $e) { logln(' (warning) tracking insert: ' . $e->getMessage()); }
logln("=== OK indicizzato: doc_uuid={$docUuid}, " . count($chunks) . " chunk ===");
+24
View File
@@ -0,0 +1,24 @@
<?php
/** ingest_v2nav_kb.php — Re-ingest della guida UI V2 CORRETTA (sostituisce i chunk stale del 12/6). Idempotente. */
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once dirname(__DIR__).'/config/env.php';
require_once dirname(__DIR__).'/config/config.php';
require_once dirname(__DIR__).'/config/database.php';
require_once dirname(__DIR__).'/services/EmbedService.php';
require_once dirname(__DIR__).'/services/VectorService.php';
function logln($m){ echo '['.date('H:i:s')."] $m\n"; }
function uuid(){ $b=random_bytes(16); $b[6]=chr((ord($b[6])&0x0f)|0x40); $b[8]=chr((ord($b[8])&0x3f)|0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s',str_split(bin2hex($b),4)); }
function chunkText($t,$size=2000,$ov=200){ $c=[];$len=mb_strlen($t,'UTF-8');$s=0; while($s<$len){$take=min($size,$len-$s);$p=mb_substr($t,$s,$take,'UTF-8');if(trim($p)!=='')$c[]=$p;if($s+$take>=$len)break;$s+=($size-$ov);} return $c; }
$SOURCE='NIS2 Agile — Guida interfaccia V2 (Bootstrap Italia)';
$TITLE ='Interfaccia V2 NIS2 Agile — navigazione e moduli';
$f=__DIR__.'/_v2nav.md'; if(!is_file($f)){ exit("doc mancante\n"); }
$text=preg_replace('/\n{3,}/',"\n\n",trim((string)file_get_contents($f)));
$chunks=chunkText($text);
logln("testo ".strlen($text)." char -> ".count($chunks)." chunk");
$embed=new EmbedService(); $vector=new VectorService(); $vector->ensureCollection($embed->dims);
try{ $vector->deleteByFilter(['must'=>[['key'=>'scope','match'=>['value'=>'SYSTEM']],['key'=>'source','match'=>['value'=>$SOURCE]]]]); logln("rimossi chunk stale per source"); }catch(Throwable $e){ logln("warn delete: ".$e->getMessage()); }
$du=uuid(); $pts=[];
foreach($chunks as $i=>$ck){ $v=null; for($t=1;$t<=5;$t++){ try{$v=$embed->embed($ck);break;}catch(Throwable $e){ if($t==5)throw $e; sleep($t);} }
$pts[]=['id'=>uuid(),'vector'=>$v,'payload'=>['doc_uuid'=>$du,'title'=>$TITLE.($i>0?' (parte '.($i+1).')':''),'chunk'=>$ck,'entity_type'=>'guida_prodotto','source'=>$SOURCE,'lang'=>'it','scope'=>'SYSTEM','consulting_firm_id'=>null,'organization_id'=>null,'shared_with_orgs'=>[],'uploaded_by'=>0]]; }
foreach(array_chunk($pts,64) as $b) $vector->upsertBatch($b);
logln("upsert OK: ".count($pts)." chunk SYSTEM");
@@ -0,0 +1,137 @@
<?php
/**
* migrate_058_integrity_keys.php — Hardening integrità chiavi (estende mig.039
* alle tabelle aggiunte dopo: 040-057). Runner IDEMPOTENTE e GUARDATO.
*
* Verificato sul dato reale (db_integrity_probe.php/probe2.php, 2026-06-18):
* - 0 duplicati (org,code) su internal_audits/management_reviews
* - 0 valori duplicati su kb_uploaded_documents.qdrant_doc_uuid (16 righe)
* - whistleblowing_reports vuota; anonymous_token = chiave retrieval
* - 0 orfani su consulting_firm_id (organizations/users/kb), linked_control_id,
* linked_policy_id, capa_id
*
* TIER 1 (additivo, default): 4 UNIQUE + 6 FK. Nessuna modifica di dato.
* TIER 2 (--with-cleanup): bonifica righe stale (orfane) + 2 FK. CANCELLA righe.
*
* Uso:
* docker exec nis2-app php .../application/cli/migrate_058_integrity_keys.php # Tier1
* docker exec nis2-app php .../application/cli/migrate_058_integrity_keys.php --with-cleanup
* ... --dry-run # mostra solo cosa farebbe, non esegue
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$DRY = in_array('--dry-run', $argv, true);
$CLEANUP = in_array('--with-cleanup', $argv, true);
$pdo = Database::getInstance();
$db = $pdo->query('SELECT DATABASE()')->fetchColumn();
echo "mig.058 — schema=$db | dry-run=" . ($DRY?'SI':'no') . " | cleanup=" . ($CLEANUP?'SI':'no') . "\n\n";
function col1(PDO $p,$s,$a=[]){ $st=$p->prepare($s); $st->execute($a); return $st->fetchColumn(); }
function idxExists(PDO $p,$db,$t,$idx){ return (int)col1($p,"SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND INDEX_NAME=?",[$db,$t,$idx])>0; }
function fkExists(PDO $p,$db,$name){ return (int)col1($p,"SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='FOREIGN KEY' AND CONSTRAINT_NAME=?",[$db,$name])>0; }
function tblExists(PDO $p,$db,$t){ return (int)col1($p,"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?",[$db,$t])>0; }
$applied=0; $skipped=0; $failed=0;
function run(PDO $pdo,bool $DRY,string $label,string $sql){
global $applied,$skipped,$failed;
if($DRY){ echo " [DRY] $label\n $sql\n"; return; }
try { $pdo->exec($sql); echo " [OK ] $label\n"; $applied++; }
catch(\PDOException $e){
$c=(int)($e->errorInfo[1]??0);
// 1061 dup key, 1826/1022 dup FK, 1062 dup entry (già unico) → idempotente
if(in_array($c,[1061,1826,1022,1062],true)){ echo " [SKIP] $label (già presente, code=$c)\n"; $skipped++; }
else { echo " [FAIL] $label → ".$e->getMessage()."\n"; $failed++; }
}
}
/* ───────── TIER 1: UNIQUE su chiavi naturali (pre-check duplicati) ───────── */
echo "── TIER 1: UNIQUE ──\n";
// U1 internal_audits (organization_id, code)
if(!idxExists($pdo,$db,'internal_audits','uk_intaud_code')){
$dup=(int)col1($pdo,"SELECT COUNT(*) FROM (SELECT 1 FROM internal_audits WHERE code IS NOT NULL GROUP BY organization_id,code HAVING COUNT(*)>1) x");
if($dup>0){ echo " [FAIL] internal_audits: $dup duplicati (org,code) — bonificare prima\n"; $failed++; }
else run($pdo,$DRY,'internal_audits ADD UNIQUE uk_intaud_code(organization_id,code)',
"ALTER TABLE internal_audits ADD UNIQUE KEY uk_intaud_code (organization_id, code)");
} else { echo " [SKIP] internal_audits.uk_intaud_code già presente\n"; $skipped++; }
// U2 management_reviews (organization_id, code)
if(!idxExists($pdo,$db,'management_reviews','uk_mgr_code')){
$dup=(int)col1($pdo,"SELECT COUNT(*) FROM (SELECT 1 FROM management_reviews WHERE code IS NOT NULL GROUP BY organization_id,code HAVING COUNT(*)>1) x");
if($dup>0){ echo " [FAIL] management_reviews: $dup duplicati (org,code)\n"; $failed++; }
else run($pdo,$DRY,'management_reviews ADD UNIQUE uk_mgr_code(organization_id,code)',
"ALTER TABLE management_reviews ADD UNIQUE KEY uk_mgr_code (organization_id, code)");
} else { echo " [SKIP] management_reviews.uk_mgr_code già presente\n"; $skipped++; }
// U3 kb_uploaded_documents.qdrant_doc_uuid
if(!idxExists($pdo,$db,'kb_uploaded_documents','uk_kbdoc_qdrant')){
$dup=(int)col1($pdo,"SELECT COUNT(*) FROM (SELECT 1 FROM kb_uploaded_documents WHERE qdrant_doc_uuid IS NOT NULL AND qdrant_doc_uuid<>'' GROUP BY qdrant_doc_uuid HAVING COUNT(*)>1) x");
if($dup>0){ echo " [FAIL] kb_uploaded_documents: $dup uuid duplicati\n"; $failed++; }
else run($pdo,$DRY,'kb_uploaded_documents ADD UNIQUE uk_kbdoc_qdrant(qdrant_doc_uuid)',
"ALTER TABLE kb_uploaded_documents ADD UNIQUE KEY uk_kbdoc_qdrant (qdrant_doc_uuid)");
} else { echo " [SKIP] kb_uploaded_documents.uk_kbdoc_qdrant già presente\n"; $skipped++; }
// U4 whistleblowing_reports.anonymous_token (DROP idx_token ridondante + UNIQUE)
if(!idxExists($pdo,$db,'whistleblowing_reports','uq_wb_anon_token')){
$dup=(int)col1($pdo,"SELECT COUNT(*) FROM (SELECT 1 FROM whistleblowing_reports WHERE anonymous_token IS NOT NULL AND anonymous_token<>'' GROUP BY anonymous_token HAVING COUNT(*)>1) x");
if($dup>0){ echo " [FAIL] whistleblowing_reports: $dup token duplicati\n"; $failed++; }
else {
if(idxExists($pdo,$db,'whistleblowing_reports','idx_token'))
run($pdo,$DRY,'whistleblowing_reports DROP INDEX idx_token (ridondante)',
"ALTER TABLE whistleblowing_reports DROP INDEX idx_token");
run($pdo,$DRY,'whistleblowing_reports ADD UNIQUE uq_wb_anon_token(anonymous_token)',
"ALTER TABLE whistleblowing_reports ADD UNIQUE KEY uq_wb_anon_token (anonymous_token)");
}
} else { echo " [SKIP] whistleblowing_reports.uq_wb_anon_token già presente\n"; $skipped++; }
/* ───────── TIER 1: FOREIGN KEY mancanti (pre-check orfani) ───────── */
echo "\n── TIER 1: FOREIGN KEY ──\n";
// [constraint, tabella, colonna, ref_table, on_delete]
$fks = [
['fk_org_consulting_firm','organizations','consulting_firm_id','consulting_firms','SET NULL'],
['fk_users_consulting_firm','users','consulting_firm_id','consulting_firms','SET NULL'],
['fk_kbdoc_consulting_firm','kb_uploaded_documents','consulting_firm_id','consulting_firms','SET NULL'],
['fk_isms_soa_control','isms_soa','linked_control_id','compliance_controls','SET NULL'],
['fk_isms_doc_policy','isms_documents','linked_policy_id','policies','SET NULL'],
['fk_mrd_capa','management_review_decisions','capa_id','capa_actions','SET NULL'],
];
foreach($fks as [$name,$t,$c,$ref,$od]){
if(!tblExists($pdo,$db,$t) || !tblExists($pdo,$db,$ref)){ echo " [SKIP] $name (tabella assente)\n"; $skipped++; continue; }
if(fkExists($pdo,$db,$name)){ echo " [SKIP] $name già presente\n"; $skipped++; continue; }
$orf=(int)col1($pdo,"SELECT COUNT(*) FROM `$t` x WHERE x.`$c` IS NOT NULL AND x.`$c`<>0 AND NOT EXISTS(SELECT 1 FROM `$ref` r WHERE r.id=x.`$c`)");
if($orf>0){ echo " [FAIL] $name: $orf righe orfane su $t.$c → bonificare prima\n"; $failed++; continue; }
run($pdo,$DRY,"$t ADD FK $name($c)->$ref ON DELETE $od",
"ALTER TABLE `$t` ADD CONSTRAINT `$name` FOREIGN KEY (`$c`) REFERENCES `$ref`(id) ON DELETE $od ON UPDATE CASCADE");
}
/* ───────── TIER 2: bonifica righe stale + FK (solo con --with-cleanup) ───────── */
echo "\n── TIER 2: cleanup orfani (".($CLEANUP?'ATTIVO':'saltato, usa --with-cleanup').") ──\n";
if($CLEANUP){
// C1 firm_org_assignments: 9 righe verso org 126-129 inesistenti (dogfooding, deferred-b 039)
$n=(int)col1($pdo,"SELECT COUNT(*) FROM firm_org_assignments x WHERE NOT EXISTS(SELECT 1 FROM organizations o WHERE o.id=x.organization_id)");
if($n>0){
if($DRY) echo " [DRY] DELETE $n righe stale firm_org_assignments (org inesistente)\n";
else { $pdo->exec("DELETE FROM firm_org_assignments WHERE organization_id NOT IN (SELECT id FROM organizations)"); echo " [OK ] cancellate $n righe stale firm_org_assignments\n"; $applied++; }
} else echo " [SKIP] firm_org_assignments: 0 orfani\n";
if(!fkExists($pdo,$db,'fk_firm_org_assignments_organization_id'))
run($pdo,$DRY,'firm_org_assignments ADD FK organization_id->organizations CASCADE',
"ALTER TABLE firm_org_assignments ADD CONSTRAINT fk_firm_org_assignments_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE");
else { echo " [SKIP] fk_firm_org_assignments_organization_id già presente\n"; $skipped++; }
// C2 active_sessions: 7 sessioni verso org inesistenti
$n2=(int)col1($pdo,"SELECT COUNT(*) FROM active_sessions x WHERE x.organization_id IS NOT NULL AND x.organization_id<>0 AND NOT EXISTS(SELECT 1 FROM organizations o WHERE o.id=x.organization_id)");
if($n2>0){
if($DRY) echo " [DRY] DELETE $n2 sessioni stale active_sessions\n";
else { $pdo->exec("DELETE FROM active_sessions WHERE organization_id IS NOT NULL AND organization_id<>0 AND organization_id NOT IN (SELECT id FROM organizations)"); echo " [OK ] cancellate $n2 sessioni stale active_sessions\n"; $applied++; }
} else echo " [SKIP] active_sessions: 0 orfani\n";
if(!fkExists($pdo,$db,'fk_active_sessions_organization_id'))
run($pdo,$DRY,'active_sessions ADD FK organization_id->organizations CASCADE',
"ALTER TABLE active_sessions ADD CONSTRAINT fk_active_sessions_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE");
else { echo " [SKIP] fk_active_sessions_organization_id già presente\n"; $skipped++; }
}
echo "\n── RIEPILOGO ── applicati=$applied skip=$skipped falliti=$failed\n";
echo ($failed===0 ? "ESITO OK\n" : "ESITO: rivedere i [FAIL]\n");
exit($failed===0?0:1);
@@ -0,0 +1,107 @@
<?php
/**
* migrate_059_requisito_policy.php — Ticket #384 (p.2.2 + p.3): modello
* relazionale REQUISITO <-> POLICY. Runner IDEMPOTENTE e GUARDATO (PDO app, TLS).
*
* TUTTO ADDITIVO (nessun DROP/DELETE):
* 1) policies.proc_code VARCHAR(16) NULL (chiave di join verso il catalogo)
* 2) tabella requisito_policy (M:N org-scoped requisito<->policy)
* 3) backfill SICURO: proc_code per match esatto title==proc_descr +
* requisito_policy dalle associazioni di default esistenti (INSERT IGNORE).
*
* Uso:
* docker exec nis2-app php .../application/cli/migrate_059_requisito_policy.php
* ... --dry-run
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$DRY = in_array('--dry-run', $argv, true);
$pdo = Database::getInstance();
$db = $pdo->query('SELECT DATABASE()')->fetchColumn();
echo "mig.059 — schema=$db | dry-run=" . ($DRY ? 'SI' : 'no') . "\n\n";
function col1(PDO $p, $s, $a = []) { $st = $p->prepare($s); $st->execute($a); return $st->fetchColumn(); }
function colExists(PDO $p, $db, $t, $c) { return (int)col1($p, "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND COLUMN_NAME=?", [$db, $t, $c]) > 0; }
function tblExists(PDO $p, $db, $t) { return (int)col1($p, "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?", [$db, $t]) > 0; }
$applied = 0; $skipped = 0; $failed = 0;
function run(PDO $pdo, bool $DRY, string $label, string $sql) {
global $applied, $skipped, $failed;
if ($DRY) { echo " [DRY] $label\n $sql\n"; return; }
try { $pdo->exec($sql); echo " [OK ] $label\n"; $applied++; }
catch (\PDOException $e) {
$c = (int)($e->errorInfo[1] ?? 0);
if (in_array($c, [1060, 1061, 1826, 1022, 1062, 1050], true)) { echo " [SKIP] $label (gia' presente, code=$c)\n"; $skipped++; }
else { echo " [FAIL] $label -> " . $e->getMessage() . "\n"; $failed++; }
}
}
/* ── 1) policies.proc_code ── */
echo "── 1) policies.proc_code ──\n";
if (colExists($pdo, $db, 'policies', 'proc_code')) { echo " [SKIP] policies.proc_code gia' presente\n"; $skipped++; }
else run($pdo, $DRY, 'policies ADD COLUMN proc_code VARCHAR(16) NULL',
"ALTER TABLE policies ADD COLUMN proc_code VARCHAR(16) NULL AFTER nis2_article");
/* ── 2) tabella requisito_policy ── */
echo "\n── 2) tabella requisito_policy ──\n";
if (tblExists($pdo, $db, 'requisito_policy')) { echo " [SKIP] requisito_policy gia' presente\n"; $skipped++; }
else run($pdo, $DRY, 'CREATE TABLE requisito_policy',
"CREATE TABLE requisito_policy (
id INT NOT NULL AUTO_INCREMENT,
organization_id INT NOT NULL,
requisito_id INT NOT NULL,
policy_id INT NOT NULL,
is_default TINYINT(1) NOT NULL DEFAULT 0,
created_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_req_pol (organization_id, requisito_id, policy_id),
KEY idx_rp_org (organization_id),
KEY idx_rp_req (requisito_id),
KEY idx_rp_pol (policy_id),
CONSTRAINT fk_rp_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
CONSTRAINT fk_rp_req FOREIGN KEY (requisito_id) REFERENCES cfg_nis2_requisiti (id) ON DELETE CASCADE,
CONSTRAINT fk_rp_pol FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE,
CONSTRAINT fk_rp_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
/* ── 3) backfill SICURO (additivo) ── */
echo "\n── 3) backfill (additivo, idempotente) ──\n";
if (!$DRY && colExists($pdo, $db, 'policies', 'proc_code') && tblExists($pdo, $db, 'cfg_nis2_procedure')) {
// 3a) proc_code per match esatto del titolo (title = mb_substr(proc_descr,0,255))
$n1 = $pdo->exec("UPDATE policies p
JOIN cfg_nis2_procedure c ON p.title = c.proc_descr
SET p.proc_code = c.proc_code
WHERE p.proc_code IS NULL");
echo " [OK ] proc_code valorizzato su $n1 policy (match esatto titolo)\n"; $applied++;
// 3b) requisito_policy dalle associazioni di default (per ogni proc_code)
if (tblExists($pdo, $db, 'requisito_policy') && tblExists($pdo, $db, 'cfg_nis2_requisiti')) {
$n2 = $pdo->exec("INSERT IGNORE INTO requisito_policy (organization_id, requisito_id, policy_id, is_default)
SELECT p.organization_id, q.id, p.id, 1
FROM policies p
JOIN cfg_nis2_requisiti q ON q.proc_code = p.proc_code
WHERE p.proc_code IS NOT NULL");
echo " [OK ] requisito_policy popolata: $n2 associazioni di default\n"; $applied++;
}
} else {
echo " [SKIP] backfill (dry-run o prerequisiti assenti)\n"; $skipped++;
}
/* ── verifica finale ── */
echo "\n── VERIFICA ──\n";
if (!$DRY) {
$cWith = (int)col1($pdo, "SELECT COUNT(*) FROM policies WHERE proc_code IS NOT NULL");
$cTot = (int)col1($pdo, "SELECT COUNT(*) FROM policies");
echo " policies con proc_code: $cWith / $cTot\n";
if (tblExists($pdo, $db, 'requisito_policy')) {
$cLinks = (int)col1($pdo, "SELECT COUNT(*) FROM requisito_policy");
echo " requisito_policy righe: $cLinks\n";
}
}
echo "\n── RIEPILOGO ── applicati=$applied skip=$skipped falliti=$failed\n";
echo ($failed === 0 ? "ESITO OK\n" : "ESITO: rivedere i [FAIL]\n");
exit($failed === 0 ? 0 : 1);
@@ -0,0 +1,56 @@
<?php
/**
* migrate_060_isms_doc_lifecycle.php — Ciclo di vita documentale ISMS (ISO 27001 cl.7.5).
* Estende isms_documents (stati pubblicato/archiviato + tracciamento + riesame) + tabella versioni.
* IDEMPOTENTE: ALTER guardati da information_schema, CREATE TABLE IF NOT EXISTS.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$db = $pdo->query('SELECT DATABASE()')->fetchColumn();
function col_exists(PDO $p,string $db,string $t,string $c):bool{
$s=$p->prepare("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND COLUMN_NAME=?");
$s->execute([$db,$t,$c]); return (bool)$s->fetchColumn();
}
function add_col(PDO $p,string $db,string $t,string $c,string $ddl):void{
if(col_exists($p,$db,$t,$c)){ echo " = $t.$c\n"; return; }
$p->exec("ALTER TABLE `$t` ADD COLUMN $ddl"); echo " + $t.$c\n";
}
echo "Migrazione 060 — ciclo di vita documenti ISMS (db=$db)\n";
/* 1) Estensione enum stato (idempotente: MODIFY) */
$pdo->exec("ALTER TABLE isms_documents MODIFY COLUMN status ENUM('draft','review','approved','published','archived') NOT NULL DEFAULT 'draft'");
echo " enum status -> draft/review/approved/published/archived\n";
/* 2) Colonne di tracciamento del ciclo di vita */
add_col($pdo,$db,'isms_documents','reviewed_by', "reviewed_by INT NULL");
add_col($pdo,$db,'isms_documents','reviewed_at', "reviewed_at DATETIME NULL");
add_col($pdo,$db,'isms_documents','approved_by', "approved_by INT NULL");
add_col($pdo,$db,'isms_documents','approved_at', "approved_at DATETIME NULL");
add_col($pdo,$db,'isms_documents','published_by', "published_by INT NULL");
add_col($pdo,$db,'isms_documents','published_at', "published_at DATETIME NULL");
add_col($pdo,$db,'isms_documents','archived_at', "archived_at DATETIME NULL");
add_col($pdo,$db,'isms_documents','review_note', "review_note VARCHAR(500) NULL");
add_col($pdo,$db,'isms_documents','effective_date',"effective_date DATE NULL");
add_col($pdo,$db,'isms_documents','next_review_date',"next_review_date DATE NULL");
/* 3) Tabella storico versioni (snapshot ad ogni approvazione/pubblicazione) */
$pdo->exec("CREATE TABLE IF NOT EXISTS isms_document_versions (
id INT AUTO_INCREMENT PRIMARY KEY,
document_id INT NOT NULL,
isms_model_id INT NOT NULL,
organization_id INT NOT NULL,
version VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL,
body_html LONGTEXT NULL,
change_note VARCHAR(255) NULL,
created_by INT NULL,
created_at DATETIME NULL,
INDEX idx_docver_doc (document_id),
INDEX idx_docver_org (organization_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
echo " tabella isms_document_versions OK\n";
echo "FATTO. Prossima mig=061.\n";
@@ -0,0 +1,20 @@
<?php
/**
* migrate_061_doc_permissions.php — Permessi documentali profilabili per ruolo (org-scoped).
* Tabella matrice Ruolo x Azione del ciclo di vita documenti ISMS. IDEMPOTENTE.
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("CREATE TABLE IF NOT EXISTS isms_doc_permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
organization_id INT NOT NULL,
capability VARCHAR(20) NOT NULL,
role VARCHAR(30) NOT NULL,
allowed TINYINT(1) NOT NULL DEFAULT 1,
updated_at DATETIME NULL,
UNIQUE KEY uk_docperm (organization_id, capability, role),
INDEX idx_docperm_org (organization_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
echo "Migrazione 061 — tabella isms_doc_permissions OK. Prossima mig=062.\n";
@@ -0,0 +1,11 @@
<?php
/** migrate_062_feedback_provenance.php — version-provenance su feedback_reports (std feedback-version-provenance). IDEMPOTENTE. */
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__.'/../config/env.php'; require_once __DIR__.'/../config/database.php';
$pdo=Database::getInstance(); $db=$pdo->query('SELECT DATABASE()')->fetchColumn();
function addc($p,$db,$t,$c,$ddl){ $s=$p->prepare("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND COLUMN_NAME=?"); $s->execute([$db,$t,$c]); if($s->fetchColumn()){echo " = $t.$c\n";return;} $p->exec("ALTER TABLE `$t` ADD COLUMN $ddl"); echo " + $t.$c\n"; }
echo "Mig 062 — version-provenance feedback_reports\n";
addc($pdo,$db,'feedback_reports','client_surface',"client_surface VARCHAR(16) NULL");
addc($pdo,$db,'feedback_reports','client_version',"client_version VARCHAR(32) NULL");
addc($pdo,$db,'feedback_reports','client_build', "client_build VARCHAR(64) NULL");
echo "FATTO. Prossima mig=063.\n";
@@ -0,0 +1,36 @@
<?php
/**
* migrate_063_vocea_channel.php — Integrazione Vocea (whistleblowing zero-knowledge).
* organizations += vocea_tenant_slug, vocea_channel_status, vocea_enrolled_at. IDEMPOTENTE.
* SCAFFOLD DORMIENTE: nessun effetto funzionale finché VOCEA_ENABLED=false (default).
*
* Eseguire (host): docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_063_vocea_channel.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function colExists(PDO $pdo, string $table, string $col): bool {
$st = $pdo->prepare(
"SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
);
$st->execute([$table, $col]);
return (bool)$st->fetchColumn();
}
$adds = [
'vocea_tenant_slug' => "ADD COLUMN vocea_tenant_slug VARCHAR(64) NULL COMMENT 'Slug canale Vocea (NULL=nessuno)'",
'vocea_channel_status' => "ADD COLUMN vocea_channel_status ENUM('none','provisioning','active','suspended') NOT NULL DEFAULT 'none'",
'vocea_enrolled_at' => "ADD COLUMN vocea_enrolled_at TIMESTAMP NULL COMMENT 'Enrollment canale + key holder'",
];
foreach ($adds as $col => $clause) {
if (colExists($pdo, 'organizations', $col)) { echo " - $col gia presente, skip\n"; continue; }
$pdo->exec("ALTER TABLE organizations $clause");
echo " + aggiunta colonna organizations.$col\n";
}
echo "Migrazione 063 — organizations.vocea_* OK. Prossima mig=064.\n";
@@ -0,0 +1,65 @@
<?php
/**
* migrate_064_discovery_connectors.php — Connettori discovery + run + flussi di rete. IDEMPOTENTE.
* Esegui: docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_064_discovery_connectors.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("CREATE TABLE IF NOT EXISTS discovery_connectors (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
organization_id INT NOT NULL,
name VARCHAR(120) NOT NULL,
connector_type ENUM('network','aws','azure','gcp','cmdb','agent','custom') NOT NULL DEFAULT 'network',
config JSON NULL,
api_key_id INT UNSIGNED NULL,
status ENUM('active','paused','error') NOT NULL DEFAULT 'active',
schedule ENUM('manual','hourly','daily','weekly') NOT NULL DEFAULT 'manual',
last_run_at TIMESTAMP NULL,
last_status VARCHAR(20) NULL,
last_discovered INT NULL,
last_message VARCHAR(255) NULL,
created_by INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_dc_org_name (organization_id, name),
INDEX idx_dc_org (organization_id),
INDEX idx_dc_key (api_key_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
echo " + discovery_connectors OK\n";
$pdo->exec("CREATE TABLE IF NOT EXISTS discovery_runs (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
connector_id INT UNSIGNED NOT NULL,
organization_id INT NOT NULL,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
finished_at TIMESTAMP NULL,
status ENUM('running','ok','error') NOT NULL DEFAULT 'ok',
discovered_assets INT NOT NULL DEFAULT 0,
discovered_flows INT NOT NULL DEFAULT 0,
message VARCHAR(255) NULL,
INDEX idx_dr_conn (connector_id),
INDEX idx_dr_org (organization_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
echo " + discovery_runs OK\n";
$pdo->exec("CREATE TABLE IF NOT EXISTS network_flows (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
organization_id INT NOT NULL,
src VARCHAR(190) NOT NULL,
dst VARCHAR(190) NOT NULL,
port INT NULL,
protocol VARCHAR(12) NULL,
direction ENUM('internal','inbound','outbound') NOT NULL DEFAULT 'internal',
discovery_source VARCHAR(40) NOT NULL DEFAULT 'manual',
external_ref VARCHAR(190) NULL,
last_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_flow (organization_id, external_ref),
INDEX idx_flow_org (organization_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
echo " + network_flows OK\n";
echo "Migrazione 064 — connettori discovery OK. Prossima mig=065.\n";
@@ -0,0 +1,27 @@
<?php
/**
* migrate_065_institutional_documents.php — Documenti istituzionali per-org (dashboard). IDEMPOTENTE.
* Ticket #499. Esegui: docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_065_institutional_documents.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("CREATE TABLE IF NOT EXISTS institutional_documents (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
organization_id INT NOT NULL,
doc_key VARCHAR(60) NULL,
title VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
file_url VARCHAR(500) NULL,
is_standard TINYINT(1) NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
created_by INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_instdoc_org_key (organization_id, doc_key),
INDEX idx_instdoc_org (organization_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
echo " + institutional_documents OK\n";
echo "Migrazione 065 — documenti istituzionali OK. Prossima mig=066.\n";
@@ -0,0 +1,36 @@
<?php
/**
* migrate_066_requisito_risk_scoring.php — Ticket #500 (opzione B, matrice).
* org_requisito_state += likelihood TINYINT NULL, impact TINYINT NULL (scala 1-5).
* Serve a posizionare i rischi-da-requisito (RSK-<misura>Com.N) sulla matrice 5x5.
* Additiva e IDEMPOTENTE. Nessun dato esistente toccato (valori NULL = non ancora valutato).
*
* Eseguire (host): docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_066_requisito_risk_scoring.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function colExists(PDO $pdo, string $table, string $col): bool {
$st = $pdo->prepare(
"SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
);
$st->execute([$table, $col]);
return (bool) $st->fetchColumn();
}
$adds = [
'likelihood' => "ADD COLUMN likelihood TINYINT NULL COMMENT 'Probabilita 1-5 (rischio da non conformita del requisito)' AFTER valutazione_rischio",
'impact' => "ADD COLUMN impact TINYINT NULL COMMENT 'Impatto 1-5' AFTER likelihood",
];
foreach ($adds as $col => $clause) {
if (colExists($pdo, 'org_requisito_state', $col)) { echo " - $col gia presente, skip\n"; continue; }
$pdo->exec("ALTER TABLE org_requisito_state $clause");
echo " + aggiunta colonna org_requisito_state.$col\n";
}
echo "Migrazione 066 — org_requisito_state.likelihood/impact OK. Prossima mig=067.\n";
@@ -0,0 +1,64 @@
<?php
/**
* migrate_067_requisito_residual_and_actions.php — Integrazione "Modulo Azioni" (Simon), Fase 1.
* (1) org_requisito_state += residual_likelihood, residual_impact (TINYINT NULL, scala 1-5).
* (2) requisito_actions (azioni collegate al rischio-da-requisito).
* Additiva e IDEMPOTENTE. Nessun dato esistente toccato.
*
* Eseguire (host): docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_067_requisito_residual_and_actions.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
function colExists(PDO $pdo, string $table, string $col): bool {
$st = $pdo->prepare(
"SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
);
$st->execute([$table, $col]);
return (bool) $st->fetchColumn();
}
function tblExists(PDO $pdo, string $table): bool {
$st = $pdo->prepare(
"SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?"
);
$st->execute([$table]);
return (bool) $st->fetchColumn();
}
$adds = [
'residual_likelihood' => "ADD COLUMN residual_likelihood TINYINT NULL COMMENT 'Probabilita residua 1-5' AFTER impact",
'residual_impact' => "ADD COLUMN residual_impact TINYINT NULL COMMENT 'Impatto residuo 1-5' AFTER residual_likelihood",
];
foreach ($adds as $col => $clause) {
if (colExists($pdo, 'org_requisito_state', $col)) { echo " - $col gia presente, skip\n"; continue; }
$pdo->exec("ALTER TABLE org_requisito_state $clause");
echo " + aggiunta colonna org_requisito_state.$col\n";
}
if (tblExists($pdo, 'requisito_actions')) {
echo " - tabella requisito_actions gia presente, skip\n";
} else {
$pdo->exec(
"CREATE TABLE requisito_actions (
id INT AUTO_INCREMENT PRIMARY KEY,
organization_id INT NOT NULL,
requisito_id INT NOT NULL,
risk_code VARCHAR(64) NULL,
description TEXT NOT NULL,
status ENUM('bozza','in_corso','archiviata') NOT NULL DEFAULT 'bozza',
created_by INT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_reqact_org_req (organization_id, requisito_id, created_at),
INDEX idx_reqact_org (organization_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
echo " + creata tabella requisito_actions\n";
}
echo "Migrazione 067 — residuo + requisito_actions OK. Prossima mig=068.\n";
+168
View File
@@ -0,0 +1,168 @@
<?php
/**
* provision_testers.php — Client CLI sicuro per provisioning account tester/admin NIS2.
*
* DEVE girare DENTRO nis2-app (unico container che raggiunge nis2-db via TLS):
* ssh root@172.18.0.1 "docker exec [-e PROV_PASSWORD=...] nis2-app \
* php /var/www/nis2-agile/application/cli/provision_testers.php <args>"
*
* Sicurezza by design:
* - DRY-RUN di default: senza --apply NON scrive nulla, stampa solo il piano.
* - Target per email/cognome ESATTI (IN parametrico), mai UPDATE ciechi/LIKE.
* - --expect=N: se il match non risolve ESATTAMENTE N utenti -> ABORT (anti grant accidentale).
* - Password NON in argv: letta da env PROV_PASSWORD (non compare in `ps`).
* - password_version lasciato invariato -> la sync SSO non sovrascrive (vedi sso-password-sync.sh).
* - must_change_password=0; membership idempotente (UNIQUE user+org).
* - Tutte le scritture in UNA transazione (rollback on error). Nessun segreto hardcoded.
*
* Args:
* --match=Cognome1,Cognome2 cognomi da cercare (CSV) [opz.]
* --emails=a@b,c@d email esatte (CSV) [opz.]
* --role=super_admin users.role da impostare [opz.; se assente non tocca il ruolo]
* --orgs=151:primary,152 membership da garantire (org[:primary]) CSV [opz.]
* --set-password imposta password_hash da env PROV_PASSWORD [opz.]
* --expect=N n. atteso di utenti risolti (safety) [opz. ma consigliato]
* --apply esegue le scritture (default: dry-run)
*/
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE);
/* --- prime DB_* da PID1 (creds vault-injected non presenti nell'env di docker exec) --- */
$environ = @file_get_contents('/proc/1/environ');
if ($environ) {
foreach (explode("\0", $environ) as $kv) {
if ($kv === '' || strpos($kv, '=') === false) continue;
[$k, $v] = explode('=', $kv, 2);
if (strncmp($k, 'DB_', 3) === 0) { putenv("$k=$v"); $_ENV[$k] = $v; $_SERVER[$k] = $v; }
}
}
require_once '/var/www/nis2-agile/application/config/env.php';
require_once '/var/www/nis2-agile/application/config/database.php';
/* --- arg parsing --- */
$args = [];
foreach (array_slice($argv, 1) as $a) {
if (preg_match('/^--([^=]+)(?:=(.*))?$/', $a, $m)) $args[$m[1]] = $m[2] ?? true;
}
$matchNames = isset($args['match']) ? array_filter(array_map('trim', explode(',', $args['match']))) : [];
$emails = isset($args['emails']) ? array_filter(array_map('trim', explode(',', $args['emails']))) : [];
$role = $args['role'] ?? null;
$orgsRaw = isset($args['orgs']) ? array_filter(array_map('trim', explode(',', $args['orgs']))) : [];
$setPw = isset($args['set-password']);
$expect = isset($args['expect']) ? (int)$args['expect'] : null;
$apply = isset($args['apply']);
if (!$matchNames && !$emails) { fwrite(STDERR, "ERRORE: specifica --match e/o --emails\n"); exit(2); }
$validRoles = ['super_admin','org_admin','compliance_manager','board_member','auditor','employee','consultant'];
if ($role !== null && !in_array($role, $validRoles, true)) { fwrite(STDERR, "ERRORE: role '$role' non valido\n"); exit(2); }
$pw = null;
if ($setPw) {
$pw = getenv('PROV_PASSWORD') ?: '';
if ($pw === '') { fwrite(STDERR, "ERRORE: --set-password ma env PROV_PASSWORD vuota\n"); exit(2); }
}
/* parse orgs: ['151'=>true(primary), '152'=>false] */
$orgs = [];
foreach ($orgsRaw as $o) {
$parts = explode(':', $o);
$orgs[(int)$parts[0]] = (isset($parts[1]) && strtolower($parts[1]) === 'primary');
}
echo "==================================================================\n";
echo " provision_testers.php — " . ($apply ? "*** APPLY (scrive) ***" : "DRY-RUN (sola lettura)") . "\n";
echo "==================================================================\n";
/* --- risoluzione utenti (esatti) --- */
$conds = []; $params = [];
if ($matchNames) {
$like = [];
foreach ($matchNames as $n) { $like[] = 'full_name LIKE ?'; $params[] = '%' . $n . '%'; }
$conds[] = '(' . implode(' OR ', $like) . ')';
}
if ($emails) { $conds[] = 'email IN (' . implode(',', array_fill(0, count($emails), '?')) . ')'; $params = array_merge($params, $emails); }
$sql = "SELECT id,email,full_name,role,is_active,sso_identity_id,password_version,must_change_password
FROM users WHERE " . implode(' OR ', $conds) . " ORDER BY id";
$users = Database::fetchAll($sql, $params);
if (!$users) { fwrite(STDERR, "Nessun utente risolto.\n"); exit(1); }
echo "\nUTENTI RISOLTI (" . count($users) . "):\n";
foreach ($users as $u) {
$mem = Database::fetchAll(
"SELECT organization_id, role, is_primary FROM user_organizations WHERE user_id=?", [$u['id']]
);
$u['_mem'] = $mem;
echo sprintf(" #%-4d %-38s %-22s role=%-16s active=%s sso=%-5s pwver=%-3s mcp=%s orgs=%s\n",
$u['id'], $u['email'], $u['full_name'],
$u['role'], $u['is_active'] ?? '-', $u['sso_identity_id'] ?? '-', $u['password_version'] ?? '-',
$u['must_change_password'] ?? '-',
$mem ? implode(',', array_map(fn($m)=>$m['organization_id'].($m['is_primary']?'(P)':''), $mem)) : 'NESSUNA(!! onboarding)');
}
/* --- safety: expect --- */
if ($expect !== null && count($users) !== $expect) {
fwrite(STDERR, "\nABORT: risolti " . count($users) . " utenti ma --expect=$expect. Niente modifiche.\n");
exit(3);
}
/* --- piano --- */
echo "\nPIANO:\n";
echo $role ? " - role -> '$role' (dove diverso)\n" : " - role: invariato\n";
echo $setPw ? " - password_hash -> (da PROV_PASSWORD), password_version INVARIATO, must_change_password=0\n" : " - password: invariata\n";
echo $orgs ? " - membership garantita su org: " . implode(', ', array_map(fn($k)=>$k.($orgs[$k]?':primary':''), array_keys($orgs))) . " (role org_admin, idempotente)\n" : " - membership: invariata\n";
if (!$apply) { echo "\n[DRY-RUN] nessuna scrittura. Aggiungi --apply per eseguire.\n"; exit(0); }
/* --- APPLY (transazione) --- */
$pwHash = $setPw ? password_hash($pw, PASSWORD_DEFAULT) : null;
$changes = [];
try {
Database::beginTransaction();
foreach ($users as $u) {
$uid = (int)$u['id']; $line = [];
if ($role !== null && $u['role'] !== $role) {
Database::update('users', ['role'=>$role], 'id=?', [$uid]); $line[] = "role {$u['role']}->$role";
}
if ((int)$u['is_active'] !== 1) {
Database::update('users', ['is_active'=>1], 'id=?', [$uid]); $line[] = "is_active 0->1";
}
if ($setPw) {
Database::update('users', ['password_hash'=>$pwHash, 'must_change_password'=>0], 'id=?', [$uid]); $line[] = "password set (ver invariato)";
}
foreach ($orgs as $oid => $isPrimary) {
// idempotente: UNIQUE (user_id, organization_id)
Database::query(
"INSERT INTO user_organizations (user_id, organization_id, role, is_primary)
VALUES (?,?, 'org_admin', ?)
ON DUPLICATE KEY UPDATE role=VALUES(role), is_primary=VALUES(is_primary)",
[$uid, (int)$oid, $isPrimary ? 1 : 0]
);
$line[] = "org $oid".($isPrimary?'(P)':'');
}
$changes[$uid] = $line ? implode('; ', $line) : 'nessuna modifica (gia conforme)';
}
Database::commit();
} catch (\Throwable $e) {
Database::rollback();
fwrite(STDERR, "\nROLLBACK — errore: " . $e->getMessage() . "\n");
exit(4);
}
echo "\nAPPLICATO (commit ok):\n";
foreach ($changes as $uid => $c) echo " #$uid: $c\n";
/* --- verifica post --- */
echo "\nVERIFICA POST:\n";
$ids = implode(',', array_map(fn($u)=>(int)$u['id'], $users));
$after = Database::fetchAll("SELECT id,email,role,is_active,must_change_password,
(SELECT COUNT(*) FROM user_organizations uo WHERE uo.user_id=users.id) AS org_count
FROM users WHERE id IN ($ids) ORDER BY id");
foreach ($after as $a) {
echo sprintf(" #%-4d %-38s role=%-14s active=%s mcp=%s org_count=%d %s\n",
$a['id'], $a['email'], $a['role'], $a['is_active'], $a['must_change_password'], $a['org_count'],
$a['org_count'] > 0 ? 'OK (no onboarding)' : '!! ANCORA 0 ORG');
}
echo "\nFatto.\n";
+121
View File
@@ -0,0 +1,121 @@
<?php
/**
* NIS2 Agile — Importer idempotente del framework canonico (Epic C / C1).
* ----------------------------------------------------------------------------
* Crea (se mancanti) le tabelle di configurazione cfg_nis2_* (mig.046) e le
* popola dal seed autoritativo application/data/nis2_framework_seed.json
* (generato dai file docs/simon/*.xlsx — codifica NON variabile).
*
* Idempotente: CREATE TABLE IF NOT EXISTS + INSERT ... ON DUPLICATE KEY UPDATE.
* Rilanciabile senza duplicare né rompere le FK. Pensato per ricreare/riseedare
* un ambiente in modo versionato (sostituisce i runner temporanei usa-e-getta).
*
* Uso (dentro il container app):
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_framework.php
*
* NB: solo CLI. DB API via PDO singleton (Database::getInstance()), TLS gestito
* dalla config dell'app.
*/
if (PHP_SAPI !== 'cli') {
http_response_code(403);
exit("Solo da CLI.\n");
}
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
$ddl = [
"CREATE TABLE IF NOT EXISTS cfg_nis2_ambiti (
nist_code VARCHAR(16) NOT NULL, nist_descr TEXT NOT NULL,
PRIMARY KEY (nist_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS cfg_nis2_procedure (
proc_code VARCHAR(16) NOT NULL, proc_descr TEXT NOT NULL,
PRIMARY KEY (proc_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS cfg_nis2_rischi (
risk_code VARCHAR(16) NOT NULL, risk_descr TEXT NOT NULL,
PRIMARY KEY (risk_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS cfg_nis2_misure (
misura_code VARCHAR(16) NOT NULL, nist_code VARCHAR(16) NOT NULL,
area_politica VARCHAR(255) NOT NULL, misura_descr TEXT NOT NULL, ord INT NOT NULL,
applies_important TINYINT(1) NOT NULL DEFAULT 1, applies_essential TINYINT(1) NOT NULL DEFAULT 1,
PRIMARY KEY (misura_code), KEY idx_misura_nist (nist_code), KEY idx_misura_ord (ord),
CONSTRAINT fk_misura_ambito FOREIGN KEY (nist_code) REFERENCES cfg_nis2_ambiti (nist_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS cfg_nis2_requisiti (
id INT NOT NULL AUTO_INCREMENT, n INT NULL, misura_code VARCHAR(16) NOT NULL,
requisito_code VARCHAR(32) NOT NULL, requisito_descr TEXT NOT NULL,
proc_code VARCHAR(16) NULL, risk_code VARCHAR(16) NULL,
applies_important TINYINT(1) NOT NULL DEFAULT 0, applies_essential TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id), UNIQUE KEY uq_req (misura_code, requisito_code),
KEY idx_req_misura (misura_code), KEY idx_req_proc (proc_code), KEY idx_req_risk (risk_code),
CONSTRAINT fk_req_misura FOREIGN KEY (misura_code) REFERENCES cfg_nis2_misure (misura_code),
CONSTRAINT fk_req_proc FOREIGN KEY (proc_code) REFERENCES cfg_nis2_procedure (proc_code),
CONSTRAINT fk_req_risk FOREIGN KEY (risk_code) REFERENCES cfg_nis2_rischi (risk_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($ddl as $stmt) {
try { $pdo->exec($stmt); }
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
}
$seedPath = __DIR__ . '/../data/nis2_framework_seed.json';
$seed = json_decode(@file_get_contents($seedPath), true);
if (!$seed) { fwrite(STDERR, "SEED non leggibile: $seedPath\n"); exit(1); }
$pdo->beginTransaction();
try {
$st = $pdo->prepare("INSERT INTO cfg_nis2_ambiti (nist_code,nist_descr) VALUES (?,?)
ON DUPLICATE KEY UPDATE nist_descr=VALUES(nist_descr)");
foreach ($seed['ambiti_nist'] as $a) { $st->execute([$a['nist_code'], $a['nist_descr']]); }
$st = $pdo->prepare("INSERT INTO cfg_nis2_procedure (proc_code,proc_descr) VALUES (?,?)
ON DUPLICATE KEY UPDATE proc_descr=VALUES(proc_descr)");
foreach ($seed['procedure'] as $p) { $st->execute([$p['proc_code'], $p['proc_descr']]); }
$st = $pdo->prepare("INSERT INTO cfg_nis2_rischi (risk_code,risk_descr) VALUES (?,?)
ON DUPLICATE KEY UPDATE risk_descr=VALUES(risk_descr)");
foreach ($seed['rischi'] as $r) { $st->execute([$r['risk_code'], $r['risk_descr']]); }
$st = $pdo->prepare("INSERT INTO cfg_nis2_misure
(misura_code,nist_code,area_politica,misura_descr,ord,applies_important,applies_essential)
VALUES (?,?,?,?,?,?,?)
ON DUPLICATE KEY UPDATE nist_code=VALUES(nist_code),area_politica=VALUES(area_politica),
misura_descr=VALUES(misura_descr),ord=VALUES(ord),
applies_important=VALUES(applies_important),applies_essential=VALUES(applies_essential)");
foreach ($seed['misure'] as $m) {
$st->execute([$m['misura_code'], $m['nist_code'], $m['area_politica'], $m['misura_descr'],
$m['ord'], $m['applies_important'], $m['applies_essential']]);
}
$st = $pdo->prepare("INSERT INTO cfg_nis2_requisiti
(n,misura_code,requisito_code,requisito_descr,proc_code,risk_code,applies_important,applies_essential)
VALUES (?,?,?,?,?,?,?,?)
ON DUPLICATE KEY UPDATE n=VALUES(n),requisito_descr=VALUES(requisito_descr),
proc_code=VALUES(proc_code),risk_code=VALUES(risk_code),
applies_important=VALUES(applies_important),applies_essential=VALUES(applies_essential)");
foreach ($seed['requisiti'] as $q) {
$st->execute([$q['n'], $q['misura_code'], $q['requisito_code'], $q['requisito_descr'],
$q['proc_code'], $q['risk_code'], $q['applies_important'], $q['applies_essential']]);
}
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
fwrite(STDERR, "SEED FALLITO: " . $e->getMessage() . "\n");
exit(1);
}
$counts = [
'ambiti' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_ambiti")->fetchColumn(),
'procedure' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_procedure")->fetchColumn(),
'rischi' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_rischi")->fetchColumn(),
'misure' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_misure")->fetchColumn(),
'requisiti' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_requisiti")->fetchColumn(),
];
echo "OK seed-framework — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
+87
View File
@@ -0,0 +1,87 @@
<?php
/**
* NIS2 Agile — DDL idempotente Audit interni (Modulo A, mig.055).
* ----------------------------------------------------------------------------
* Crea (se mancanti) le tabelle del modulo Audit interni (ISO 27001 §9.2):
* - internal_audits (programma/sessioni di audit)
* - internal_audit_items (checklist: clausole 4-10, Annex A, NIS2, custom)
* NESSUN dato di sistema: le checklist sono pre-popolate per-org dal controller
* al create (clausole statiche + Annex A dal SoA dell'org).
*
* Idempotente: CREATE TABLE IF NOT EXISTS (re-eseguibile senza errore).
* Allineato a docs/sql/055_internal_audits.sql.
*
* Uso (dentro il container app):
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_internal_audits.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
$ddl = [
"CREATE TABLE IF NOT EXISTS internal_audits (
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
code VARCHAR(20) NULL, title VARCHAR(255) NOT NULL,
scope TEXT NULL, criteria TEXT NULL,
planned_date DATE NULL, executed_date DATE NULL,
status ENUM('planned','in_progress','completed','cancelled') NOT NULL DEFAULT 'planned',
lead_auditor_user_id INT NULL, lead_auditor_role_id INT NULL,
conclusion TEXT NULL, created_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id), KEY idx_intaud_org (organization_id),
CONSTRAINT fk_intaud_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
CONSTRAINT fk_intaud_lead_user FOREIGN KEY (lead_auditor_user_id) REFERENCES users (id) ON DELETE SET NULL,
CONSTRAINT fk_intaud_lead_role FOREIGN KEY (lead_auditor_role_id) REFERENCES org_roles (id) ON DELETE SET NULL,
CONSTRAINT fk_intaud_creator FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS internal_audit_items (
id INT NOT NULL AUTO_INCREMENT, audit_id INT NOT NULL,
ref_type ENUM('clause','annex_control','nis2_measure','custom') NOT NULL DEFAULT 'clause',
ref_code VARCHAR(32) NULL, checkpoint TEXT NOT NULL,
result ENUM('da_verificare','conforme','non_conforme','osservazione','opportunita','non_applicabile') NOT NULL DEFAULT 'da_verificare',
note TEXT NULL, ord INT NOT NULL DEFAULT 0,
PRIMARY KEY (id), KEY idx_intauditem_audit (audit_id),
CONSTRAINT fk_intauditem_audit FOREIGN KEY (audit_id) REFERENCES internal_audits (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($ddl as $stmt) {
try { $pdo->exec($stmt); }
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
}
// Estende l'ENUM del calendario (review_schedule) con 'internal_audit' così la
// data pianificata dell'audit compare nel calendario NIS2. Idempotente in effetto.
// Include i valori già introdotti da seeder precedenti (stakeholder_activity) per
// non regredirli; se 'internal_audit' è già presente l'ALTER è un no-op.
try {
$cur = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'review_schedule' AND COLUMN_NAME = 'entity_type'")->fetchColumn();
if (!str_contains($cur, "'internal_audit'")) {
$vals = ['role','skill','inventory','procedure','risk','supplier','measure','custom','stakeholder_activity','internal_audit'];
// conserva eventuali valori extra già presenti nell'ENUM corrente
if (preg_match_all("/'([^']+)'/", $cur, $m)) {
foreach ($m[1] as $v) { if (!in_array($v, $vals, true)) { $vals[] = $v; } }
}
$enum = "'" . implode("','", $vals) . "'";
$pdo->exec("ALTER TABLE review_schedule MODIFY COLUMN entity_type ENUM($enum) NOT NULL");
}
} catch (PDOException $e) {
fwrite(STDERR, "WARN ALTER review_schedule: " . $e->getMessage() . "\n");
}
$counts = [];
foreach (['internal_audits', 'internal_audit_items'] as $t) {
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
}
$enum = $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'review_schedule' AND COLUMN_NAME = 'entity_type'")->fetchColumn();
echo "OK seed-internal-audits — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
echo "review_schedule.entity_type has internal_audit: " . (str_contains((string) $enum, 'internal_audit') ? 'YES' : 'NO') . "\n";
@@ -0,0 +1,77 @@
<?php
/**
* NIS2 Agile — DDL idempotente Riesame di Direzione (ISO 27001 §9.3, mig.056).
* ----------------------------------------------------------------------------
* Crea (se mancanti) le tabelle del Modulo B "Riesame di Direzione":
* - management_reviews (il verbale: input aggregati congelati + esiti)
* - management_review_decisions (decisioni/output → owner ruolo, scadenza, CAPA)
*
* NESSUN dato di sistema: i riesami sono per-org (creati dall'utente).
*
* Idempotente: CREATE TABLE IF NOT EXISTS (re-eseguibile senza errore; in caso
* di tabella già presente ignora gli errno 1050/1061). Allineato a
* docs/sql/056_management_reviews.sql.
*
* Uso (dentro il container app, o sull'host con la stessa connessione PDO):
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_management_reviews.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
$ddl = [
"CREATE TABLE IF NOT EXISTS management_reviews (
id INT NOT NULL AUTO_INCREMENT,
organization_id INT NOT NULL,
code VARCHAR(20) NULL,
review_date DATE NULL,
period_label VARCHAR(100) NULL,
chair_user_id INT NULL,
attendees JSON NULL,
status ENUM('draft','approved') NOT NULL DEFAULT 'draft',
approved_by INT NULL,
approved_at DATETIME NULL,
snapshot JSON NULL,
conclusions TEXT NULL,
created_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_mgr_org (organization_id),
CONSTRAINT fk_mgr_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
CONSTRAINT fk_mgr_chair FOREIGN KEY (chair_user_id) REFERENCES users (id) ON DELETE SET NULL,
CONSTRAINT fk_mgr_approved FOREIGN KEY (approved_by) REFERENCES users (id) ON DELETE SET NULL,
CONSTRAINT fk_mgr_creator FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS management_review_decisions (
id INT NOT NULL AUTO_INCREMENT,
review_id INT NOT NULL,
decision TEXT NOT NULL,
owner_role_id INT NULL,
due_date DATE NULL,
status ENUM('open','in_progress','done') NOT NULL DEFAULT 'open',
capa_id INT NULL,
ord INT NOT NULL DEFAULT 0,
PRIMARY KEY (id),
KEY idx_mrd_review (review_id),
CONSTRAINT fk_mrd_review FOREIGN KEY (review_id) REFERENCES management_reviews (id) ON DELETE CASCADE,
CONSTRAINT fk_mrd_owner FOREIGN KEY (owner_role_id) REFERENCES org_roles (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($ddl as $stmt) {
try { $pdo->exec($stmt); }
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
}
$counts = [];
foreach (['management_reviews', 'management_review_decisions'] as $t) {
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
}
echo "OK seed-management-reviews — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
@@ -0,0 +1,74 @@
<?php
/** DDL idempotente Controlli periodici (mig.057). Crea le tabelle ed estende
* l'ENUM non_conformities.source con 'monitoring' (preservando i valori).
* Uso: docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_periodic_controls.php */
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
$ddl = [
"CREATE TABLE IF NOT EXISTS periodic_controls (
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
code VARCHAR(20) NULL, title VARCHAR(255) NOT NULL, description TEXT NULL, category VARCHAR(100) NULL,
control_ref VARCHAR(32) NULL, owner_role_id INT NULL,
frequency ENUM('giornaliero','settimanale','mensile','trimestrale','semestrale','annuale','custom') NOT NULL DEFAULT 'mensile',
frequency_days INT NULL, method TEXT NULL, next_due_date DATE NULL, last_executed_at DATE NULL,
status ENUM('active','suspended') NOT NULL DEFAULT 'active', created_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id), UNIQUE KEY uk_pctl_code (organization_id, code),
KEY idx_pctl_org (organization_id), KEY idx_pctl_due (organization_id, next_due_date),
CONSTRAINT fk_pctl_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
CONSTRAINT fk_pctl_role FOREIGN KEY (owner_role_id) REFERENCES org_roles (id) ON DELETE SET NULL,
CONSTRAINT fk_pctl_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS periodic_control_executions (
id INT NOT NULL AUTO_INCREMENT, control_id INT NOT NULL, executed_at DATE NOT NULL, executed_by INT NULL,
outcome ENUM('conforme','non_conforme','parziale','non_applicabile') NOT NULL DEFAULT 'conforme',
notes TEXT NULL, ncr_id INT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id), KEY idx_pcexec_control (control_id),
CONSTRAINT fk_pcexec_control FOREIGN KEY (control_id) REFERENCES periodic_controls (id) ON DELETE CASCADE,
CONSTRAINT fk_pcexec_user FOREIGN KEY (executed_by) REFERENCES users (id) ON DELETE SET NULL,
CONSTRAINT fk_pcexec_ncr FOREIGN KEY (ncr_id) REFERENCES non_conformities (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($ddl as $stmt) {
try { $pdo->exec($stmt); }
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
}
// UNIQUE (organization_id, code) idempotente — la tabella su prod può preesistere senza
// l'indice (creata prima del fix). Anti-doppione su CTL-NNN concorrenti (vedi retry in create()).
try { $pdo->exec("ALTER TABLE periodic_controls ADD UNIQUE KEY uk_pctl_code (organization_id, code)"); }
catch (PDOException $e) {
$c = $e->errorInfo[1] ?? 0;
if ($c === 1062) { fwrite(STDERR, "WARN uk_pctl_code: doppioni esistenti, indice NON aggiunto.\n"); }
elseif (!in_array($c, [1061], true)) { throw $e; } // 1061 = indice già presente
}
// Estende non_conformities.source con 'monitoring' preservando i valori esistenti.
try {
$cur = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'non_conformities' AND COLUMN_NAME = 'source'")->fetchColumn();
if (stripos($cur, "'monitoring'") === false) {
$vals = [];
if (preg_match_all("/'([^']+)'/", $cur, $m)) { $vals = $m[1]; }
if (!in_array('monitoring', $vals, true)) { $vals[] = 'monitoring'; }
if (!$vals) { $vals = ['assessment','audit','incident','supplier_review','management_review','external_audit','monitoring','other']; }
$enum = implode(',', array_map(fn($v) => "'" . $v . "'", $vals));
$pdo->exec("ALTER TABLE non_conformities MODIFY COLUMN source ENUM($enum) NOT NULL DEFAULT 'assessment'");
}
} catch (PDOException $e) {
fwrite(STDERR, "WARN ALTER non_conformities.source: " . $e->getMessage() . "\n");
}
$counts = [];
foreach (['periodic_controls', 'periodic_control_executions'] as $t) {
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
}
$src = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'non_conformities' AND COLUMN_NAME = 'source'")->fetchColumn();
echo "OK seed-periodic-controls — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
echo "non_conformities.source has monitoring: " . (str_contains($src, 'monitoring') ? 'YES' : 'NO') . "\n";
@@ -0,0 +1,141 @@
<?php
/**
* NIS2 Agile — DDL idempotente Attività stakeholder (Epic C / C5.2a, mig.052).
* ----------------------------------------------------------------------------
* Crea (se mancanti) le tabelle del modulo Attività stakeholder ed estende
* l'ENUM entity_type di review_schedule con 'stakeholder_activity' (calendario NIS2).
* NESSUN dato di sistema: i questionari tipo sono per-org.
*
* Idempotente: CREATE TABLE IF NOT EXISTS + ALTER MODIFY (re-eseguibile senza
* errore). Allineato a docs/sql/052_stakeholder_activities.sql.
*
* Uso (dentro il container app):
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_stakeholder_activities.php
*/
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
$ddl = [
"CREATE TABLE IF NOT EXISTS stk_questionnaire_templates (
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
name VARCHAR(255) NOT NULL, kind ENUM('questionnaire','read_ack') NOT NULL DEFAULT 'questionnaire',
description TEXT NULL, content TEXT NULL, questions JSON NULL,
status ENUM('draft','active','archived') NOT NULL DEFAULT 'active',
created_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id), KEY idx_stkqt_org (organization_id),
CONSTRAINT fk_stkqt_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
CONSTRAINT fk_stkqt_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stk_template_procedures (
template_id INT NOT NULL, policy_id INT NOT NULL,
PRIMARY KEY (template_id, policy_id), KEY idx_stktp_policy (policy_id),
CONSTRAINT fk_stktp_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE,
CONSTRAINT fk_stktp_policy FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stk_template_misure (
template_id INT NOT NULL, misura_code VARCHAR(16) NOT NULL,
PRIMARY KEY (template_id, misura_code), KEY idx_stktm_misura (misura_code),
CONSTRAINT fk_stktm_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE,
CONSTRAINT fk_stktm_misura FOREIGN KEY (misura_code) REFERENCES cfg_nis2_misure (misura_code) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stk_template_requisiti (
template_id INT NOT NULL, requisito_id INT NOT NULL,
PRIMARY KEY (template_id, requisito_id), KEY idx_stktr_req (requisito_id),
CONSTRAINT fk_stktr_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE,
CONSTRAINT fk_stktr_req FOREIGN KEY (requisito_id) REFERENCES cfg_nis2_requisiti (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stk_activities (
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
title VARCHAR(255) NOT NULL, type ENUM('questionnaire','read_ack','action') NOT NULL DEFAULT 'questionnaire',
template_id INT NULL, description TEXT NULL,
assign_mode ENUM('by_code','individual') NOT NULL DEFAULT 'individual', stak_code VARCHAR(16) NULL,
planned_date DATE NULL, due_date DATE NULL,
status ENUM('draft','scheduled','sent','in_progress','completed','cancelled') NOT NULL DEFAULT 'draft',
created_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id), KEY idx_stka_org (organization_id), KEY idx_stka_tpl (template_id),
CONSTRAINT fk_stka_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
CONSTRAINT fk_stka_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE SET NULL,
CONSTRAINT fk_stka_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stk_activity_targets (
id INT NOT NULL AUTO_INCREMENT, activity_id INT NOT NULL, stakeholder_id INT NOT NULL,
access_token_hash CHAR(64) NULL,
state ENUM('pending','sent','responded','acknowledged','expired') NOT NULL DEFAULT 'pending',
sent_at DATETIME NULL, responded_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id), UNIQUE KEY uq_stkat (activity_id, stakeholder_id),
KEY idx_stkat_stk (stakeholder_id), KEY idx_stkat_token (access_token_hash),
CONSTRAINT fk_stkat_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE,
CONSTRAINT fk_stkat_stk FOREIGN KEY (stakeholder_id) REFERENCES stakeholders (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stk_activity_procedures (
activity_id INT NOT NULL, policy_id INT NOT NULL,
PRIMARY KEY (activity_id, policy_id), KEY idx_stkap_policy (policy_id),
CONSTRAINT fk_stkap_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE,
CONSTRAINT fk_stkap_policy FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// C5.2b (mig.053) — feedback: risposte + commenti
"CREATE TABLE IF NOT EXISTS stk_activity_responses (
id INT NOT NULL AUTO_INCREMENT, target_id INT NOT NULL,
answers JSON NULL, acknowledged_at DATETIME NULL,
respondent_name VARCHAR(255) NULL, respondent_email VARCHAR(255) NULL, ip_address VARCHAR(45) NULL,
submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id), UNIQUE KEY uq_stkar_target (target_id),
CONSTRAINT fk_stkar_target FOREIGN KEY (target_id) REFERENCES stk_activity_targets (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stk_activity_comments (
id INT NOT NULL AUTO_INCREMENT, activity_id INT NOT NULL, body TEXT NOT NULL,
author_kind ENUM('internal','external') NOT NULL DEFAULT 'internal',
author_user_id INT NULL, author_label VARCHAR(255) NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id), KEY idx_stkac_act (activity_id),
CONSTRAINT fk_stkac_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE,
CONSTRAINT fk_stkac_user FOREIGN KEY (author_user_id) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($ddl as $stmt) {
try { $pdo->exec($stmt); }
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
}
// Estende l'ENUM del calendario (review_schedule). Idempotente in effetto.
try {
$pdo->exec("ALTER TABLE review_schedule
MODIFY COLUMN entity_type ENUM('role','skill','inventory','procedure','risk','supplier','measure','custom','stakeholder_activity') NOT NULL");
} catch (PDOException $e) {
fwrite(STDERR, "WARN ALTER review_schedule: " . $e->getMessage() . "\n");
}
// mig.054 — scadenza magic-link (idempotente: ignora 1060 colonna già esistente).
try {
$pdo->exec("ALTER TABLE stk_activity_targets ADD COLUMN token_expires_at DATETIME NULL DEFAULT NULL AFTER access_token_hash");
} catch (PDOException $e) {
if (($e->errorInfo[1] ?? 0) !== 1060) { fwrite(STDERR, "WARN ALTER stk_activity_targets: " . $e->getMessage() . "\n"); }
}
$counts = [];
foreach (['stk_questionnaire_templates','stk_template_procedures','stk_template_misure','stk_template_requisiti',
'stk_activities','stk_activity_targets','stk_activity_procedures',
'stk_activity_responses','stk_activity_comments'] as $t) {
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
}
$enum = $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'review_schedule' AND COLUMN_NAME = 'entity_type'")->fetchColumn();
echo "OK seed-stakeholder-activities — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
echo "review_schedule.entity_type has stakeholder_activity: " . (str_contains((string) $enum, 'stakeholder_activity') ? 'YES' : 'NO') . "\n";
+151
View File
@@ -0,0 +1,151 @@
<?php
/**
* NIS2 Agile — Importer idempotente del registro Stakeholder (Epic C / C5.1).
* ----------------------------------------------------------------------------
* Crea (se mancanti) le tabelle del modulo Stakeholder (mig.051) e popola i dati
* di SISTEMA: i 4 quadranti della matrice di Mendelow (range corretti, Q4 potere
* 0-2) e i 30 tipi di stakeholder Stak.01-30 (6 Interni / 24 Esterni) dal seed
* autoritativo application/data/nis2_framework_seed.json (chiave stakeholder_types,
* generata dai file docs/simon/*.xlsx — codifica NON variabile).
*
* Idempotente: CREATE TABLE IF NOT EXISTS + INSERT ... ON DUPLICATE KEY UPDATE
* sul PK `code` (nessuna trappola NULL-dedupe: il codice E' la chiave primaria).
* Rilanciabile senza duplicare ne' rompere le FK. Sostituisce i runner usa-e-getta.
*
* NB: la matrice di Mendelow (potere/interesse) e' BUONA PRASSI, NON un obbligo
* NIS2; l'obbligo e' GV.SC-02 (ruoli/responsabilita verso fornitori/clienti/partner).
*
* Uso (dentro il container app):
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_stakeholders.php
*
* NB: solo CLI. DB API via PDO singleton (Database::getInstance()), TLS gestito
* dalla config dell'app.
*/
if (PHP_SAPI !== 'cli') {
http_response_code(403);
exit("Solo da CLI.\n");
}
require_once __DIR__ . '/../config/env.php';
require_once __DIR__ . '/../config/database.php';
$pdo = Database::getInstance();
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
// --- DDL (idempotente, FK dentro il CREATE; allineato a docs/sql/051_*.sql) ---
$ddl = [
"CREATE TABLE IF NOT EXISTS cfg_stakeholder_types (
code VARCHAR(16) NOT NULL, organization_id INT NULL,
tipo ENUM('Interno','Esterno') NOT NULL, descr TEXT NOT NULL, ord INT NOT NULL DEFAULT 0,
created_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (code), KEY idx_cfg_stk_type_org (organization_id),
CONSTRAINT fk_cfg_stk_type_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
CONSTRAINT fk_cfg_stk_type_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS cfg_stakeholder_quadrants (
code VARCHAR(4) NOT NULL, organization_id INT NULL,
label VARCHAR(64) NOT NULL, strategy TEXT NULL,
power_min TINYINT NOT NULL, power_max TINYINT NOT NULL,
interest_min TINYINT NOT NULL, interest_max TINYINT NOT NULL, ord INT NOT NULL DEFAULT 0,
PRIMARY KEY (code), KEY idx_cfg_stk_quad_org (organization_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stakeholders (
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
stak_code VARCHAR(16) NOT NULL, name VARCHAR(255) NOT NULL,
org_role_id INT NULL, supplier_id INT NULL,
power TINYINT NULL, interest TINYINT NULL,
contact_name VARCHAR(255) NULL, contact_email VARCHAR(255) NULL, notes TEXT NULL,
created_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_stk_org (organization_id), KEY idx_stk_org_code (organization_id, stak_code),
KEY idx_stk_role (org_role_id), KEY idx_stk_supplier (supplier_id),
CONSTRAINT fk_stk_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
CONSTRAINT fk_stk_code FOREIGN KEY (stak_code) REFERENCES cfg_stakeholder_types (code),
CONSTRAINT fk_stk_role FOREIGN KEY (org_role_id) REFERENCES org_roles (id) ON DELETE SET NULL,
CONSTRAINT fk_stk_supplier FOREIGN KEY (supplier_id) REFERENCES suppliers (id) ON DELETE SET NULL,
CONSTRAINT fk_stk_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS stakeholder_procedures (
stakeholder_id INT NOT NULL, policy_id INT NOT NULL,
PRIMARY KEY (stakeholder_id, policy_id), KEY idx_stk_proc_policy (policy_id),
CONSTRAINT fk_stk_proc_stk FOREIGN KEY (stakeholder_id) REFERENCES stakeholders (id) ON DELETE CASCADE,
CONSTRAINT fk_stk_proc_policy FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($ddl as $stmt) {
try { $pdo->exec($stmt); }
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
}
// --- Dati di sistema ---
$seedPath = __DIR__ . '/../data/nis2_framework_seed.json';
$seed = json_decode(@file_get_contents($seedPath), true);
if (!$seed || empty($seed['stakeholder_types'])) {
fwrite(STDERR, "SEED non leggibile o privo di stakeholder_types: $seedPath\n");
exit(1);
}
// 4 quadranti di sistema — matrice di Mendelow (x=interesse, y=potere).
// REFUSO corretto: Q4 (basso-sinistra, Monitorare) ha potere 0-2 (non 3-5).
$quadrants = [
// code, label, strategy, power_min, power_max, interest_min, interest_max, ord
['Q1', 'Tenere soddisfatti (Keep Satisfied)',
'Stakeholder istituzionali: forte potere di blocco, scarso interesse quotidiano. Tenerli soddisfatti ed evitare scontenti improvvisi.',
3, 5, 0, 2, 1],
['Q2', 'Gestire attivamente (Manage Closely)',
'Stakeholder chiave: decisori principali, supporto vitale. Gestione attiva e coinvolgimento costante.',
3, 5, 3, 5, 2],
['Q3', 'Tenere informati (Keep Informed)',
'Stakeholder operativi: molto coinvolti ma scarso peso decisionale. Tenerli informati.',
0, 2, 3, 5, 3],
['Q4', 'Monitorare (Monitor)',
'Stakeholder marginali: basso potere e basso interesse. Monitoraggio costante col minimo sforzo, comunicazioni periodiche non dispendiose.',
0, 2, 0, 2, 4],
];
$pdo->beginTransaction();
try {
$stQ = $pdo->prepare(
"INSERT INTO cfg_stakeholder_quadrants
(code,label,strategy,power_min,power_max,interest_min,interest_max,ord,organization_id)
VALUES (?,?,?,?,?,?,?,?,NULL)
ON DUPLICATE KEY UPDATE label=VALUES(label),strategy=VALUES(strategy),
power_min=VALUES(power_min),power_max=VALUES(power_max),
interest_min=VALUES(interest_min),interest_max=VALUES(interest_max),ord=VALUES(ord)"
);
foreach ($quadrants as $q) { $stQ->execute($q); }
// 30 tipi di sistema: code PK, organization_id NULL, tipo Interno/Esterno, descr verbatim.
$stT = $pdo->prepare(
"INSERT INTO cfg_stakeholder_types (code,organization_id,tipo,descr,ord)
VALUES (?,NULL,?,?,?)
ON DUPLICATE KEY UPDATE tipo=VALUES(tipo),descr=VALUES(descr),ord=VALUES(ord)"
);
$ord = 0;
foreach ($seed['stakeholder_types'] as $t) {
$ord++;
$tipo = ($t['tipo'] === 'Interno') ? 'Interno' : 'Esterno';
$stT->execute([$t['code'], $tipo, $t['descr'], $ord]);
}
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
fwrite(STDERR, "SEED FALLITO: " . $e->getMessage() . "\n");
exit(1);
}
$counts = [
'quadranti' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_stakeholder_quadrants WHERE organization_id IS NULL")->fetchColumn(),
'tipi_sistema' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_stakeholder_types WHERE organization_id IS NULL")->fetchColumn(),
'tipi_interni' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_stakeholder_types WHERE organization_id IS NULL AND tipo='Interno'")->fetchColumn(),
'tipi_esterni' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_stakeholder_types WHERE organization_id IS NULL AND tipo='Esterno'")->fetchColumn(),
];
echo "OK seed-stakeholders — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
+18
View File
@@ -103,6 +103,24 @@ define('ANTHROPIC_API_KEY', Env::get('ANTHROPIC_API_KEY', ''));
define('ANTHROPIC_MODEL', Env::get('ANTHROPIC_MODEL', 'claude-sonnet-4-5-20250929')); define('ANTHROPIC_MODEL', Env::get('ANTHROPIC_MODEL', 'claude-sonnet-4-5-20250929'));
define('ANTHROPIC_MAX_TOKENS', Env::int('ANTHROPIC_MAX_TOKENS', 4096)); define('ANTHROPIC_MAX_TOKENS', Env::int('ANTHROPIC_MAX_TOKENS', 4096));
// ═══════════════════════════════════════════════════════════════════════════
// VOCEA (Whistleblowing zero-knowledge — integrazione, DISABILITATA di default)
// ═══════════════════════════════════════════════════════════════════════════
// SCAFFOLD DORMIENTE: con VOCEA_ENABLED=false (default) VoceaService non effettua
// nessuna chiamata di rete. Attivazione a MS live: applicare migrate_063 +
// VOCEA_ENABLED=true + VOCEA_API_KEY (vault tier1__nis2-app__vocea__*) +
// slug del canale su organizations.vocea_tenant_slug (status='active').
define('VOCEA_ENABLED', filter_var(Env::get('VOCEA_ENABLED', 'false'), FILTER_VALIDATE_BOOLEAN));
define('VOCEA_BASE_URL', Env::get('VOCEA_BASE_URL', 'https://api.vocea.cloud/api/wb/v1')); // API integratori (X-API-Key)
define('VOCEA_API_KEY', Env::get('VOCEA_API_KEY', '')); // wbk_... per-tenant (da vault)
define('VOCEA_PORTAL_URL', Env::get('VOCEA_PORTAL_URL', 'https://app.vocea.cloud')); // portale embed via wb-link.js
// ═══════════════════════════════════════════════════════════════════════════
// VOICE / TTS (ARIA voce naturale — proxy same-origin a nexus-voice-ms AgileHub)
// ═══════════════════════════════════════════════════════════════════════════
define('VOICE_MS_URL', Env::get('VOICE_MS_URL', 'http://172.21.0.1:4215/tts/speak')); // gateway nis2-network → host:4215
define('TTS_VOICE_ID', Env::get('TTS_VOICE_ID', 'EXAVITQu4vr4xnSDxMaL')); // voce "Sarah" (come AllTax/TRPG)
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// CERTISOURCE (atti-service.php) // CERTISOURCE (atti-service.php)
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
+54
View File
@@ -41,6 +41,11 @@ class Database
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci", PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
]; ];
// TLS-in-transito (VIGILE 2026-06-10) — pre-equip, GATED, DEFAULT OFF.
// Unione con '+' (NON array_merge): preserva le chiavi-intere delle
// opzioni PDO (array_merge le re-indicizzerebbe). Vedi doc VIGILE §14.
$options = $options + self::sslOptions();
try { try {
self::$instance = new PDO($dsn, DB_USER, DB_PASS, $options); self::$instance = new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) { } catch (PDOException $e) {
@@ -55,6 +60,55 @@ class Database
return self::$instance; return self::$instance;
} }
/**
* Opzioni PDO per TLS-in-transito verso MySQL (VIGILE 2026-06-10, pre-equip).
*
* GATED, DEFAULT OFF: ritorna [] (nessuna modifica al comportamento attuale)
* a meno che NON sia attivato esplicitamente tramite:
* - env DB_SSL=true (canonico)
* - OPPURE flag-file application/config/.db_ssl_on (robusto anche in
* PHP-FPM dove getenv() puo' non propagare; nessun recreate necessario)
*
* IMPORTANTE (doc VIGILE §11): PHP PDO/mysqlnd NON cifra senza un CA file.
* MYSQL_ATTR_SSL_VERIFY_SERVER_CERT=false DA SOLO connette IN CHIARO. Quindi
* il TLS si attiva solo se il CA e' presente e leggibile; altrimenti si
* resta in chiaro (come oggi) e si logga — MAI una connessione "a meta'".
* CA host self-signed => VERIFY_SERVER_CERT=false (cifra il canale; la
* verifica della CA e' hardening successivo, coordinato con VIGILE).
*
* @return array opzioni PDO SSL (vuoto se disattivato o CA mancante)
*/
private static function sslOptions(): array
{
$enabled = Env::bool('DB_SSL', false) || is_file(__DIR__ . '/.db_ssl_on');
if (!$enabled) {
return [];
}
// GUARD anti-outage (lezione 2026-06-10, 3 outage): il TLS NON si applica a
// una connessione via UNIX socket (DB_HOST 'localhost', vuoto o path '/...').
// Forzarlo da' PDOException [2002] "Cannot connect to MySQL using SSL" su OGNI
// richiesta. Il socket e' IPC locale (nessun transito di rete da cifrare):
// restiamo in chiaro + log, anche se il flag/env e' attivo. SSL si applica solo
// alle connessioni TCP (es. 127.0.0.1 o un host di rete).
$host = defined('DB_HOST') ? (string) DB_HOST : '';
if ($host === '' || strcasecmp($host, 'localhost') === 0 || $host[0] === '/') {
error_log('[Database] DB_SSL attivo ma connessione via UNIX socket (DB_HOST=' . $host . '): SSL non applicabile, resto in chiaro (socket = IPC locale, nessun transito da cifrare).');
return [];
}
$ca = Env::get('DB_SSL_CA', __DIR__ . '/db-ca.pem');
if (!$ca || !is_file($ca) || !is_readable($ca)) {
error_log('[Database] DB_SSL attivo ma CA non trovato/leggibile (' . $ca . '): connessione NON cifrata (serve il CA file).');
return [];
}
return [
PDO::MYSQL_ATTR_SSL_CA => $ca,
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
];
}
/** /**
* Esegue una query con parametri * Esegue una query con parametri
*/ */
+55 -7
View File
@@ -49,19 +49,28 @@ return [
'key' => 'determina_164179_2025', 'key' => 'determina_164179_2025',
'short' => 'Determina ACN 164179/2025', 'short' => 'Determina ACN 164179/2025',
'citation' => 'Determinazione ACN n. 164179 del 14 aprile 2025', 'citation' => 'Determinazione ACN n. 164179 del 14 aprile 2025',
'full' => 'Determinazione del Direttore generale dell\'Agenzia per la Cybersicurezza Nazionale n. 164179 del 14 aprile 2025, in attuazione del D.Lgs. 138/2024, che stabilisce modalita e specifiche di base per gli obblighi di cui agli articoli 23, 24, 25, 29 e 32 (incluse classificazione e notifica degli incidenti significativi, Allegati 3 e 4).', 'full' => 'Determinazione del Direttore generale dell\'Agenzia per la Cybersicurezza Nazionale n. 164179 del 14 aprile 2025, in attuazione del D.Lgs. 138/2024, che stabilisce modalita e specifiche di base per gli obblighi di cui agli articoli 23, 24, 25, 29 e 32 (incluse classificazione e notifica degli incidenti significativi: Allegato 3 = soggetti IMPORTANTI, Allegato 4 = soggetti ESSENZIALI).',
'file' => 'docs/nis2/Determina164179_apr2025.pdf', 'file' => 'docs/nis2/Determina164179_apr2025.pdf',
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)', 'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
'url' => 'https://www.acn.gov.it/', 'url' => 'https://www.acn.gov.it/portale/w/nis-avviata-la-seconda-fase',
], ],
'determina_333017_2025' => [ 'determina_333017_2025' => [
'key' => 'determina_333017_2025', 'key' => 'determina_333017_2025',
'short' => 'Determina ACN 333017/2025', 'short' => 'Determina ACN 333017/2025 (storica, superata)',
'citation' => 'Determinazione ACN n. 333017 del settembre 2025', 'citation' => 'Determinazione ACN n. 333017 del 22 settembre 2025 (SUPERATA dalla n. 379887/2025)',
'full' => 'Determinazione del Direttore generale ACN n. 333017 (settembre 2025): termini, modalita e procedimenti di utilizzo e accesso alla piattaforma digitale ACN, ulteriori informazioni che i soggetti devono fornire e designazione dei rappresentanti NIS sul territorio nazionale.', 'full' => 'Determinazione del Direttore generale ACN n. 333017 del 22 settembre 2025 [data-giorno da confermare sul testo ufficiale]: termini, modalita e procedimenti di utilizzo e accesso alla piattaforma digitale ACN, ulteriori informazioni che i soggetti devono fornire e designazione dei rappresentanti NIS sul territorio nazionale. NOTA: citazione STORICA, SUPERATA dalla Determinazione ACN n. 379887/2025 (efficace dal 31 dicembre 2025); per piattaforma/registrazione/designazioni fare riferimento alla 379887.',
'file' => 'docs/nis2/Determina333017_sett2025.pdf', 'file' => 'docs/nis2/Determina333017_sett2025.pdf',
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)', 'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
'url' => 'https://www.acn.gov.it/', 'url' => 'https://www.acn.gov.it/portale/w/nis-avviata-la-seconda-fase',
],
'determina_379887_2025' => [
'key' => 'determina_379887_2025',
'short' => 'Determina ACN 379887/2025 (vigente)',
'citation' => 'Determinazione ACN n. 379887/2025, efficace dal 31 dicembre 2025',
'full' => 'Determinazione del Direttore generale ACN n. 379887/2025 (adozione 19 dicembre 2025 [da confermare sul testo ufficiale], efficacia dal 31 dicembre 2025) che SOSTITUISCE la n. 333017/2025: disciplina il Portale ACN e i Servizi NIS per il ciclo di registrazione 2026 (registrazione, aggiornamento dei dati entro 10 giorni dalla presentazione, designazioni incluso il referente CSIRT). Fonte VIGENTE per piattaforma/registrazione/designazioni.',
'file' => null,
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
'url' => 'https://www.acn.gov.it/portale/w/nis-avviata-la-seconda-fase',
], ],
'ambiti_nis2' => [ 'ambiti_nis2' => [
'key' => 'ambiti_nis2', 'key' => 'ambiti_nis2',
@@ -76,9 +85,48 @@ return [
'key' => 'acn_specifiche_base_2025', 'key' => 'acn_specifiche_base_2025',
'short' => 'Specifiche di base ACN (Framework Nazionale 2025)', 'short' => 'Specifiche di base ACN (Framework Nazionale 2025)',
'citation' => 'ACN, Misure di sicurezza di base NIS - Allegati 1 e 2 alla Determinazione n. 164179/2025', 'citation' => 'ACN, Misure di sicurezza di base NIS - Allegati 1 e 2 alla Determinazione n. 164179/2025',
'full' => 'Misure di sicurezza di base (Determinazione ACN 164179/2025) articolate secondo il Framework Nazionale per la Cybersecurity e la Data Protection (ed. 2025): funzioni Governance (GV), Identificazione (ID), Protezione (PR), Rilevazione (DE), Risposta (RS), Ripristino (RC), poi categorie, sottocategorie e requisiti. Soggetti importanti: 37 misure / 87 requisiti (Allegato 1). Soggetti essenziali: 43 misure / 116 requisiti (Allegato 2), codifica identica con requisiti aggiuntivi. Esempi: GV.RR-04 (cybersicurezza nelle risorse umane, adeguatezza al ruolo), GV.PO-01 (policy di gestione del rischio). Adozione: 18 mesi dalla notifica di inserimento nell elenco NIS.', 'full' => 'Misure di sicurezza di base (Determinazione ACN 164179/2025) articolate secondo il Framework Nazionale per la Cybersecurity e la Data Protection (ed. 2025): funzioni Governance (GV), Identificazione (ID), Protezione (PR), Rilevazione (DE), Risposta (RS), Ripristino (RC), poi categorie, sottocategorie e requisiti. Soggetti importanti: 37 misure / 92 requisiti (Allegato 1). Soggetti essenziali: 43 misure / 116 requisiti (Allegato 2), codifica identica con requisiti aggiuntivi. Esempi: GV.RR-04 (cybersicurezza nelle risorse umane, adeguatezza al ruolo), GV.PO-01 (policy di gestione del rischio). Adozione: 18 mesi dalla notifica di inserimento nell elenco NIS. NOTA DI PROVENIENZA: il prodotto adotta 92 requisiti per i soggetti importanti come da file del referente compliance (Simon); la pagina ufficiale ACN riporta 87 - divergenza nota e accettata, prevale il dato del referente; da riconciliare sul testo ufficiale.',
'file' => 'docs/nis2/Determina164179_apr2025.pdf', 'file' => 'docs/nis2/Determina164179_apr2025.pdf',
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)', 'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
'url' => 'https://www.acn.gov.it/portale/nis/modalita-specifiche-base', 'url' => 'https://www.acn.gov.it/portale/nis/modalita-specifiche-base',
], ],
// ── Standard ISO/IEC 27000 (best practice, NON fonti normative vincolanti) ──
// Usate dal modulo Modello Organizzativo SGSI per il grounding di AI e help.
'iso_27001_2022' => [
'key' => 'iso_27001_2022',
'short' => 'ISO/IEC 27001:2022',
'citation' => 'ISO/IEC 27001:2022',
'full' => 'ISO/IEC 27001:2022 - Information security, cybersecurity and privacy protection - Information security management systems - Requirements. Definisce i requisiti del SGSI (clausole 4-10) e l\'Annex A con 93 controlli su 4 temi. BEST PRACTICE internazionale, NON obbligo normativo.',
'file' => null,
'authority' => 'ISO/IEC (best practice, non vincolante)',
'url' => 'https://www.iso.org/standard/27001',
],
'iso_27002_2022' => [
'key' => 'iso_27002_2022',
'short' => 'ISO/IEC 27002:2022',
'citation' => 'ISO/IEC 27002:2022',
'full' => 'ISO/IEC 27002:2022 - Information security controls. Guida implementativa dei 93 controlli dell\'Annex A di ISO 27001:2022. BEST PRACTICE, non vincolante.',
'file' => null,
'authority' => 'ISO/IEC (best practice, non vincolante)',
'url' => 'https://www.iso.org/standard/75652.html',
],
'iso_27017_2015' => [
'key' => 'iso_27017_2015',
'short' => 'ISO/IEC 27017:2015',
'citation' => 'ISO/IEC 27017:2015',
'full' => 'ISO/IEC 27017:2015 - Code of practice for information security controls based on ISO/IEC 27002 for cloud services. Aggiunge guida cloud-specifica e 7 controlli CLD.* (numerati su ISO 27002:2013). Applicabile a clienti e fornitori cloud. BEST PRACTICE, non vincolante.',
'file' => null,
'authority' => 'ISO/IEC (best practice, non vincolante)',
'url' => 'https://www.iso.org/standard/43757.html',
],
'iso_27018_2019' => [
'key' => 'iso_27018_2019',
'short' => 'ISO/IEC 27018:2019',
'citation' => 'ISO/IEC 27018:2019',
'full' => 'ISO/IEC 27018:2019 - Code of practice for protection of personally identifiable information (PII) in public clouds acting as PII processors. Estende ISO 27002 con controlli privacy (principi ISO 29100). Sinergico con il GDPR per gli obblighi del responsabile del trattamento. BEST PRACTICE, non vincolante.',
'file' => null,
'authority' => 'ISO/IEC (best practice, non vincolante)',
'url' => 'https://www.iso.org/standard/76559.html',
],
]; ];
@@ -1,607 +0,0 @@
<?php
/**
* NIS2 Agile - Gap Analysis ACN (Determinazione 164179/2025)
* ----------------------------------------------------------------------------
* Assessment di conformita di SECONDO LIVELLO: misure e requisiti puntuali
* della Determinazione ACN n. 164179/2025 (Allegati 1 e 2), NON le 10 lettere
* generiche dell'Art. 21 NIS2 (quelle restano in AssessmentController).
*
* - soggetti IMPORTANTI (Allegato 1): 37 misure, 87 requisiti
* - soggetti ESSENZIALI (Allegato 2): 43 misure, 116 requisiti (= 87 + 29)
*
* La codifica dei requisiti e identica per i due livelli; agli essenziali si
* aggiungono misure e punti. Il perimetro applicabile e filtrato in base al
* livello del soggetto (acn_assessments.entity_level, derivato da
* organizations.entity_type).
*
* Fonte testi: application/data/acn_measures.json (estratto integrale ACN).
*
* Endpoint (base /api/acn-gap):
* GET /catalog - catalogo misure/requisiti per il livello dell'org
* GET /list - assessment ACN dell'org
* POST /create - crea assessment (pre-popola requisiti applicabili)
* GET /{id} - intestazione + stats
* GET /{id}/requirements - requisiti raggruppati per funzione/categoria/misura
* POST /{id}/respond - salva risposta (singola o batch)
* POST /{id}/complete - calcola score e completa
* GET /{id}/report - report completo
* POST /{id}/aiAnalyze - analisi AI con grounding sui requisiti ACN
*/
require_once __DIR__ . '/BaseController.php';
class AcnAssessmentController extends BaseController
{
/** Cache in-process del dataset misure. */
private ?array $measuresCache = null;
// ════════════════════════ CATALOGO ════════════════════════
/**
* GET /api/acn-gap/catalog
* Catalogo dei requisiti ACN applicabili al livello del soggetto corrente
* (importante/essenziale), raggruppato per funzione FW. Sola lettura,
* non crea assessment. Utile per anteprima/consultazione normativa.
*/
public function catalog(): void
{
$this->requireOrgAccess();
$level = $this->resolveEntityLevel();
if ($level === null) {
// Coerente con create(): senza classificazione non c'e perimetro.
// Il frontend intercetta ENTITY_LEVEL_REQUIRED e mostra l'invito a classificare.
$this->jsonError(
'Il soggetto non e classificato come importante o essenziale. Completa prima la classificazione NIS2 dell\'organizzazione.',
422,
'ENTITY_LEVEL_REQUIRED'
);
}
$grouped = $this->groupedRequirements($level, []);
$totals = $this->datasetTotals();
$this->jsonSuccess([
'entity_level' => $level,
'totals' => $totals[$level],
'functions' => $grouped,
'source' => $this->measures()['source'] ?? null,
]);
}
// ════════════════════════ LISTA / CREATE ════════════════════════
/** GET /api/acn-gap/list */
public function list(): void
{
$this->requireOrgAccess();
$orgId = $this->getCurrentOrgId();
$rows = Database::fetchAll(
'SELECT id, title, entity_level, status, overall_score, completed_at, created_at
FROM acn_assessments
WHERE organization_id = ?
ORDER BY created_at DESC',
[$orgId]
);
$this->jsonSuccess(['assessments' => $rows]);
}
/**
* POST /api/acn-gap/create Body: { title? }
* Crea un assessment ACN e pre-popola TUTTI i requisiti applicabili al
* livello del soggetto (snapshot del testo congelato).
*/
public function create(): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$orgId = $this->getCurrentOrgId();
$userId = $this->getCurrentUserId();
$level = $this->resolveEntityLevel();
if ($level === null) {
$this->jsonError(
'Il soggetto non e classificato come importante o essenziale. Completa prima la classificazione NIS2 dell\'organizzazione.',
422,
'ENTITY_LEVEL_REQUIRED'
);
}
$title = trim((string) $this->getParam('title', ''));
if ($title === '') {
$title = 'Gap Analysis ACN ' . date('Y-m-d');
}
Database::beginTransaction();
try {
$assessmentId = Database::insert('acn_assessments', [
'organization_id' => $orgId,
'title' => $title,
'entity_level' => $level,
'status' => 'draft',
'created_by' => $userId,
]);
$applicable = $this->applicableRequirements($level);
foreach ($applicable as $r) {
Database::insert('acn_assessment_responses', [
'assessment_id' => $assessmentId,
'organization_id' => $orgId,
'requirement_key' => $r['key'],
'measure_code' => $r['measure_code'],
'requirement_index' => $r['index'],
'function_code' => $r['function_code'],
'category_code' => $r['category_code'],
'requirement_text' => $r['text'],
'response_value' => null,
]);
}
Database::commit();
} catch (Throwable $e) {
if (Database::getInstance()->inTransaction()) {
Database::rollback();
}
throw $e;
}
$this->logAudit('acn_assessment_created', 'acn_assessment', $assessmentId, [
'entity_level' => $level,
'requirements' => count($applicable),
]);
$this->jsonSuccess([
'id' => $assessmentId,
'entity_level' => $level,
'requirements' => count($applicable),
], 'Assessment ACN creato', 201);
}
// ════════════════════════ DETTAGLIO ════════════════════════
/** GET /api/acn-gap/{id} */
public function get(int $id): void
{
$this->requireOrgAccess();
$a = $this->loadOwned($id);
$a['stats'] = $this->computeStats($id);
$this->jsonSuccess($a);
}
/**
* GET /api/acn-gap/{id}/requirements
* Requisiti raggruppati per funzione -> categoria -> misura, con la risposta
* salvata. Struttura pensata per il rendering a wizard/accordion.
*/
public function getRequirements(int $id): void
{
$this->requireOrgAccess();
$a = $this->loadOwned($id);
$rows = Database::fetchAll(
'SELECT requirement_key, measure_code, requirement_index, function_code,
category_code, requirement_text, response_value, evidence_description, notes, answered_at
FROM acn_assessment_responses
WHERE assessment_id = ?
ORDER BY id',
[$id]
);
// arricchimento da dataset: titoli misura/categoria/funzione
$meta = $this->measureMeta();
$byFunc = [];
foreach ($rows as $r) {
$fc = $r['function_code'];
$cc = $r['category_code'];
$mc = $r['measure_code'];
$byFunc[$fc] ??= [
'function_code' => $fc,
'function_name' => $meta['functions'][$fc] ?? $fc,
'categories' => [],
];
$byFunc[$fc]['categories'][$cc] ??= [
'category_code' => $cc,
'category_title' => $meta['categories'][$cc] ?? $cc,
'measures' => [],
];
$byFunc[$fc]['categories'][$cc]['measures'][$mc] ??= [
'measure_code' => $mc,
'measure_title' => $meta['measures'][$mc] ?? $mc,
'requirements' => [],
];
$byFunc[$fc]['categories'][$cc]['measures'][$mc]['requirements'][] = [
'key' => $r['requirement_key'],
'index' => (int) $r['requirement_index'],
'text' => $r['requirement_text'],
'response' => $r['response_value'],
'evidence' => $r['evidence_description'],
'notes' => $r['notes'],
'answered_at' => $r['answered_at'],
];
}
// converti mappe associative in liste ordinate
$functions = [];
foreach ($byFunc as $f) {
$cats = [];
foreach ($f['categories'] as $c) {
$c['measures'] = array_values($c['measures']);
$cats[] = $c;
}
$f['categories'] = $cats;
$functions[] = $f;
}
$this->jsonSuccess([
'assessment' => ['id' => $id, 'entity_level' => $a['entity_level'], 'status' => $a['status']],
'functions' => $functions,
'stats' => $this->computeStats($id),
]);
}
/**
* POST /api/acn-gap/{id}/respond
* Body: { requirement_key, response_value, evidence?, notes? }
* oppure { responses: [ {requirement_key, response_value, ...}, ... ] }
*/
public function saveResponse(int $id): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager', 'auditor']);
$a = $this->loadOwned($id);
if ($a['status'] === 'completed') {
$this->jsonError('Assessment gia completato: non modificabile.', 409, 'ALREADY_COMPLETED');
}
$userId = $this->getCurrentUserId();
$orgId = $this->getCurrentOrgId();
$body = $this->getJsonBody();
$batch = is_array($body['responses'] ?? null) ? $body['responses'] : [$body];
$valid = ['not_implemented', 'partial', 'implemented', 'not_applicable'];
$saved = 0;
foreach ($batch as $item) {
if (!is_array($item)) {
continue;
}
$key = trim((string) ($item['requirement_key'] ?? ''));
$val = (string) ($item['response_value'] ?? '');
if ($key === '' || !in_array($val, $valid, true)) {
continue;
}
$updated = Database::update('acn_assessment_responses', [
'response_value' => $val,
'evidence_description' => isset($item['evidence']) ? (string) $item['evidence'] : null,
'notes' => isset($item['notes']) ? (string) $item['notes'] : null,
'answered_by' => $userId,
'answered_at' => date('Y-m-d H:i:s'),
], 'assessment_id = ? AND requirement_key = ? AND organization_id = ?', [$id, $key, $orgId]);
$saved += $updated > 0 ? 1 : 0;
}
// porta a in_progress se era draft
if ($a['status'] === 'draft' && $saved > 0) {
Database::update('acn_assessments', ['status' => 'in_progress'], 'id = ?', [$id]);
}
$this->jsonSuccess(['saved' => $saved, 'stats' => $this->computeStats($id)], 'Risposte salvate');
}
/**
* POST /api/acn-gap/{id}/complete
* Calcola overall_score + function_scores + stats e marca completed.
* Richiede che tutti i requisiti applicabili abbiano una risposta.
*/
public function complete(int $id): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$a = $this->loadOwned($id);
if ($a['status'] === 'completed') {
$this->jsonError('Assessment gia completato.', 409, 'ALREADY_COMPLETED');
}
$unanswered = (int) (Database::fetchOne(
'SELECT COUNT(*) AS n FROM acn_assessment_responses WHERE assessment_id = ? AND response_value IS NULL',
[$id]
)['n'] ?? 0);
if ($unanswered > 0) {
$this->jsonError(
"Mancano $unanswered requisiti senza risposta. Completa tutte le risposte prima di chiudere.",
422,
'INCOMPLETE',
['unanswered' => $unanswered]
);
}
[$overall, $funcScores, $stats] = $this->calculateScores($id);
Database::update('acn_assessments', [
'status' => 'completed',
'overall_score' => $overall,
'function_scores' => json_encode($funcScores, JSON_UNESCAPED_UNICODE),
'stats' => json_encode($stats, JSON_UNESCAPED_UNICODE),
'completed_by' => $this->getCurrentUserId(),
'completed_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$id]);
$this->logAudit('acn_assessment_completed', 'acn_assessment', $id, [
'overall_score' => $overall,
'entity_level' => $a['entity_level'],
]);
$this->jsonSuccess([
'overall_score' => $overall,
'function_scores' => $funcScores,
'stats' => $stats,
], 'Assessment ACN completato');
}
/** GET /api/acn-gap/{id}/report */
public function getReport(int $id): void
{
$this->requireOrgAccess();
$a = $this->loadOwned($id);
// requisiti non conformi (priorita per il piano d'azione)
$gaps = Database::fetchAll(
"SELECT requirement_key, measure_code, function_code, category_code, requirement_text,
response_value, notes
FROM acn_assessment_responses
WHERE assessment_id = ? AND response_value IN ('not_implemented','partial')
ORDER BY FIELD(response_value,'not_implemented','partial'), function_code, measure_code, requirement_index",
[$id]
);
$a['function_scores'] = $a['function_scores'] ? json_decode($a['function_scores'], true) : null;
$a['stats'] = $a['stats'] ? json_decode($a['stats'], true) : $this->computeStats($id);
$a['ai_recommendations'] = $a['ai_recommendations'] ? json_decode($a['ai_recommendations'], true) : null;
$this->jsonSuccess(['assessment' => $a, 'gaps' => $gaps]);
}
/**
* POST /api/acn-gap/{id}/aiAnalyze
* Analisi AI dei gap con grounding sui requisiti ACN (la KB contiene gia i
* 203 requisiti, scope SYSTEM). Salva sintesi e raccomandazioni.
*/
public function aiAnalyze(int $id): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$a = $this->loadOwned($id);
$gaps = Database::fetchAll(
"SELECT measure_code, requirement_index, requirement_text, response_value
FROM acn_assessment_responses
WHERE assessment_id = ? AND response_value IN ('not_implemented','partial')
ORDER BY function_code, measure_code, requirement_index
LIMIT 60",
[$id]
);
if (empty($gaps)) {
$this->jsonSuccess(['ai_summary' => 'Nessun gap rilevato: tutti i requisiti applicabili risultano conformi.'], 'Analisi completata');
}
require_once APP_PATH . '/services/AIService.php';
$ai = new AIService();
$lines = array_map(
fn($g) => "- {$g['measure_code']} punto {$g['requirement_index']} [{$g['response_value']}]: {$g['requirement_text']}",
$gaps
);
$question = "Analizza i seguenti gap di conformita rispetto alle misure di sicurezza di base ACN "
. "(Determinazione 164179/2025) per un soggetto " . ($a['entity_level'] === 'essential' ? 'ESSENZIALE' : 'IMPORTANTE') . ". "
. "Per ciascun gap suggerisci un'azione concreta e prioritizza. Cita SOLO requisiti ACN reali.\n\n"
. implode("\n", $lines);
try {
$userContext = [
'organization_id' => $this->getCurrentOrgId(),
'consulting_firm_id' => $this->currentUser['consulting_firm_id'] ?? null,
];
// askWithRag ritorna ['answer'=>string, 'sources'=>array, 'rag_used'=>bool]:
// estraiamo il testo, NON salviamo l'intero array.
$result = $ai->askWithRag($question, $userContext);
$answer = is_array($result) ? (string) ($result['answer'] ?? '') : (string) $result;
$sources = (is_array($result) && isset($result['sources'])) ? $result['sources'] : [];
} catch (Throwable $e) {
error_log('[AcnAssessment] aiAnalyze fallita: ' . $e->getMessage());
$this->jsonError('Analisi AI temporaneamente non disponibile. Riprova piu tardi.', 503, 'AI_UNAVAILABLE');
}
Database::update('acn_assessments', [
'ai_summary' => $answer,
'ai_recommendations' => json_encode($sources, JSON_UNESCAPED_UNICODE),
], 'id = ?', [$id]);
$this->logAudit('acn_assessment_ai_analyzed', 'acn_assessment', $id, ['gaps' => count($gaps)]);
$this->jsonSuccess(['ai_summary' => $answer, 'sources' => $sources], 'Analisi AI completata');
}
// ════════════════════════ HELPER ════════════════════════
/** Carica l'assessment verificando ownership org (anti-IDOR). */
private function loadOwned(int $id): array
{
$a = Database::fetchOne(
'SELECT * FROM acn_assessments WHERE id = ? AND organization_id = ?',
[$id, $this->getCurrentOrgId()]
);
if (!$a) {
$this->jsonError('Assessment non trovato', 404, 'NOT_FOUND');
}
return $a;
}
/**
* Determina il livello del soggetto dall'organization corrente.
* organizations.entity_type: 'essential'|'important'|'not_applicable'.
* Ritorna 'essential'|'important', oppure null se non classificato.
*/
private function resolveEntityLevel(): ?string
{
$org = Database::fetchOne('SELECT entity_type FROM organizations WHERE id = ?', [$this->getCurrentOrgId()]);
$t = $org['entity_type'] ?? null;
if ($t === 'essential' || $t === 'important') {
return $t;
}
return null;
}
/** Carica e cache il dataset misure ACN. */
private function measures(): array
{
if ($this->measuresCache === null) {
$path = APP_PATH . '/data/acn_measures.json';
$json = is_readable($path) ? json_decode((string) file_get_contents($path), true) : null;
$this->measuresCache = is_array($json) ? $json : ['measures' => []];
}
return $this->measuresCache;
}
/** Totali requisiti per livello (da dataset). */
private function datasetTotals(): array
{
$t = $this->measures()['totals'] ?? [];
return [
'important' => [
'measures' => $t['measures_importante'] ?? 37,
'requirements' => $t['requirements_importante'] ?? 87,
],
'essential' => [
'measures' => $t['measures_essenziale'] ?? 43,
'requirements' => $t['requirements_essenziale'] ?? 116,
],
];
}
/**
* Lista piatta dei requisiti applicabili a un livello.
* @return array<int,array{key:string,measure_code:string,index:int,function_code:string,category_code:string,text:string}>
*/
private function applicableRequirements(string $level): array
{
$flag = $level === 'essential' ? 'essenziale' : 'importante';
$out = [];
foreach ($this->measures()['measures'] ?? [] as $m) {
$applies = $level === 'essential' ? ($m['applies_essenziale'] ?? false) : ($m['applies_importante'] ?? false);
if (!$applies) {
continue;
}
foreach ($m['requirements'] ?? [] as $r) {
if (empty($r[$flag])) {
continue; // punto non applicabile a questo livello
}
$out[] = [
'key' => $m['code'] . '#' . $r['index'],
'measure_code' => $m['code'],
'index' => (int) $r['index'],
'function_code' => $m['function_code'],
'category_code' => $m['category_code'],
'text' => $r['text'],
];
}
}
return $out;
}
/** Requisiti raggruppati per funzione (per il catalogo). */
private function groupedRequirements(?string $level, array $responses): array
{
if ($level === null) {
return [];
}
$reqs = $this->applicableRequirements($level);
$meta = $this->measureMeta();
$byFunc = [];
foreach ($reqs as $r) {
$fc = $r['function_code'];
$byFunc[$fc] ??= ['function_code' => $fc, 'function_name' => $meta['functions'][$fc] ?? $fc, 'measures' => []];
$mc = $r['measure_code'];
$byFunc[$fc]['measures'][$mc] ??= ['measure_code' => $mc, 'measure_title' => $meta['measures'][$mc] ?? $mc, 'requirements' => []];
$byFunc[$fc]['measures'][$mc]['requirements'][] = ['index' => $r['index'], 'text' => $r['text']];
}
$out = [];
foreach ($byFunc as $f) {
$f['measures'] = array_values($f['measures']);
$out[] = $f;
}
return $out;
}
/** Mappe codice->titolo per funzioni/categorie/misure (dal dataset). */
private function measureMeta(): array
{
static $meta = null;
if ($meta !== null) {
return $meta;
}
$functions = [];
$categories = [];
$measures = [];
foreach ($this->measures()['measures'] ?? [] as $m) {
$functions[$m['function_code']] = $m['function_name'] ?? $m['function_code'];
$categories[$m['category_code']] = $m['category_title'] ?? $m['category_code'];
$measures[$m['code']] = $m['title'] ?? $m['code'];
}
$meta = ['functions' => $functions, 'categories' => $categories, 'measures' => $measures];
return $meta;
}
/** Conteggi rapidi per stato risposta. */
private function computeStats(int $id): array
{
$rows = Database::fetchAll(
'SELECT response_value, COUNT(*) AS n FROM acn_assessment_responses WHERE assessment_id = ? GROUP BY response_value',
[$id]
);
$stats = ['applicable' => 0, 'implemented' => 0, 'partial' => 0, 'not_implemented' => 0, 'not_applicable' => 0, 'unanswered' => 0];
foreach ($rows as $r) {
$n = (int) $r['n'];
$stats['applicable'] += $n;
$v = $r['response_value'];
if ($v === null) {
$stats['unanswered'] += $n;
} elseif (isset($stats[$v])) {
$stats[$v] += $n;
}
}
return $stats;
}
/**
* Calcolo punteggi: overall + per funzione.
* Scoring per requisito: implemented=100, partial=50, not_implemented=0.
* not_applicable e unanswered ESCLUSI dal denominatore (coerente con
* l'assessment Art.21 esistente).
* @return array{0:float,1:array,2:array}
*/
private function calculateScores(int $id): array
{
$rows = Database::fetchAll(
'SELECT function_code, response_value FROM acn_assessment_responses WHERE assessment_id = ?',
[$id]
);
$val = ['implemented' => 100.0, 'partial' => 50.0, 'not_implemented' => 0.0];
$sum = 0.0; $cnt = 0;
$byFunc = [];
foreach ($rows as $r) {
$v = $r['response_value'];
if ($v === 'not_applicable' || $v === null || !isset($val[$v])) {
continue;
}
$fc = $r['function_code'];
$byFunc[$fc] ??= ['sum' => 0.0, 'cnt' => 0];
$byFunc[$fc]['sum'] += $val[$v];
$byFunc[$fc]['cnt']++;
$sum += $val[$v];
$cnt++;
}
// Se nessun requisito e applicabile (tutti not_applicable) il punteggio
// non e definito: null, non 0.0 (che significherebbe "0% conforme").
$overall = $cnt > 0 ? round($sum / $cnt, 2) : null;
$funcScores = [];
foreach ($byFunc as $fc => $d) {
$funcScores[$fc] = $d['cnt'] > 0 ? round($d['sum'] / $d['cnt'], 2) : 0.0;
}
return [$overall, $funcScores, $this->computeStats($id)];
}
}
+114 -1
View File
@@ -39,12 +39,51 @@ class AiController extends BaseController
} }
$user = $this->getCurrentUser() ?? []; $user = $this->getCurrentUser() ?? [];
// Pagina corrente (facoltativa) inviata dal frontend, per dare ad ARIA
// contesto sulla schermata da cui l'utente sta scrivendo (ticket #424).
$page = mb_substr(trim((string) $this->getParam('page', '')), 0, 120);
// pageId canonico + testo dell'help "?" della pagina (allineamento ARIA↔Help).
$pageId = mb_substr(trim((string) $this->getParam('page_id', '')), 0, 40);
$pageHelp = mb_substr(trim((string) $this->getParam('page_help', '')), 0, 2500);
// Org attiva per lo snapshot dati di ARIA. ask() chiama requireAuth() ma NON
// requireOrgAccess(), quindi getCurrentOrgId() resta null e org_data_ok sarebbe
// sempre falso (ARIA cieca sui dati org). Risolviamo a mano: header
// X-Organization-Id → param org_id → currentOrgId → org primaria dell'utente.
// Lo spoofing è neutralizzato dal controllo membership qui sotto.
$orgId = (int) ($_SERVER['HTTP_X_ORGANIZATION_ID'] ?? $this->getParam('org_id') ?? 0);
if ($orgId <= 0) $orgId = (int) ($this->getCurrentOrgId() ?? 0);
if ($orgId <= 0) {
$primary = Database::fetchOne(
'SELECT organization_id FROM user_organizations WHERE user_id = ? ORDER BY is_primary DESC, id ASC LIMIT 1',
[$userId]
);
$orgId = (int) ($primary['organization_id'] ?? 0);
}
$userContext = [ $userContext = [
'user_id' => $userId, 'user_id' => $userId,
'organization_id' => $this->getCurrentOrgId(), // può essere null 'organization_id' => $orgId ?: null,
'consulting_firm_id' => $user['consulting_firm_id'] ?? null, 'consulting_firm_id' => $user['consulting_firm_id'] ?? null,
'page' => $page,
'page_id' => $pageId,
'page_help' => $pageHelp,
]; ];
// Membership verificata → ARIA può iniettare lo snapshot DATI dell'org
// (anti-spoof X-Organization-Id; super_admin bypassa, come da modello ruoli).
$orgId = (int) ($userContext['organization_id'] ?? 0);
$userContext['org_data_ok'] = false;
if ($orgId > 0) {
if (($user['role'] ?? '') === 'super_admin') {
$userContext['org_data_ok'] = true;
} else {
$member = Database::fetchOne(
'SELECT 1 FROM user_organizations WHERE user_id = ? AND organization_id = ?',
[$userId, $orgId]
);
$userContext['org_data_ok'] = (bool) $member;
}
}
try { try {
$aiService = new AIService(); $aiService = new AIService();
$result = $aiService->askWithRag($question, $userContext); $result = $aiService->askWithRag($question, $userContext);
@@ -72,4 +111,78 @@ class AiController extends BaseController
$this->jsonError('Assistente AI non disponibile in questo momento', 503, 'AI_UNAVAILABLE'); $this->jsonError('Assistente AI non disponibile in questo momento', 503, 'AI_UNAVAILABLE');
} }
} }
/**
* POST /api/ai/tts — Voce naturale di ARIA.
* Proxy same-origin verso nexus-voice-ms (ElevenLabs): il voice-ms non espone CORS
* per l'origin NIS2 → il browser non può chiamarlo diretto. Cache MP3 per hash del
* testo (stesse frasi → niente costo ripetuto). Stream audio/mpeg.
*/
public function tts(): void
{
$this->requireAuth();
$text = trim((string) $this->getParam('text', ''));
if ($text === '') { $this->jsonError('Testo mancante', 422, 'TTS_EMPTY'); }
if (mb_strlen($text) > 800) { $text = mb_substr($text, 0, 800); }
$lang = substr(strtolower(preg_replace('/[^a-z]/', '', (string) $this->getParam('lang', 'it')) ?: 'it'), 0, 5) ?: 'it';
$voice = (string) $this->getParam('voice_id', defined('TTS_VOICE_ID') ? TTS_VOICE_ID : 'EXAVITQu4vr4xnSDxMaL');
if (!preg_match('/^[A-Za-z0-9]{8,40}$/', $voice)) {
$voice = defined('TTS_VOICE_ID') ? TTS_VOICE_ID : 'EXAVITQu4vr4xnSDxMaL';
}
$cacheDir = sys_get_temp_dir() . '/nis2-tts-cache';
$cacheFile = $cacheDir . '/' . hash('sha256', $voice . '|' . $lang . '|' . $text) . '.mp3';
if (is_file($cacheFile) && filesize($cacheFile) > 256) {
$this->streamMp3((string) file_get_contents($cacheFile), 'HIT');
return;
}
$payload = json_encode([
'text' => $text,
'lang' => $lang,
'voice_id' => $voice,
'model' => 'eleven_turbo_v2_5',
'stability' => 0.5,
'similarity_boost' => 0.75,
'style' => 0.0,
'output_format' => 'mp3_44100_128',
]);
$url = defined('VOICE_MS_URL') ? VOICE_MS_URL : 'http://172.21.0.1:4215/tts/speak';
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 6,
CURLOPT_TIMEOUT => 30,
]);
$audio = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$ctype = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
if ($code !== 200 || !$audio || strlen($audio) < 256 || stripos($ctype, 'audio') === false) {
$this->jsonError('Sintesi vocale non disponibile', 502, 'TTS_UPSTREAM');
}
if (!is_dir($cacheDir)) { @mkdir($cacheDir, 0775, true); }
@file_put_contents($cacheFile . '.tmp', $audio);
@rename($cacheFile . '.tmp', $cacheFile);
$this->streamMp3($audio, 'MISS');
}
private function streamMp3(string $audio, string $cache): void
{
header('Content-Type: audio/mpeg');
header('Content-Length: ' . strlen($audio));
header('Cache-Control: private, max-age=86400');
header('X-TTS-Cache: ' . $cache);
echo $audio;
exit;
}
} }
+176 -3
View File
@@ -37,9 +37,12 @@ class AssetController extends BaseController
$total = Database::count('assets', $where, $params); $total = Database::count('assets', $where, $params);
$assets = Database::fetchAll( $assets = Database::fetchAll(
"SELECT a.*, u.full_name as owner_name "SELECT a.*, u.full_name as owner_name,
sc.label AS subclass_label, v.label AS voce_label
FROM assets a FROM assets a
LEFT JOIN users u ON u.id = a.owner_user_id LEFT JOIN users u ON u.id = a.owner_user_id
LEFT JOIN inventory_subclassi sc ON sc.id = a.subclass_id
LEFT JOIN cfg_inventory_voci v ON v.voce_code = a.voce_code
WHERE a.{$where} WHERE a.{$where}
ORDER BY a.criticality DESC, a.name ORDER BY a.criticality DESC, a.name
LIMIT {$pagination['per_page']} OFFSET {$pagination['offset']}", LIMIT {$pagination['per_page']} OFFSET {$pagination['offset']}",
@@ -52,12 +55,28 @@ class AssetController extends BaseController
public function create(): void public function create(): void
{ {
$this->requireOrgRole(['org_admin', 'compliance_manager']); $this->requireOrgRole(['org_admin', 'compliance_manager']);
$this->validateRequired(['name', 'asset_type']); $this->validateRequired(['name']);
$voce = $this->getParam('voce_code');
if (!$voce) {
// retro-compatibilità: deriva la voce dal vecchio asset_type (UI non ancora aggiornata / import)
$at = (string) $this->getParam('asset_type');
if (in_array($at, ['software', 'service', 'data'], true)) { $voce = 'ID.AM-02'; }
elseif (in_array($at, ['hardware', 'network', 'facility'], true)) { $voce = 'ID.AM-01'; }
}
if (!in_array($voce, ['ID.AM-01', 'ID.AM-02'], true)) {
$this->jsonError('Voce inventario obbligatoria (ID.AM-01 o ID.AM-02)', 422, 'INVALID_VOCE');
}
$subclassId = $this->validateSubclass($this->getParam('subclass_id'), $voce);
// asset_type resta come campo legacy (viste secondarie): usa quello passato o deriva dalla voce
$assetType = $this->getParam('asset_type') ?: ($voce === 'ID.AM-01' ? 'hardware' : 'service');
$assetId = Database::insert('assets', [ $assetId = Database::insert('assets', [
'organization_id' => $this->getCurrentOrgId(), 'organization_id' => $this->getCurrentOrgId(),
'name' => trim($this->getParam('name')), 'name' => trim($this->getParam('name')),
'asset_type' => $this->getParam('asset_type'), 'asset_type' => $assetType,
'voce_code' => $voce,
'subclass_id' => $subclassId,
'category' => $this->getParam('category'), 'category' => $this->getParam('category'),
'description' => $this->getParam('description'), 'description' => $this->getParam('description'),
'criticality' => $this->getParam('criticality', 'medium'), 'criticality' => $this->getParam('criticality', 'medium'),
@@ -119,6 +138,8 @@ class AssetController extends BaseController
$type = strtolower((string) ($a['asset_type'] ?? 'service')); $type = strtolower((string) ($a['asset_type'] ?? 'service'));
if (!in_array($type, $validType, true)) $type = 'service'; if (!in_array($type, $validType, true)) $type = 'service';
// C4: voce (2 valori) derivata dal tipo legacy per gli asset importati
$voce = in_array($type, ['hardware', 'network', 'facility'], true) ? 'ID.AM-01' : 'ID.AM-02';
// Scoring automatico GV.OC-04 da euristica sui campi CMDB // Scoring automatico GV.OC-04 da euristica sui campi CMDB
$criteria = AssetScoringService::inferCriteria($a); $criteria = AssetScoringService::inferCriteria($a);
@@ -130,6 +151,7 @@ class AssetController extends BaseController
'organization_id' => $orgId, 'organization_id' => $orgId,
'name' => $name, 'name' => $name,
'asset_type' => $type, 'asset_type' => $type,
'voce_code' => $voce,
'category' => $a['category'] ?? null, 'category' => $a['category'] ?? null,
'description' => $a['description'] ?? null, 'description' => $a['description'] ?? null,
'criticality' => $sc['criticality'], 'criticality' => $sc['criticality'],
@@ -145,6 +167,10 @@ class AssetController extends BaseController
'relevance_assessed_at' => date('Y-m-d H:i:s'), 'relevance_assessed_at' => date('Y-m-d H:i:s'),
'relevance_assessed_by' => $userId, 'relevance_assessed_by' => $userId,
]; ];
// Dipendenze scoperte dal connettore → popolano la Mappa Dipendenze.
if (isset($a['dependencies']) && is_array($a['dependencies'])) {
$row['dependencies'] = json_encode(array_values($a['dependencies']), JSON_UNESCAPED_UNICODE);
}
try { try {
$existing = $extRef !== null $existing = $extRef !== null
@@ -216,6 +242,21 @@ class AssetController extends BaseController
$updates['dependencies'] = json_encode($this->getParam('dependencies')); $updates['dependencies'] = json_encode($this->getParam('dependencies'));
} }
// Voce/sottoclasse (Epic C / C4) — con validazione (no loop generico)
if ($this->hasParam('voce_code')) {
$voce = $this->getParam('voce_code');
if (!in_array($voce, ['ID.AM-01', 'ID.AM-02'], true)) {
$this->jsonError('Voce inventario non valida', 422, 'INVALID_VOCE');
}
$updates['voce_code'] = $voce;
if ($this->hasParam('subclass_id')) {
$updates['subclass_id'] = $this->validateSubclass($this->getParam('subclass_id'), $voce);
}
} elseif ($this->hasParam('subclass_id')) {
$cur = Database::fetchOne('SELECT voce_code FROM assets WHERE id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
$updates['subclass_id'] = $this->validateSubclass($this->getParam('subclass_id'), $cur['voce_code'] ?? '');
}
if (!empty($updates)) { if (!empty($updates)) {
Database::update('assets', $updates, 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]); Database::update('assets', $updates, 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
$this->logAudit('asset_updated', 'asset', $id, $updates); $this->logAudit('asset_updated', 'asset', $id, $updates);
@@ -224,6 +265,138 @@ class AssetController extends BaseController
$this->jsonSuccess($updates, 'Asset aggiornato'); $this->jsonSuccess($updates, 'Asset aggiornato');
} }
/**
* Valida una sottoclasse per la voce indicata: deve esistere ed essere o di
* sistema (organization_id NULL) o dell'org corrente, e appartenere alla voce.
* Ritorna l'id (int) oppure null se non fornita.
*/
private function validateSubclass($id, string $voce): ?int
{
$id = (int) $id;
if ($id <= 0) { return null; }
$row = Database::fetchOne(
'SELECT id FROM inventory_subclassi
WHERE id = ? AND voce_code = ? AND (organization_id = ? OR organization_id IS NULL)',
[$id, $voce, $this->getCurrentOrgId()]
);
if (!$row) { $this->jsonError('Sottoclasse non valida per la voce selezionata', 422, 'INVALID_SUBCLASS'); }
return $id;
}
/**
* GET /api/assets/subclasses — le 2 voci canoniche + le sottoclassi visibili
* (default di sistema + quelle dell'org), per i selettori dell'inventario.
*/
public function subclasses(): void
{
$this->requireOrgAccess();
$orgId = $this->getCurrentOrgId();
$voci = Database::fetchAll('SELECT voce_code, label, descr, ord FROM cfg_inventory_voci ORDER BY ord');
$subs = Database::fetchAll(
'SELECT id, voce_code, label, (organization_id IS NULL) AS is_default, ord
FROM inventory_subclassi
WHERE organization_id IS NULL OR organization_id = ?
ORDER BY voce_code, ord, label',
[$orgId]
);
$byVoce = [];
foreach ($subs as $s) {
$byVoce[$s['voce_code']][] = [
'id' => (int) $s['id'], 'label' => $s['label'], 'is_default' => ((int) $s['is_default'] === 1),
];
}
$out = [];
foreach ($voci as $v) {
$out[] = [
'voce_code' => $v['voce_code'], 'label' => $v['label'], 'descr' => $v['descr'],
'subclassi' => $byVoce[$v['voce_code']] ?? [],
];
}
$this->jsonSuccess(['voci' => $out]);
}
/**
* POST /api/assets/subclasses { voce_code*, label* }
* Aggiunge una sottoclasse ORGANIZZATIVA (org-scoped). Le due voci principali
* NON sono modificabili dall'utente (restano due e solo due).
*/
public function addSubclass(): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$body = $this->getJsonBody();
$voce = $body['voce_code'] ?? '';
$label = trim((string) ($body['label'] ?? ''));
if (!in_array($voce, ['ID.AM-01', 'ID.AM-02'], true)) {
$this->jsonError('Voce inventario non valida', 422, 'INVALID_VOCE');
}
if ($label === '' || mb_strlen($label) > 120) {
$this->jsonError('Etichetta sottoclasse obbligatoria (max 120 caratteri)', 422, 'INVALID_LABEL');
}
$orgId = $this->getCurrentOrgId();
$dup = Database::fetchOne(
'SELECT id FROM inventory_subclassi WHERE voce_code = ? AND label = ? AND (organization_id = ? OR organization_id IS NULL)',
[$voce, $label, $orgId]
);
if ($dup) { $this->jsonError('Sottoclasse già esistente', 409, 'DUPLICATE'); }
$id = Database::insert('inventory_subclassi', [
'organization_id' => $orgId, 'voce_code' => $voce, 'label' => $label, 'ord' => 99,
]);
$this->jsonSuccess(['id' => $id, 'voce_code' => $voce, 'label' => $label], 'Sottoclasse aggiunta', 201);
}
/**
* PUT /api/assets/subclasses/{id} { label* }
* Rinomina una sottoclasse ORGANIZZATIVA dell'org corrente. Le sottoclassi di
* sistema (organization_id NULL) NON sono modificabili dall'utente.
*/
public function updateSubclass(int $id): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$body = $this->getJsonBody();
$label = trim((string) ($body['label'] ?? ''));
if ($label === '' || mb_strlen($label) > 120) {
$this->jsonError('Etichetta sottoclasse obbligatoria (max 120 caratteri)', 422, 'INVALID_LABEL');
}
$orgId = $this->getCurrentOrgId();
$row = Database::fetchOne(
'SELECT id, voce_code FROM inventory_subclassi WHERE id = ? AND organization_id = ?',
[$id, $orgId]
);
if (!$row) {
$this->jsonError('Sottoclasse non trovata o non modificabile (le voci di sistema non si modificano)', 404, 'SUBCLASS_NOT_FOUND');
}
$dup = Database::fetchOne(
'SELECT id FROM inventory_subclassi WHERE voce_code = ? AND label = ? AND (organization_id = ? OR organization_id IS NULL) AND id <> ?',
[$row['voce_code'], $label, $orgId, $id]
);
if ($dup) { $this->jsonError('Sottoclasse già esistente', 409, 'DUPLICATE'); }
Database::update('inventory_subclassi', ['label' => $label], 'id = ? AND organization_id = ?', [$id, $orgId]);
$this->jsonSuccess(['id' => $id, 'label' => $label], 'Sottoclasse aggiornata');
}
/**
* DELETE /api/assets/subclasses/{id}
* Rimuove una sottoclasse ORGANIZZATIVA dell'org corrente. Gli asset che la
* usano restano senza sottoclasse (FK ON DELETE SET NULL su assets.subclass_id).
* Le sottoclassi di sistema (organization_id NULL) NON sono eliminabili.
*/
public function deleteSubclass(int $id): void
{
$this->requireOrgRole(['org_admin', 'compliance_manager']);
$orgId = $this->getCurrentOrgId();
$row = Database::fetchOne(
'SELECT id FROM inventory_subclassi WHERE id = ? AND organization_id = ?',
[$id, $orgId]
);
if (!$row) {
$this->jsonError('Sottoclasse non trovata o non eliminabile (le voci di sistema non si rimuovono)', 404, 'SUBCLASS_NOT_FOUND');
}
$used = Database::fetchOne('SELECT COUNT(*) AS c FROM assets WHERE subclass_id = ?', [$id]);
$usedCount = (int) ($used['c'] ?? 0);
Database::delete('inventory_subclassi', 'id = ? AND organization_id = ?', [$id, $orgId]);
$this->jsonSuccess(['id' => $id, 'unlinked_assets' => $usedCount], 'Sottoclasse rimossa');
}
public function delete(int $id): void public function delete(int $id): void
{ {
$this->requireOrgRole(['org_admin']); $this->requireOrgRole(['org_admin']);
@@ -64,6 +64,14 @@ class AuditController extends BaseController
$this->jsonError('File troppo grande (max 10MB)', 400, 'FILE_TOO_LARGE'); $this->jsonError('File troppo grande (max 10MB)', 400, 'FILE_TOO_LARGE');
} }
// Allowlist estensioni (anti stored-XSS same-origin: niente html/svg/js eseguibili).
// Allineata a StakeholderPortalController::ALLOWED_EXT.
$allowedExt = ['pdf','png','jpg','jpeg','gif','webp','txt','csv','xlsx','xls','docx','doc','pptx','ppt','odt','ods','zip'];
$extCheck = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($extCheck === '' || !in_array($extCheck, $allowedExt, true)) {
$this->jsonError('Tipo di file non consentito. Formati ammessi: PDF, immagini, documenti Office, txt, csv, zip.', 422, 'BAD_FILE_TYPE');
}
$orgId = $this->getCurrentOrgId(); $orgId = $this->getCurrentOrgId();
$uploadDir = UPLOAD_PATH . "/evidence/{$orgId}"; $uploadDir = UPLOAD_PATH . "/evidence/{$orgId}";
@@ -16,6 +16,11 @@ class BaseController
protected ?int $currentOrgId = null; protected ?int $currentOrgId = null;
protected ?string $currentOrgRole = null; protected ?string $currentOrgRole = null;
// ── Contesto Avatar di prodotto (demo/sandbox) — vedi applyDemoGuard() ──
protected bool $isDemo = false; // sessione demo attiva (read-only o sandbox)
protected bool $isSandbox = false; // scope training:sandbox (scritture su org sandbox)
protected ?int $demoOrgId = null; // org del range riservato demo (996000-996999)
// ═══════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════
// RISPOSTE JSON // RISPOSTE JSON
// ═══════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════
@@ -250,8 +255,78 @@ class BaseController
/** /**
* Richiede autenticazione JWT * Richiede autenticazione JWT
*/ */
/**
* Read-only guard Avatar di prodotto (product-demo-protocol v1.0.1).
*
* SAFE-BY-CONSTRUCTION: ritorna false (e NON tocca nulla) per qualsiasi JWT
* applicativo normale. Si attiva SOLO con un demo_jwt firmato che porta
* scope=demo:read-only oppure training:sandbox, validato contro demo_sessions.
*
* - demo:read-only → blocca OGNI scrittura (POST/PUT/PATCH/DELETE) con 403.
* - training:sandbox → consente scritture, ma SOLO sull'org sandbox della sessione.
* - Contesto sintetico: user id=0, ruolo compliance_manager, org = org del range
* demo riservato (996000-996999). Mai super_admin → admin resta irraggiungibile.
*
* @return bool true se ha gestito un contesto demo (il chiamante deve return).
*/
protected function applyDemoGuard(): bool
{
$token = $this->getBearerToken();
if (!$token) return false;
$payload = $this->verifyJWT($token);
if (!$payload) return false;
$scope = $payload['scope'] ?? '';
if ($scope !== 'demo:read-only' && $scope !== 'training:sandbox') {
return false; // JWT applicativo normale → flusso invariato
}
$orgId = (int) ($payload['org_id'] ?? 0);
if ($orgId < 996000 || $orgId > 996999) {
$this->jsonError('Contesto demo non valido', 401, 'DEMO_CTX_INVALID');
}
$sid = (string) ($payload['demo_session_id'] ?? '');
$sess = $sid !== '' ? Database::fetchOne('SELECT * FROM demo_sessions WHERE session_id = ?', [$sid]) : null;
if (!$sess || strtotime($sess['expires_at']) < time()) {
$this->jsonError('Sessione demo non valida o scaduta', 401, 'DEMO_SESSION_INVALID');
}
$isWrite = in_array($this->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'], true);
if ($scope === 'demo:read-only' && $isWrite) {
$this->jsonError('Modalità demo in sola lettura: azione non disponibile', 403, 'DEMO_READ_ONLY');
}
if ($scope === 'training:sandbox' && $isWrite) {
// Scritture consentite SOLO sull'org sandbox della sessione (anti-spoof X-Organization-Id).
$reqOrgRaw = $_SERVER['HTTP_X_ORGANIZATION_ID'] ?? $this->getParam('org_id');
$reqOrg = ($reqOrgRaw !== null && $reqOrgRaw !== '') ? (int) $reqOrgRaw : null;
if ($reqOrg !== null && $reqOrg !== $orgId) {
$this->jsonError('Sandbox: scritture consentite solo sull\'organizzazione sandbox', 403, 'SANDBOX_ORG_LOCKED');
}
}
// Contesto sintetico read (o sandbox). Nessun accesso al DB utenti reali.
$this->isDemo = true;
$this->isSandbox = ($scope === 'training:sandbox');
$this->demoOrgId = $orgId;
$this->currentUser = [
'id' => 0, 'email' => 'demo@nis2-demo.local',
'full_name' => 'Avatar Demo', 'role' => 'compliance_manager',
'consulting_firm_id' => null,
];
$this->currentOrgId = $orgId;
$this->currentOrgRole = 'compliance_manager';
return true;
}
protected function requireAuth(): void protected function requireAuth(): void
{ {
// Avatar di prodotto: se è un demo_jwt valido, applica il guard e termina qui.
if ($this->applyDemoGuard()) {
return;
}
$token = $this->getBearerToken(); $token = $this->getBearerToken();
if (!$token) { if (!$token) {
@@ -351,6 +426,13 @@ class BaseController
{ {
$this->requireAuth(); $this->requireAuth();
// Avatar di prodotto: contesto org già fissato dal guard sull'org demo/sandbox.
if ($this->isDemo) {
$this->currentOrgId = $this->demoOrgId;
$this->currentOrgRole = 'compliance_manager';
return;
}
$orgId = $this->resolveOrgId(); $orgId = $this->resolveOrgId();
if (!$orgId) { if (!$orgId) {
@@ -385,6 +467,12 @@ class BaseController
{ {
$this->requireOrgAccess(); $this->requireOrgAccess();
// Avatar di prodotto: le scritture sono già filtrate dal guard (demo=block,
// sandbox=solo org sandbox). Le letture passano i gate di ruolo.
if ($this->isDemo) {
return;
}
if ($this->currentOrgRole === 'super_admin') { if ($this->currentOrgRole === 'super_admin') {
return; return;
} }
@@ -0,0 +1,494 @@
<?php
/**
* NIS2 Agile - Calendario unico delle scadenze (Modulo C)
* ----------------------------------------------------------------------------
* AGGREGATORE IN SOLA LETTURA. Nessuna tabella propria, nessuna migrazione: le
* scadenze vivono gia' nei moduli sorgente, ciascuna col proprio ciclo di vita.
* Questo controller fa una UNION LIVE di TUTTE le sorgenti e normalizza ogni
* evento in una forma unica:
* { source, type, title, date (YYYY-MM-DD), status, severity, entity_type,
* entity_id, link }
* dove status e' calcolato LIVE confrontando la data con oggi (come review_schedule):
* - done = scadenza gia' assolta/chiusa
* - overdue = data < oggi e non assolta
* - due_soon = data entro 14 giorni da oggi
* - upcoming = data oltre 14 giorni
*
* Generalizza il pattern di DashboardController::deadlines() su TUTTE le sorgenti.
* Ogni sorgente e' racchiusa in un try/catch DIFENSIVO: alcune tabelle (Modulo A
* internal_audits, Modulo B management_reviews) potrebbero non esistere ancora, e
* il calendario deve continuare a funzionare comunque (degrada solo quella fonte).
*
* Multi-tenancy: OGNI query filtra organization_id (anti-IDOR). Sola lettura →
* requireOrgAccess(). NOTE: Database::query/fetchAll/fetchOne; jsonSuccess/Error exit.
*/
require_once __DIR__ . '/BaseController.php';
class CalendarController extends BaseController
{
/** Soglia "in scadenza": eventi entro N giorni da oggi (inclusi) sono due_soon. */
private const DUE_SOON_DAYS = 14;
/** Tipi noti (per la validazione del filtro ?types= e per la legenda frontend). */
private const KNOWN_TYPES = [
'incident_early_warning', 'incident_notification', 'incident_final_report',
'policy_review', 'risk_treatment', 'control_review',
'nc_target_close', 'capa_action', 'training_due',
'stakeholder_activity', 'review_schedule',
'internal_audit', 'management_review_decision', 'periodic_control',
];
// ─────────────────────────────────────────────────────────────────────────
// GET /api/calendar/events?from=YYYY-MM-DD&to=YYYY-MM-DD&types=csv
// ─────────────────────────────────────────────────────────────────────────
public function events(): void
{
$this->requireOrgAccess();
$orgId = $this->getCurrentOrgId();
[$from, $to] = $this->resolveRange();
$typeFilter = $this->resolveTypeFilter();
$events = $this->collect($orgId, $from, $to);
// Filtro per tipo richiesto (applicato in PHP, gli eventi sono pochi per org).
if ($typeFilter !== null) {
$events = array_values(array_filter($events, static fn($e) => in_array($e['type'], $typeFilter, true)));
}
// Ordina: overdue prima, poi per data crescente.
usort($events, static function ($a, $b) {
$oa = $a['status'] === 'overdue' ? 0 : 1;
$ob = $b['status'] === 'overdue' ? 0 : 1;
if ($oa !== $ob) { return $oa <=> $ob; }
return strcmp($a['date'], $b['date']);
});
$this->jsonSuccess([
'from' => $from,
'to' => $to,
'today' => date('Y-m-d'),
'due_soon_days'=> self::DUE_SOON_DAYS,
'known_types' => self::KNOWN_TYPES,
'count' => count($events),
'events' => $events,
]);
}
// ─────────────────────────────────────────────────────────────────────────
// GET /api/calendar/summary — conteggi per stato (e per tipo)
// ─────────────────────────────────────────────────────────────────────────
public function summary(): void
{
$this->requireOrgAccess();
$orgId = $this->getCurrentOrgId();
[$from, $to] = $this->resolveRange();
$events = $this->collect($orgId, $from, $to);
$byStatus = ['overdue' => 0, 'due_soon' => 0, 'upcoming' => 0, 'done' => 0];
$byType = [];
foreach ($events as $e) {
$byStatus[$e['status']] = ($byStatus[$e['status']] ?? 0) + 1;
$byType[$e['type']] = ($byType[$e['type']] ?? 0) + 1;
}
// Aperte = tutte tranne done (utile come badge dashboard).
$open = $byStatus['overdue'] + $byStatus['due_soon'] + $byStatus['upcoming'];
$this->jsonSuccess([
'from' => $from,
'to' => $to,
'today' => date('Y-m-d'),
'total' => count($events),
'open' => $open,
'by_status' => $byStatus,
'by_type' => $byType,
]);
}
// ─────────────────────────────────────────────────────────────────────────
// RACCOLTA — UNION di tutte le sorgenti, ognuna difensiva (try/catch)
// ─────────────────────────────────────────────────────────────────────────
/**
* Raccoglie e normalizza tutti gli eventi della finestra [from,to] per l'org.
* Ogni sorgente che fallisce (tabella/colonna mancante) viene saltata senza
* far cadere l'intero calendario.
*/
private function collect(int $orgId, string $from, string $to): array
{
$events = [];
$sources = [
'incidents' => fn() => $this->srcIncidents($orgId, $from, $to),
'policies' => fn() => $this->srcPolicies($orgId, $from, $to),
'risk_treatments' => fn() => $this->srcRiskTreatments($orgId, $from, $to),
'controls' => fn() => $this->srcControls($orgId, $from, $to),
'non_conformities' => fn() => $this->srcNonConformities($orgId, $from, $to),
'capa_actions' => fn() => $this->srcCapaActions($orgId, $from, $to),
'training' => fn() => $this->srcTraining($orgId, $from, $to),
'stk_activities' => fn() => $this->srcStkActivities($orgId, $from, $to),
'review_schedule' => fn() => $this->srcReviewSchedule($orgId, $from, $to),
'internal_audits' => fn() => $this->srcInternalAudits($orgId, $from, $to),
'mgmt_reviews' => fn() => $this->srcManagementReviewDecisions($orgId, $from, $to),
'periodic_controls'=> fn() => $this->srcPeriodicControls($orgId, $from, $to),
];
foreach ($sources as $rows) {
try {
foreach ($rows() as $ev) {
if ($ev !== null) { $events[] = $ev; }
}
} catch (\Throwable $e) {
// Sorgente non disponibile (es. tabella di un modulo non ancora migrato):
// si ignora senza compromettere le altre fonti.
continue;
}
}
return $events;
}
/** Incidenti Art.23: early_warning / notification / final_report (datetime). */
private function srcIncidents(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT id, title, severity,
early_warning_due, early_warning_sent_at,
notification_due, notification_sent_at,
final_report_due, final_report_sent_at
FROM incidents
WHERE organization_id = ? AND is_significant = 1
AND status NOT IN ("closed", "post_mortem")',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
if ($r['early_warning_due'] && !$r['early_warning_sent_at']) {
$out[] = $this->makeEvent('incidents', 'incident_early_warning',
'Early Warning: ' . $r['title'], $r['early_warning_due'],
'critical', 'incident', (int) $r['id'], '/incidents.html', $from, $to, false);
}
if ($r['notification_due'] && !$r['notification_sent_at']) {
$out[] = $this->makeEvent('incidents', 'incident_notification',
'Notifica CSIRT: ' . $r['title'], $r['notification_due'],
'high', 'incident', (int) $r['id'], '/incidents.html', $from, $to, false);
}
if ($r['final_report_due'] && !$r['final_report_sent_at']) {
$out[] = $this->makeEvent('incidents', 'incident_final_report',
'Report finale: ' . $r['title'], $r['final_report_due'],
'medium', 'incident', (int) $r['id'], '/incidents.html', $from, $to, false);
}
}
return array_values(array_filter($out));
}
/** Revisione policy/procedure: policies.next_review_date (status != archived). */
private function srcPolicies(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT id, title, next_review_date
FROM policies
WHERE organization_id = ? AND next_review_date IS NOT NULL
AND status NOT IN ("archived")',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$out[] = $this->makeEvent('policies', 'policy_review',
'Revisione policy: ' . $r['title'], $r['next_review_date'],
'medium', 'policy', (int) $r['id'], '/policies.html', $from, $to, false);
}
return array_values(array_filter($out));
}
/** Trattamenti rischio: risk_treatments.due_date JOIN risks (org via risks). */
private function srcRiskTreatments(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT rt.id, rt.due_date, r.title AS risk_title
FROM risk_treatments rt
JOIN risks r ON r.id = rt.risk_id
WHERE r.organization_id = ? AND rt.status IN ("planned", "in_progress")
AND rt.due_date IS NOT NULL',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$out[] = $this->makeEvent('risk_treatments', 'risk_treatment',
'Trattamento rischio: ' . $r['risk_title'], $r['due_date'],
'medium', 'risk_treatment', (int) $r['id'], '/risks.html', $from, $to, false);
}
return array_values(array_filter($out));
}
/** Revisione controlli: compliance_controls.next_review_date (non verificati/in corso). */
private function srcControls(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT id, control_code, title, next_review_date, status
FROM compliance_controls
WHERE organization_id = ? AND next_review_date IS NOT NULL',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$title = trim((string) $r['control_code'] . ' ' . (string) $r['title']);
$out[] = $this->makeEvent('compliance_controls', 'control_review',
'Revisione controllo: ' . $title, $r['next_review_date'],
'medium', 'compliance_control', (int) $r['id'], '/reports.html', $from, $to, false);
}
return array_values(array_filter($out));
}
/** Non conformita': non_conformities.target_close_date (status non chiuso). */
private function srcNonConformities(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT id, ncr_code, title, target_close_date
FROM non_conformities
WHERE organization_id = ? AND target_close_date IS NOT NULL
AND status NOT IN ("closed", "cancelled")',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$title = trim((string) $r['ncr_code'] . ' ' . (string) $r['title']);
$out[] = $this->makeEvent('non_conformities', 'nc_target_close',
'Chiusura NC: ' . $title, $r['target_close_date'],
'high', 'non_conformity', (int) $r['id'], '/reports.html', $from, $to, false);
}
return array_values(array_filter($out));
}
/** Azioni correttive: capa_actions.due_date (status non completed/verified). */
private function srcCapaActions(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT id, capa_code, title, due_date
FROM capa_actions
WHERE organization_id = ? AND due_date IS NOT NULL
AND status NOT IN ("completed", "verified")',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$title = trim((string) $r['capa_code'] . ' ' . (string) $r['title']);
$out[] = $this->makeEvent('capa_actions', 'capa_action',
'Azione correttiva: ' . $title, $r['due_date'],
'medium', 'capa_action', (int) $r['id'], '/reports.html', $from, $to, false);
}
return array_values(array_filter($out));
}
/** Formazione: training_assignments.due_date (status assigned/in_progress). */
private function srcTraining(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT ta.id, ta.due_date, tc.title, u.full_name
FROM training_assignments ta
JOIN training_courses tc ON tc.id = ta.course_id
LEFT JOIN users u ON u.id = ta.user_id
WHERE ta.organization_id = ? AND ta.status IN ("assigned", "in_progress")
AND ta.due_date IS NOT NULL',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$who = $r['full_name'] ? (' - ' . $r['full_name']) : '';
$out[] = $this->makeEvent('training', 'training_due',
'Formazione: ' . $r['title'] . $who, $r['due_date'],
'low', 'training', (int) $r['id'], '/training.html', $from, $to, false);
}
return array_values(array_filter($out));
}
/**
* Attivita' stakeholder (C5.2): stk_activities.due_date (preferita) e, se assente,
* planned_date. Esclude annullate/completate. due_date "assolta" se completed.
*/
private function srcStkActivities(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT id, title, type, planned_date, due_date, status
FROM stk_activities
WHERE organization_id = ? AND status NOT IN ("cancelled")',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$done = ($r['status'] === 'completed');
$date = $r['due_date'] ?: $r['planned_date'];
if (!$date) { continue; }
$label = ($r['due_date'] ? 'Scadenza attivita': 'Attivita pianificata') . ': ' . $r['title'];
$out[] = $this->makeEvent('stk_activities', 'stakeholder_activity',
$label, $date, 'medium', 'stk_activity', (int) $r['id'],
'/stakeholder-activities.html', $from, $to, $done);
}
return array_values(array_filter($out));
}
/** Scadenziario revisioni periodiche (A4 4.4): review_schedule.next_review_date. */
private function srcReviewSchedule(int $orgId, string $from, string $to): array
{
// Esclude i tipi che hanno già una sorgente diretta dedicata (internal_audit via
// srcInternalAudits, stakeholder_activity via srcStkActivities): altrimenti
// comparirebbero due volte nel calendario. Restano gli scheduler generici.
$rows = Database::fetchAll(
'SELECT id, title, entity_type, next_review_date, last_reviewed_at
FROM review_schedule
WHERE organization_id = ? AND next_review_date IS NOT NULL
AND entity_type NOT IN ("internal_audit", "stakeholder_activity")',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$out[] = $this->makeEvent('review_schedule', 'review_schedule',
$r['title'], $r['next_review_date'],
'medium', 'review_schedule', (int) $r['id'], '/review-schedule.html', $from, $to, false);
}
return array_values(array_filter($out));
}
/** Audit interni (Modulo A, opzionale): internal_audits.planned_date. */
private function srcInternalAudits(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT id, code, title, planned_date, status
FROM internal_audits
WHERE organization_id = ? AND planned_date IS NOT NULL',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$done = in_array($r['status'], ['completed', 'cancelled'], true);
$title = trim((string) $r['code'] . ' ' . (string) $r['title']);
$out[] = $this->makeEvent('internal_audits', 'internal_audit',
'Audit interno: ' . $title, $r['planned_date'],
'medium', 'internal_audit', (int) $r['id'], '/internal-audits.html', $from, $to, $done);
}
return array_values(array_filter($out));
}
/** Controlli periodici (mig.057, opzionale): periodic_controls.next_due_date. */
private function srcPeriodicControls(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
"SELECT id, code, title, next_due_date, status
FROM periodic_controls
WHERE organization_id = ? AND next_due_date IS NOT NULL AND status = 'active'",
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$title = trim((string) $r['code'] . ' ' . (string) $r['title']);
$out[] = $this->makeEvent('periodic_controls', 'periodic_control',
'Controllo periodico: ' . $title, $r['next_due_date'],
'medium', 'periodic_control', (int) $r['id'], '/controlli-periodici.html', $from, $to, false);
}
return array_values(array_filter($out));
}
/**
* Decisioni del riesame di direzione (Modulo B, opzionale):
* management_review_decisions.due_date JOIN management_reviews per filtrare per org.
*/
private function srcManagementReviewDecisions(int $orgId, string $from, string $to): array
{
$rows = Database::fetchAll(
'SELECT d.id, d.decision, d.due_date, d.status, mr.code AS review_code
FROM management_review_decisions d
JOIN management_reviews mr ON mr.id = d.review_id
WHERE mr.organization_id = ? AND d.due_date IS NOT NULL',
[$orgId]
);
$out = [];
foreach ($rows as $r) {
$done = ($r['status'] === 'done');
$txt = mb_substr((string) $r['decision'], 0, 120);
$out[] = $this->makeEvent('management_reviews', 'management_review_decision',
'Decisione riesame: ' . $txt, $r['due_date'],
'medium', 'management_review_decision', (int) $r['id'],
'/management-review.html', $from, $to, $done);
}
return array_values(array_filter($out));
}
// ─────────────────────────────────────────────────────────────────────────
// HELPER
// ─────────────────────────────────────────────────────────────────────────
/**
* Normalizza una riga in evento di calendario. Ritorna null se la data
* (normalizzata a Y-m-d) cade fuori dalla finestra [from,to]. $done forza
* lo status a 'done' (scadenza gia' assolta/chiusa), altrimenti lo calcola
* LIVE rispetto a oggi.
*/
private function makeEvent(
string $source, string $type, string $title, ?string $rawDate,
string $severity, string $entityType, int $entityId, string $link,
string $from, string $to, bool $done
): ?array {
if (!$rawDate) { return null; }
$date = substr((string) $rawDate, 0, 10); // DATETIME o DATE → Y-m-d
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { return null; }
if ($date < $from || $date > $to) { return null; }
return [
'source' => $source,
'type' => $type,
'title' => mb_substr($title, 0, 255),
'date' => $date,
'status' => $this->computeStatus($date, $done),
'severity' => $severity,
'entity_type' => $entityType,
'entity_id' => $entityId,
'link' => $link,
];
}
/** Stato LIVE: done | overdue | due_soon (<=N gg) | upcoming. */
private function computeStatus(string $date, bool $done): string
{
if ($done) { return 'done'; }
$today = new DateTimeImmutable('today');
$d = DateTimeImmutable::createFromFormat('!Y-m-d', $date);
if (!$d) { return 'upcoming'; }
$diffDays = (int) $today->diff($d)->format('%r%a');
if ($diffDays < 0) { return 'overdue'; }
if ($diffDays <= self::DUE_SOON_DAYS) { return 'due_soon'; }
return 'upcoming';
}
/**
* Risolve la finestra [from,to]. Default ampio: dal 1° giorno di 1 mese fa al
* 1° giorno di 13 mesi avanti (copre tutto cio' che ha senso vedere in un
* calendario annuale). Param from/to opzionali sovrascrivono (validati Y-m-d).
*/
private function resolveRange(): array
{
$from = $this->validDate($this->getParam('from'));
$to = $this->validDate($this->getParam('to'));
if (!$from) { $from = (new DateTimeImmutable('first day of this month'))->modify('-1 month')->format('Y-m-d'); }
if (!$to) { $to = (new DateTimeImmutable('first day of this month'))->modify('+13 months')->format('Y-m-d'); }
if ($from > $to) { [$from, $to] = [$to, $from]; }
return [$from, $to];
}
/** Filtro tipi: ?types=a,b,c → solo i tipi noti; null = nessun filtro. */
private function resolveTypeFilter(): ?array
{
$raw = $this->getParam('types');
if ($raw === null || $raw === '') { return null; }
$parts = array_filter(array_map('trim', explode(',', (string) $raw)), static fn($t) => $t !== '');
$valid = array_values(array_intersect($parts, self::KNOWN_TYPES));
return $valid ?: null;
}
/** Valida una data Y-m-d; ritorna la stringa normalizzata o null. */
private function validDate($v): ?string
{
if ($v === null || $v === '') { return null; }
$s = trim((string) $v);
$dt = DateTime::createFromFormat('Y-m-d', $s);
return ($dt && $dt->format('Y-m-d') === $s) ? $s : null;
}
}
@@ -0,0 +1,700 @@
<?php
/**
* NIS2 Agile - Competenze (A4 Fase 4.2)
* ----------------------------------------------------------------------------
* Catalogo competenze (skills), requisiti per ruolo (role_skills), competenze
* possedute dagli utenti (user_skills), mappatura competenza<->corso
* (skill_course_map), calcolo del GAP competenze e apertura di un'azione
* correttiva dal gap riusando il workflow NCR/CAPA esistente.
*
* Multi-tenancy ancorata a getCurrentOrgId(); scritture riservate a
* org_admin/compliance_manager (super_admin bypassa). Anti-IDOR su (id +
* organization_id). Le competenze globali (skills.organization_id NULL) sono in
* sola lettura per gli utenti dell'org (solo super_admin le gestisce).
*
* Ancoraggio normativo (docs/DESIGN_A4_RELATIONAL.md sez.3/4): PR.AT-01 (tutti),
* PR.AT-02 (solo soggetti essenziali), GV.RR-04 (cyber nelle pratiche HR).
*
* NB: la tabella delle azioni e 'capa_actions' (figlia di 'non_conformities'),
* NON 'corrective_actions' (che non esiste). openAction() crea una NCR ancorata
* al gap (source_entity_type='competence_gap') + una CAPA collegata.
*/
require_once __DIR__ . '/BaseController.php';
class CompetenceController extends BaseController
{
private const MANAGE_ROLES = ['org_admin', 'compliance_manager'];
// ═══════════════════════════════════════════════════════════════════════
// CATALOGO COMPETENZE (skills)
// ═══════════════════════════════════════════════════════════════════════
/** GET /api/competences/catalog — skill visibili (org + globali) + corsi mappati. */
public function catalog(): void
{
$this->requireOrgAccess();
$orgId = $this->getCurrentOrgId();
$skills = Database::fetchAll(
'SELECT s.id, s.organization_id, s.name, s.area, s.description, s.created_at, s.updated_at
FROM skills s
WHERE s.organization_id = ? OR s.organization_id IS NULL
ORDER BY (s.organization_id IS NULL) DESC, s.area, s.name',
[$orgId]
);
// Corsi mappati per ciascuna skill (corsi visibili: org o globali).
$maps = Database::fetchAll(
'SELECT scm.id AS map_id, scm.skill_id, scm.training_course_id, tc.title
FROM skill_course_map scm
JOIN training_courses tc ON tc.id = scm.training_course_id
WHERE tc.organization_id = ? OR tc.organization_id IS NULL',
[$orgId]
);
$coursesBySkill = [];
foreach ($maps as $m) {
$coursesBySkill[(int) $m['skill_id']][] = [
'map_id' => (int) $m['map_id'],
'course_id' => (int) $m['training_course_id'],
'title' => $m['title'],
];
}
$isSuper = ($this->currentUser['role'] ?? '') === 'super_admin';
$out = array_map(function ($s) use ($coursesBySkill, $isSuper, $orgId) {
$s = $this->normalizeSkill($s);
$s['courses'] = $coursesBySkill[$s['id']] ?? [];
$s['editable'] = $s['is_global'] ? $isSuper : ($s['organization_id'] === $orgId);
return $s;
}, $skills);
$this->jsonSuccess([
'skills' => $out,
'total' => count($out),
]);
}
/** POST /api/competences/skills — {name, area?, description?, global?(super_admin)} */
public function createSkill(): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$name = trim((string) $this->getParam('name', ''));
if ($name === '') {
$this->jsonError('Il nome della competenza e obbligatorio', 422, 'SKILL_NAME_REQUIRED');
}
if (mb_strlen($name) > 150) {
$this->jsonError('Il nome della competenza supera 150 caratteri', 422, 'SKILL_NAME_TOO_LONG');
}
// Solo super_admin puo creare competenze globali (organization_id NULL).
$isSuper = ($this->currentUser['role'] ?? '') === 'super_admin';
$orgId = ($isSuper && $this->getParam('global')) ? null : $this->getCurrentOrgId();
$id = Database::insert('skills', [
'organization_id' => $orgId,
'name' => $name,
'area' => $this->nullableText($this->getParam('area'), 100),
'description' => $this->nullableText($this->getParam('description')),
'created_by' => $this->getCurrentUserId(),
]);
$this->logAudit('skill_created', 'skill', $id, ['name' => $name, 'global' => $orgId === null]);
$skill = Database::fetchOne('SELECT * FROM skills WHERE id = ?', [$id]);
$this->jsonSuccess($this->normalizeSkill($skill), 'Competenza creata', 201);
}
/** PUT /api/competences/skills/{id} */
public function updateSkill(int $id): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$skill = $this->fetchVisibleSkillOrFail($id);
$this->assertSkillWritable($skill);
$updates = [];
if ($this->hasParam('name')) {
$name = trim((string) $this->getParam('name', ''));
if ($name === '') {
$this->jsonError('Il nome della competenza e obbligatorio', 422, 'SKILL_NAME_REQUIRED');
}
if (mb_strlen($name) > 150) {
$this->jsonError('Il nome della competenza supera 150 caratteri', 422, 'SKILL_NAME_TOO_LONG');
}
$updates['name'] = $name;
}
if ($this->hasParam('area')) {
$updates['area'] = $this->nullableText($this->getParam('area'), 100);
}
if ($this->hasParam('description')) {
$updates['description'] = $this->nullableText($this->getParam('description'));
}
if (empty($updates)) {
$this->jsonError('Nessun campo da aggiornare', 400, 'NO_UPDATES');
}
Database::update('skills', $updates, 'id = ?', [$id]);
$this->logAudit('skill_updated', 'skill', $id, $updates);
$skill = Database::fetchOne('SELECT * FROM skills WHERE id = ?', [$id]);
$this->jsonSuccess($this->normalizeSkill($skill), 'Competenza aggiornata');
}
/** DELETE /api/competences/skills/{id} — cascata su role_skills/user_skills/skill_course_map. */
public function deleteSkill(int $id): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$skill = $this->fetchVisibleSkillOrFail($id);
$this->assertSkillWritable($skill);
$usedRole = Database::count('role_skills', 'skill_id = ?', [$id]);
$usedUser = Database::count('user_skills', 'skill_id = ?', [$id]);
Database::delete('skills', 'id = ?', [$id]);
$this->logAudit('skill_deleted', 'skill', $id, ['role_links' => $usedRole, 'user_links' => $usedUser]);
$this->jsonSuccess([
'removed_role_links' => $usedRole,
'removed_user_links' => $usedUser,
], 'Competenza eliminata');
}
// ═══════════════════════════════════════════════════════════════════════
// REQUISITI PER RUOLO (role_skills)
// ═══════════════════════════════════════════════════════════════════════
/** GET /api/competences/roleSkills/{roleId} — competenze richieste dal ruolo. */
public function roleSkills(int $roleId): void
{
$this->requireOrgAccess();
$this->fetchRoleOrFail($roleId);
$rows = Database::fetchAll(
'SELECT rs.id, rs.role_id, rs.skill_id, rs.required_level, s.name AS skill_name, s.area
FROM role_skills rs JOIN skills s ON s.id = rs.skill_id
WHERE rs.role_id = ? AND rs.organization_id = ?
ORDER BY s.area, s.name',
[$roleId, $this->getCurrentOrgId()]
);
$this->jsonSuccess(array_map(fn($r) => [
'id' => (int) $r['id'],
'role_id' => (int) $r['role_id'],
'skill_id' => (int) $r['skill_id'],
'skill_name' => $r['skill_name'],
'area' => $r['area'],
'required_level' => (int) $r['required_level'],
], $rows));
}
/** POST /api/competences/roleSkills — {role_id, skill_id, required_level} upsert. */
public function setRoleSkill(): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$this->validateRequired(['role_id', 'skill_id']);
$orgId = $this->getCurrentOrgId();
$roleId = (int) $this->getParam('role_id');
$skillId = (int) $this->getParam('skill_id');
$level = $this->clampLevel($this->getParam('required_level', 3));
$this->fetchRoleOrFail($roleId);
$this->fetchVisibleSkillOrFail($skillId);
$existing = Database::fetchOne(
'SELECT id FROM role_skills WHERE role_id = ? AND skill_id = ?',
[$roleId, $skillId]
);
if ($existing) {
Database::update('role_skills', ['required_level' => $level], 'id = ?', [(int) $existing['id']]);
$id = (int) $existing['id'];
$created = false;
} else {
$id = Database::insert('role_skills', [
'organization_id' => $orgId,
'role_id' => $roleId,
'skill_id' => $skillId,
'required_level' => $level,
'created_by' => $this->getCurrentUserId(),
]);
$created = true;
}
$this->logAudit('role_skill_set', 'role_skill', $id, ['role_id' => $roleId, 'skill_id' => $skillId, 'required_level' => $level]);
$this->jsonSuccess(['id' => $id, 'required_level' => $level], $created ? 'Requisito aggiunto' : 'Requisito aggiornato', $created ? 201 : 200);
}
/** DELETE /api/competences/roleSkills/{id} */
public function removeRoleSkill(int $id): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$row = Database::fetchOne('SELECT id FROM role_skills WHERE id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
if (!$row) {
$this->jsonError('Requisito non trovato', 404, 'ROLE_SKILL_NOT_FOUND');
}
Database::delete('role_skills', 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
$this->logAudit('role_skill_removed', 'role_skill', $id, null);
$this->jsonSuccess(null, 'Requisito rimosso');
}
// ═══════════════════════════════════════════════════════════════════════
// COMPETENZE POSSEDUTE (user_skills)
// ═══════════════════════════════════════════════════════════════════════
/** GET /api/competences/userSkills/{userId} — competenze possedute dall'utente. */
public function userSkills(int $userId): void
{
$this->requireOrgAccess();
$this->assertMember($userId);
$rows = Database::fetchAll(
'SELECT us.id, us.user_id, us.skill_id, us.level, us.acquired_via_course_id, us.evidence,
s.name AS skill_name, s.area, tc.title AS course_title
FROM user_skills us
JOIN skills s ON s.id = us.skill_id
LEFT JOIN training_courses tc ON tc.id = us.acquired_via_course_id
WHERE us.user_id = ? AND us.organization_id = ?
ORDER BY s.area, s.name',
[$userId, $this->getCurrentOrgId()]
);
$this->jsonSuccess(array_map(fn($r) => [
'id' => (int) $r['id'],
'user_id' => (int) $r['user_id'],
'skill_id' => (int) $r['skill_id'],
'skill_name' => $r['skill_name'],
'area' => $r['area'],
'level' => (int) $r['level'],
'acquired_via_course_id' => $r['acquired_via_course_id'] !== null ? (int) $r['acquired_via_course_id'] : null,
'course_title' => $r['course_title'],
'evidence' => $r['evidence'],
], $rows));
}
/** POST /api/competences/userSkills — {user_id, skill_id, level, evidence?, acquired_via_course_id?} upsert. */
public function setUserSkill(): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$this->validateRequired(['user_id', 'skill_id']);
$orgId = $this->getCurrentOrgId();
$userId = (int) $this->getParam('user_id');
$skillId = (int) $this->getParam('skill_id');
$level = $this->clampLevel($this->getParam('level', 1));
$this->assertMember($userId);
$this->fetchVisibleSkillOrFail($skillId);
$courseId = $this->validateCourse($this->getParam('acquired_via_course_id'));
$existing = Database::fetchOne(
'SELECT id FROM user_skills WHERE organization_id = ? AND user_id = ? AND skill_id = ?',
[$orgId, $userId, $skillId]
);
$data = [
'level' => $level,
'acquired_via_course_id' => $courseId,
'evidence' => $this->nullableText($this->getParam('evidence'), 500),
'assessed_at' => date('Y-m-d H:i:s'),
];
if ($existing) {
Database::update('user_skills', $data, 'id = ?', [(int) $existing['id']]);
$id = (int) $existing['id'];
$created = false;
} else {
$data['organization_id'] = $orgId;
$data['user_id'] = $userId;
$data['skill_id'] = $skillId;
$data['created_by'] = $this->getCurrentUserId();
$id = Database::insert('user_skills', $data);
$created = true;
}
$this->logAudit('user_skill_set', 'user_skill', $id, ['user_id' => $userId, 'skill_id' => $skillId, 'level' => $level]);
$this->jsonSuccess(['id' => $id, 'level' => $level], $created ? 'Competenza registrata' : 'Competenza aggiornata', $created ? 201 : 200);
}
/** DELETE /api/competences/userSkills/{id} */
public function removeUserSkill(int $id): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$row = Database::fetchOne('SELECT id FROM user_skills WHERE id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
if (!$row) {
$this->jsonError('Competenza non trovata', 404, 'USER_SKILL_NOT_FOUND');
}
Database::delete('user_skills', 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
$this->logAudit('user_skill_removed', 'user_skill', $id, null);
$this->jsonSuccess(null, 'Competenza rimossa');
}
// ═══════════════════════════════════════════════════════════════════════
// MAPPATURA COMPETENZA <-> CORSO (skill_course_map)
// ═══════════════════════════════════════════════════════════════════════
/** POST /api/competences/skillCourses — {skill_id, training_course_id} */
public function mapCourse(): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$this->validateRequired(['skill_id', 'training_course_id']);
$skillId = (int) $this->getParam('skill_id');
$courseId = (int) $this->getParam('training_course_id');
$skill = $this->fetchVisibleSkillOrFail($skillId);
$this->assertSkillWritable($skill);
if ($this->validateCourse($courseId) === null) {
$this->jsonError('Corso non valido per questa organizzazione', 422, 'INVALID_COURSE');
}
$existing = Database::fetchOne(
'SELECT id FROM skill_course_map WHERE skill_id = ? AND training_course_id = ?',
[$skillId, $courseId]
);
if ($existing) {
$this->jsonSuccess(['id' => (int) $existing['id']], 'Corso gia mappato', 200);
}
$id = Database::insert('skill_course_map', [
'skill_id' => $skillId,
'training_course_id' => $courseId,
'created_by' => $this->getCurrentUserId(),
]);
$this->logAudit('skill_course_mapped', 'skill', $skillId, ['training_course_id' => $courseId]);
$this->jsonSuccess(['id' => $id], 'Corso mappato', 201);
}
/** DELETE /api/competences/skillCourses/{id} */
public function unmapCourse(int $id): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
// Verifica che il mapping riguardi una skill gestibile dall'org corrente.
$row = Database::fetchOne(
'SELECT scm.id, s.organization_id
FROM skill_course_map scm JOIN skills s ON s.id = scm.skill_id
WHERE scm.id = ? AND (s.organization_id = ? OR s.organization_id IS NULL)',
[$id, $this->getCurrentOrgId()]
);
if (!$row) {
$this->jsonError('Mappatura non trovata', 404, 'MAP_NOT_FOUND');
}
$isGlobal = $row['organization_id'] === null;
if ($isGlobal && ($this->currentUser['role'] ?? '') !== 'super_admin') {
$this->jsonError('Le competenze globali sono gestite dal fornitore', 403, 'SKILL_GLOBAL_READONLY');
}
Database::delete('skill_course_map', 'id = ?', [$id]);
$this->logAudit('skill_course_unmapped', 'skill', (int) ($row['organization_id'] ?? 0), ['map_id' => $id]);
$this->jsonSuccess(null, 'Mappatura rimossa');
}
// ═══════════════════════════════════════════════════════════════════════
// GAP COMPETENZE + ALERT->AZIONE
// ═══════════════════════════════════════════════════════════════════════
/**
* GET /api/competences/gapGrid
* Per ogni ruolo con titolare: competenze richieste vs possedute, gap,
* corsi suggeriti e se esiste gia un'azione (NCR) aperta sul gap.
*/
public function gapGrid(): void
{
$this->requireOrgAccess();
$orgId = $this->getCurrentOrgId();
$org = Database::fetchOne('SELECT entity_type FROM organizations WHERE id = ?', [$orgId]);
$entityType = $org['entity_type'] ?? 'not_applicable';
$rows = Database::fetchAll(
'SELECT r.id AS role_id, r.role_name, r.holder_user_id, u.full_name AS holder_name,
rs.id AS role_skill_id, rs.skill_id, s.name AS skill_name, s.area,
rs.required_level, COALESCE(us.level, 0) AS current_level
FROM org_roles r
JOIN users u ON u.id = r.holder_user_id
JOIN role_skills rs ON rs.role_id = r.id
JOIN skills s ON s.id = rs.skill_id
LEFT JOIN user_skills us
ON us.user_id = r.holder_user_id AND us.skill_id = rs.skill_id AND us.organization_id = r.organization_id
WHERE r.organization_id = ? AND r.holder_user_id IS NOT NULL
ORDER BY r.sort_order, r.role_name, s.area, s.name',
[$orgId]
);
// Corsi suggeriti per skill (corsi visibili: org o globali).
$maps = Database::fetchAll(
'SELECT scm.skill_id, tc.id AS course_id, tc.title
FROM skill_course_map scm JOIN training_courses tc ON tc.id = scm.training_course_id
WHERE tc.organization_id = ? OR tc.organization_id IS NULL',
[$orgId]
);
$coursesBySkill = [];
foreach ($maps as $m) {
$coursesBySkill[(int) $m['skill_id']][] = ['id' => (int) $m['course_id'], 'title' => $m['title']];
}
// Azioni (NCR) gia aperte ancorate a un gap, indicizzate per role_skill_id.
$openNcr = Database::fetchAll(
"SELECT source_entity_id, id, ncr_code, status FROM non_conformities
WHERE organization_id = ? AND source_entity_type = 'competence_gap'
AND status NOT IN ('closed','cancelled')",
[$orgId]
);
$ncrByRoleSkill = [];
foreach ($openNcr as $n) {
$ncrByRoleSkill[(int) $n['source_entity_id']] = ['ncr_id' => (int) $n['id'], 'ncr_code' => $n['ncr_code'], 'status' => $n['status']];
}
$rolesById = [];
$totalGaps = 0;
foreach ($rows as $r) {
$roleId = (int) $r['role_id'];
if (!isset($rolesById[$roleId])) {
$rolesById[$roleId] = [
'role_id' => $roleId,
'role_name' => $r['role_name'],
'holder_user_id' => (int) $r['holder_user_id'],
'holder_name' => $r['holder_name'],
'skills' => [],
'gap_count' => 0,
];
}
$required = (int) $r['required_level'];
$current = (int) $r['current_level'];
$gap = max(0, $required - $current);
$roleSkillId = (int) $r['role_skill_id'];
if ($gap > 0) {
$totalGaps++;
$rolesById[$roleId]['gap_count']++;
}
$rolesById[$roleId]['skills'][] = [
'role_skill_id' => $roleSkillId,
'skill_id' => (int) $r['skill_id'],
'skill_name' => $r['skill_name'],
'area' => $r['area'],
'required_level' => $required,
'current_level' => $current,
'gap' => $gap,
'suggested_courses'=> $coursesBySkill[(int) $r['skill_id']] ?? [],
'open_action' => $ncrByRoleSkill[$roleSkillId] ?? null,
];
}
$roles = array_values($rolesById);
$this->jsonSuccess([
'entity_type' => $entityType,
'pr_at_02_applies' => $entityType === 'essential',
'roles' => $roles,
'roles_with_holder' => count($roles),
'roles_with_gaps' => count(array_filter($roles, fn($r) => $r['gap_count'] > 0)),
'total_gaps' => $totalGaps,
]);
}
/**
* POST /api/competences/openAction — {role_skill_id}
* Apre una Non Conformita (NCR) ancorata al gap + una CAPA collegata,
* riusando il workflow NCR/CAPA esistente. Idempotente: se esiste gia una
* NCR aperta per lo stesso gap, ritorna quella (409).
*/
public function openAction(): void
{
$this->requireOrgRole(self::MANAGE_ROLES);
$this->validateRequired(['role_skill_id']);
$orgId = $this->getCurrentOrgId();
$roleSkillId = (int) $this->getParam('role_skill_id');
$rs = Database::fetchOne(
'SELECT rs.id, rs.role_id, rs.skill_id, rs.required_level, s.name AS skill_name,
r.role_name, r.holder_user_id
FROM role_skills rs
JOIN skills s ON s.id = rs.skill_id
JOIN org_roles r ON r.id = rs.role_id
WHERE rs.id = ? AND rs.organization_id = ?',
[$roleSkillId, $orgId]
);
if (!$rs) {
$this->jsonError('Requisito di competenza non trovato', 404, 'ROLE_SKILL_NOT_FOUND');
}
if ($rs['holder_user_id'] === null) {
$this->jsonError('Il ruolo non ha un titolare: assegna prima un titolare', 422, 'ROLE_NO_HOLDER');
}
$holderId = (int) $rs['holder_user_id'];
$required = (int) $rs['required_level'];
$cur = Database::fetchOne(
'SELECT level FROM user_skills WHERE organization_id = ? AND user_id = ? AND skill_id = ?',
[$orgId, $holderId, (int) $rs['skill_id']]
);
$current = $cur ? (int) $cur['level'] : 0;
$gap = $required - $current;
if ($gap <= 0) {
$this->jsonError('Nessun gap di competenza su questo requisito', 422, 'NO_GAP');
}
// Anti-duplicato: NCR gia aperta per lo stesso gap.
$dup = Database::fetchOne(
"SELECT id, ncr_code FROM non_conformities
WHERE organization_id = ? AND source_entity_type = 'competence_gap' AND source_entity_id = ?
AND status NOT IN ('closed','cancelled')",
[$orgId, $roleSkillId]
);
if ($dup) {
$this->jsonError('Esiste gia una non conformita aperta per questo gap (' . $dup['ncr_code'] . ')', 409, 'ACTION_EXISTS', [
'ncr_id' => (int) $dup['id'],
'ncr_code' => $dup['ncr_code'],
]);
}
// PR.AT-02 vale solo per i soggetti essenziali; PR.AT-01 per tutti.
$org = Database::fetchOne('SELECT entity_type FROM organizations WHERE id = ?', [$orgId]);
$prAt = (($org['entity_type'] ?? '') === 'essential') ? 'PR.AT-02' : 'PR.AT-01';
$severity = $gap >= 2 ? 'major' : 'minor';
$title = 'Gap competenza: ' . $rs['skill_name'] . ' (' . $rs['role_name'] . ')';
$desc = "Gap di competenza rilevato dall'organigramma.\n"
. 'Ruolo: ' . $rs['role_name'] . "\n"
. 'Competenza: ' . $rs['skill_name'] . "\n"
. 'Livello richiesto: ' . $required . ' / Posseduto: ' . $current . ' (gap: ' . $gap . ")\n"
. 'Riferimento: ' . $prAt . ' (formazione e consapevolezza); GV.RR-04 (cyber nelle pratiche HR). '
. 'Gli obblighi di gestione del rischio fanno capo all\'art. 24 D.Lgs. 138/2024.';
Database::beginTransaction();
try {
$ncrCode = $this->generateCode('NCR');
$ncrId = Database::insert('non_conformities', [
'organization_id' => $orgId,
'ncr_code' => $ncrCode,
'title' => mb_substr($title, 0, 255),
'description' => $desc,
'source' => 'management_review',
'source_entity_type'=> 'competence_gap',
'source_entity_id' => $roleSkillId,
'severity' => $severity,
'category' => 'Competenze',
'nis2_article' => $prAt,
'status' => 'action_planned',
'identified_by' => $this->getCurrentUserId(),
'assigned_to' => $holderId,
]);
$capaCode = $this->generateCode('CAPA');
$capaId = Database::insert('capa_actions', [
'ncr_id' => $ncrId,
'organization_id' => $orgId,
'capa_code' => $capaCode,
'action_type' => 'corrective',
'title' => mb_substr('Colmare il gap di competenza: ' . $rs['skill_name'], 0, 255),
'description' => 'Pianificare formazione/affiancamento per portare ' . $rs['skill_name']
. ' al livello ' . $required . '. Valutare i corsi mappati alla competenza.',
'status' => 'planned',
'responsible_user_id'=> $holderId,
]);
Database::commit();
} catch (Throwable $e) {
Database::rollback();
throw $e;
}
$this->logAudit('competence_action_opened', 'non_conformity', $ncrId, [
'role_skill_id' => $roleSkillId, 'capa_id' => $capaId, 'gap' => $gap,
]);
$this->jsonSuccess([
'ncr_id' => $ncrId,
'ncr_code' => $ncrCode,
'capa_id' => $capaId,
'capa_code' => $capaCode,
], 'Non conformita e azione correttiva create', 201);
}
// ═══════════════════════════════════════════════════════════════════════
// PRIVATI
// ═══════════════════════════════════════════════════════════════════════
/** Skill visibile all'org (org-owned o globale) oppure 404. */
private function fetchVisibleSkillOrFail(int $id): array
{
$s = Database::fetchOne(
'SELECT * FROM skills WHERE id = ? AND (organization_id = ? OR organization_id IS NULL)',
[$id, $this->getCurrentOrgId()]
);
if (!$s) {
$this->jsonError('Competenza non trovata', 404, 'SKILL_NOT_FOUND');
}
return $s;
}
/** Una skill e scrivibile se appartiene all'org; le globali solo da super_admin. */
private function assertSkillWritable(array $skill): void
{
$isGlobal = $skill['organization_id'] === null;
if ($isGlobal && ($this->currentUser['role'] ?? '') !== 'super_admin') {
$this->jsonError('Le competenze globali sono gestite dal fornitore', 403, 'SKILL_GLOBAL_READONLY');
}
}
/** Ruolo dell'organigramma nell'org corrente oppure 404 (anti-IDOR). */
private function fetchRoleOrFail(int $id): array
{
$r = Database::fetchOne('SELECT * FROM org_roles WHERE id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
if (!$r) {
$this->jsonError('Ruolo non trovato', 404, 'ROLE_NOT_FOUND');
}
return $r;
}
/** L'utente deve essere membro dell'org corrente. */
private function assertMember(int $userId): void
{
$m = Database::fetchOne(
'SELECT id FROM user_organizations WHERE user_id = ? AND organization_id = ?',
[$userId, $this->getCurrentOrgId()]
);
if (!$m) {
$this->jsonError('Utente non membro dell organizzazione', 422, 'USER_NOT_MEMBER');
}
}
/** Corso visibile (org o globale) oppure null se non fornito; jsonError se id non valido. */
private function validateCourse($raw): ?int
{
if ($raw === null || $raw === '' || (int) $raw === 0) {
return null;
}
$courseId = (int) $raw;
$c = Database::fetchOne(
'SELECT id FROM training_courses WHERE id = ? AND (organization_id = ? OR organization_id IS NULL)',
[$courseId, $this->getCurrentOrgId()]
);
if (!$c) {
$this->jsonError('Corso non valido per questa organizzazione', 422, 'INVALID_COURSE');
}
return $courseId;
}
private function clampLevel($raw): int
{
$n = (int) $raw;
if ($n < 1) { $n = 1; }
if ($n > 5) { $n = 5; }
return $n;
}
private function nullableText($v, int $max = 4000): ?string
{
if ($v === null) {
return null;
}
$v = trim((string) $v);
if ($v === '') {
return null;
}
return mb_substr($v, 0, $max);
}
private function normalizeSkill(array $s): array
{
return [
'id' => (int) $s['id'],
'organization_id' => $s['organization_id'] !== null ? (int) $s['organization_id'] : null,
'is_global' => $s['organization_id'] === null,
'name' => $s['name'],
'area' => $s['area'],
'description' => $s['description'] ?? null,
'created_at' => $s['created_at'] ?? null,
'updated_at' => $s['updated_at'] ?? null,
];
}
}
@@ -0,0 +1,161 @@
<?php
require_once __DIR__ . '/BaseController.php';
/**
* ConsultantController — Cruscotto Studio (consulente / società di consulenza).
* Vista d'insieme su TUTTE le aziende gestite dall'utente: compliance, rischi,
* incidenti e SCADENZE GENERALI aggregate. Read-only, org-scoping = visibilità
* dell'utente (super_admin = tutte le org attive; altri = le proprie membership).
*/
class ConsultantController extends BaseController
{
/**
* GET /api/consultant/portfolio
*/
public function portfolio(): void
{
$this->requireAuth();
$user = $this->getCurrentUser() ?? [];
$uid = (int) $this->getCurrentUserId();
// Aziende visibili all'utente (stessa regola di OrganizationController::list):
// super_admin = tutte; consulente = clienti del proprio studio (consulting_firm_id)
// UNION le proprie membership; altri = solo le proprie membership.
$firmId = (int) ($user['consulting_firm_id'] ?? 0);
if (($user['role'] ?? '') === 'super_admin') {
$orgs = Database::fetchAll(
"SELECT id, name, entity_type, voluntary_compliance, sector
FROM organizations WHERE is_active = 1 ORDER BY name LIMIT 200"
);
} elseif (($user['role'] ?? '') === 'consultant' && $firmId > 0) {
$orgs = Database::fetchAll(
"SELECT DISTINCT o.id, o.name, o.entity_type, o.voluntary_compliance, o.sector
FROM organizations o
LEFT JOIN user_organizations uo ON uo.organization_id = o.id AND uo.user_id = ?
WHERE o.is_active = 1 AND (o.consulting_firm_id = ? OR uo.user_id IS NOT NULL)
ORDER BY o.name LIMIT 200",
[$uid, $firmId]
);
} else {
$orgs = Database::fetchAll(
"SELECT o.id, o.name, o.entity_type, o.voluntary_compliance, o.sector
FROM organizations o
JOIN user_organizations uo ON uo.organization_id = o.id
WHERE uo.user_id = ? AND o.is_active = 1 ORDER BY o.name LIMIT 200",
[$uid]
);
}
$now = date('Y-m-d H:i:s');
$companies = [];
$allDeadlines = [];
$sumScore = 0; $nScore = 0; $overdueTotal = 0;
foreach ($orgs as $o) {
$oid = (int) $o['id'];
$asmt = Database::fetchOne(
"SELECT overall_score FROM assessments
WHERE organization_id = ? AND status = 'completed'
ORDER BY completed_at DESC LIMIT 1", [$oid]);
$score = ($asmt && $asmt['overall_score'] !== null) ? (int) round((float) $asmt['overall_score']) : null;
$risks = (int) (Database::fetchOne(
"SELECT COUNT(*) c FROM risks WHERE organization_id = ? AND status <> 'closed'", [$oid])['c'] ?? 0);
$incs = (int) (Database::fetchOne(
"SELECT COUNT(*) c FROM incidents WHERE organization_id = ? AND status NOT IN ('closed','post_mortem')", [$oid])['c'] ?? 0);
$dls = $this->collectDeadlines($oid);
$overdue = 0;
foreach ($dls as $d) {
$d['org_id'] = $oid;
$d['org_name'] = $o['name'];
$d['overdue'] = ($d['due_date'] < $now);
if ($d['overdue']) { $overdue++; }
$allDeadlines[] = $d;
}
if ($score !== null) { $sumScore += $score; $nScore++; }
$overdueTotal += $overdue;
$companies[] = [
'id' => $oid,
'name' => $o['name'],
'entity_type' => $o['entity_type'],
'voluntary_compliance' => (int) $o['voluntary_compliance'],
'sector' => $o['sector'],
'compliance_score' => $score,
'open_risks' => $risks,
'open_incidents' => $incs,
'deadlines_overdue' => $overdue,
'deadlines_upcoming' => count($dls) - $overdue,
];
}
usort($allDeadlines, fn($a, $b) => strcmp((string) $a['due_date'], (string) $b['due_date']));
$this->jsonSuccess([
'companies' => $companies,
'deadlines' => $allDeadlines,
'kpis' => [
'total_companies' => count($companies),
'avg_compliance' => $nScore ? (int) round($sumScore / $nScore) : null,
'overdue_total' => $overdueTotal,
'upcoming_total' => count($allDeadlines) - $overdueTotal,
],
]);
}
/**
* Scadenze "aperte" di una org (incidenti CSIRT, policy, trattamenti rischio,
* formazione) con orizzonte a +30 giorni ma includendo anche gli scaduti (past-due),
* così il cruscotto distingue "in ritardo" da "in arrivo". Fonti coerenti con
* DashboardController::deadlines().
*/
private function collectDeadlines(int $orgId): array
{
$out = [];
$horizon = "DATE_ADD(NOW(), INTERVAL 30 DAY)";
$inc = Database::fetchAll(
"SELECT id, title, early_warning_due, early_warning_sent_at, notification_due,
notification_sent_at, final_report_due, final_report_sent_at
FROM incidents
WHERE organization_id = ? AND is_significant = 1 AND status NOT IN ('closed','post_mortem')",
[$orgId]);
foreach ($inc as $i) {
if ($i['early_warning_due'] && !$i['early_warning_sent_at'])
$out[] = ['type'=>'incident_early_warning','title'=>"Early Warning: {$i['title']}",'due_date'=>$i['early_warning_due'],'severity'=>'critical','entity_type'=>'incident','entity_id'=>(int)$i['id']];
if ($i['notification_due'] && !$i['notification_sent_at'])
$out[] = ['type'=>'incident_notification','title'=>"Notifica CSIRT: {$i['title']}",'due_date'=>$i['notification_due'],'severity'=>'high','entity_type'=>'incident','entity_id'=>(int)$i['id']];
if ($i['final_report_due'] && !$i['final_report_sent_at'])
$out[] = ['type'=>'incident_final_report','title'=>"Report finale: {$i['title']}",'due_date'=>$i['final_report_due'],'severity'=>'medium','entity_type'=>'incident','entity_id'=>(int)$i['id']];
}
foreach (Database::fetchAll(
"SELECT id, title, next_review_date FROM policies
WHERE organization_id = ? AND next_review_date IS NOT NULL
AND next_review_date <= $horizon AND status NOT IN ('archived')", [$orgId]) as $p) {
$out[] = ['type'=>'policy_review','title'=>"Revisione policy: {$p['title']}",'due_date'=>$p['next_review_date'],'severity'=>'medium','entity_type'=>'policy','entity_id'=>(int)$p['id']];
}
foreach (Database::fetchAll(
"SELECT rt.id, rt.due_date, r.title AS risk_title FROM risk_treatments rt
JOIN risks r ON r.id = rt.risk_id
WHERE r.organization_id = ? AND rt.status IN ('planned','in_progress')
AND rt.due_date IS NOT NULL AND rt.due_date <= $horizon", [$orgId]) as $t) {
$out[] = ['type'=>'risk_treatment','title'=>"Trattamento rischio: {$t['risk_title']}",'due_date'=>$t['due_date'],'severity'=>'medium','entity_type'=>'risk_treatment','entity_id'=>(int)$t['id']];
}
foreach (Database::fetchAll(
"SELECT ta.id, tc.title, ta.due_date FROM training_assignments ta
JOIN training_courses tc ON tc.id = ta.course_id
WHERE ta.organization_id = ? AND ta.status IN ('assigned','in_progress')
AND ta.due_date IS NOT NULL AND ta.due_date <= $horizon", [$orgId]) as $tr) {
$out[] = ['type'=>'training_due','title'=>"Formazione: {$tr['title']}",'due_date'=>$tr['due_date'],'severity'=>'low','entity_type'=>'training','entity_id'=>(int)$tr['id']];
}
return $out;
}
}
+268
View File
@@ -0,0 +1,268 @@
<?php
/**
* NIS2 Agile - Demo Controller (Avatar di prodotto, product-demo-protocol v1.0.1)
*
* 5 endpoint per la modalità demo guidata dall'avatar (Modalità A, prodotto live).
* Doc: docs/DEMO_AVATAR_NIS2.md | Standard: agile-services/docs/STANDARD_PRODUCT_DEMO_PROTOCOL.md
*
* NB: questi endpoint NON usano requireAuth() — auth dedicata demo (JWT scope=demo:read-only
* / X-Internal-Key). Il riconoscimento del demo_jwt sulle API app è in BaseController (step a parte).
*/
require_once __DIR__ . '/BaseController.php';
require_once __DIR__ . '/../services/DemoSeedService.php';
class DemoController extends BaseController
{
/** Range org riservato demo NIS2 (standard §7.1) */
private const DEMO_ORG_ID = 996001;
private const DEMO_RANGE = [996000, 996999];
private const JWT_TTL_SEC = 1800; // 30 min
// SPA demo servita sul sottodominio per-prodotto (reverse-proxy AgileHub → nis2.agile.software).
// Override via env DEMO_BASE_URL (vedi demoBase()). Il widget/hub resta su dimostrazione.agile.software.
private const DEMO_BASE = 'https://nis2.dimostrazione.agile.software';
private const RL_DIR = '/tmp/nis2_demo_rl';
// ── 1) POST /api/demo/session-start ──────────────────────────────────────
public function sessionStart(): void
{
$ip = $this->clientIp();
if (!$this->rateLimit($ip)) {
header('Retry-After: 60');
$this->jsonError('Troppe richieste demo, riprova tra poco', 429, 'RATE_LIMIT_EXCEEDED');
}
$language = in_array($this->getParam('language'), ['it', 'en'], true) ? $this->getParam('language') : 'it';
$variant = in_array($this->getParam('tour_variant'), ['full', 'express'], true) ? $this->getParam('tour_variant') : 'full';
$sessionId = 'demo-' . $this->uuid4();
$hmacKeySeed = base64_encode(random_bytes(32)); // 44 char base64 standard
$orgId = self::DEMO_ORG_ID;
$expiresTs = time() + self::JWT_TTL_SEC;
$demoJwt = $this->generateJWT(0, [
'scope' => 'demo:read-only',
'org_id' => $orgId,
'demo_session_id' => $sessionId,
'exp' => $expiresTs,
]);
// ip_hash server-side (anti-spoof): sha256(ip + daily salt)
$ipHash = hash('sha256', $ip . '|' . date('Y-m-d') . '|' . JWT_SECRET);
Database::insert('demo_sessions', [
'session_id' => $sessionId,
'organization_id' => $orgId,
'hmac_key_seed' => $hmacKeySeed,
'language' => $language,
'tour_variant' => $variant,
'presentation_mode' => 'realtime',
'referrer_url' => mb_substr((string) $this->getParam('referrer_url', ''), 0, 512) ?: null,
'user_agent' => mb_substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 512) ?: null,
'ip_hash' => $ipHash,
'expires_at' => date('Y-m-d H:i:s', $expiresTs),
]);
$this->jsonSuccess([
'session_id' => $sessionId,
'demo_jwt' => $demoJwt,
'company_id' => $orgId, // alias di compatibilità standard
'organization_id' => $orgId,
// spa_iframe_url atterra su dashboard e porta il demo_jwt nel FRAGMENT (#djwt):
// common-bi.js lo imposta come token API in modo SINCRONO (prima di checkAuth),
// così la SPA si auto-autentica anche cross-origin (niente sessionStorage same-origin).
'spa_iframe_url' => $this->demoBase() . '/dashboard.html?demo=' . $sessionId . '#djwt=' . $demoJwt,
'manifest_url' => $this->demoBase() . '/api/demo/manifest?session_id=' . $sessionId,
'credentials_url' => $this->demoBase() . '/api/demo/credentials?session_id=' . $sessionId,
'expires_at' => gmdate('Y-m-d\TH:i:s\Z', $expiresTs),
'hmac_key_seed' => $hmacKeySeed,
'presentation_mode' => 'realtime',
], 'Sessione demo creata', 201);
}
// ── 2) GET /api/demo/manifest?session_id= ────────────────────────────────
public function manifest(): void
{
$session = $this->resolveSession();
if (!$session) {
$this->jsonError('Sessione demo non valida o scaduta', 401, 'DEMO_SESSION_INVALID');
}
$lang = in_array($this->getParam('language'), ['it', 'en'], true)
? $this->getParam('language') : ($session['language'] ?? 'it');
$file = PUBLIC_PATH . '/demo/nis2-tour-2026.json';
if (is_file($file)) {
$manifest = json_decode((string) file_get_contents($file), true) ?: [];
$manifest['session_id'] = $session['session_id'];
$manifest['language'] = $lang;
$this->jsonSuccess($manifest);
}
// Placeholder finché il manifest reale non è pubblicato (Fase 1, step successivo)
$this->jsonSuccess([
'tour_id' => 'demo:nis2-tour-2026',
'tour_version' => '0.1',
'session_id' => $session['session_id'],
'language' => $lang,
'tour_variant' => $session['tour_variant'] ?? 'full',
'presentation_mode' => 'realtime',
'persona_key' => 'ARIA_SUPPORT_NIS2',
'control_protocol' => 'agilehub.product-demo.v1',
'iframe_origin' => self::DEMO_BASE,
'steps' => [],
'fallback_static' => ['image_url' => null, 'audio_url_it' => null, 'audio_url_en' => null],
'_note' => 'manifest placeholder — steps in arrivo (Fase 1)',
]);
}
// ── 3) POST /api/demo/event ──────────────────────────────────────────────
public function event(): void
{
$session = $this->resolveSession();
if (!$session) {
$this->jsonError('Sessione demo non valida o scaduta', 401, 'DEMO_SESSION_INVALID');
}
$eventType = (string) $this->getParam('event_type', '');
$allowed = ['step_completed','step_skipped','tour_paused','tour_aborted','free_question_asked','cta_clicked','presentation_mode_chosen','error_encountered'];
if (!in_array($eventType, $allowed, true)) {
$this->jsonError('event_type non valido', 400, 'INVALID_EVENT_TYPE');
}
$stepId = $this->getParam('step_id');
$tsIso = mb_substr((string) $this->getParam('client_timestamp_iso', gmdate('Y-m-d\TH:i:s\Z')), 0, 40);
$meta = $this->getParam('metadata');
// Idempotente: UNIQUE (session_id, event_type, step_id, client_timestamp_iso)
try {
Database::insert('demo_events', [
'session_id' => $session['session_id'],
'event_type' => $eventType,
'step_id' => $stepId !== null ? (int) $stepId : null,
'metadata' => $meta !== null ? json_encode($meta, JSON_UNESCAPED_UNICODE) : null,
'client_timestamp_iso' => $tsIso,
]);
} catch (Throwable $e) {
// duplicato (replay idempotente) → ignora silenziosamente
}
http_response_code(204);
exit;
}
// ── 4) GET /api/demo/credentials?session_id= ─────────────────────────────
public function credentials(): void
{
$sessionId = (string) $this->getParam('session_id', '');
$session = $sessionId !== '' ? $this->loadSession($sessionId) : null;
if (!$session) {
$this->jsonError('Sessione demo non trovata o scaduta', 404, 'DEMO_SESSION_NOT_FOUND');
}
$expiresTs = strtotime($session['expires_at']);
$demoJwt = $this->generateJWT(0, [
'scope' => 'demo:read-only',
'org_id' => (int) $session['organization_id'],
'demo_session_id' => $session['session_id'],
'exp' => $expiresTs,
]);
$this->jsonSuccess([
'demo_jwt' => $demoJwt,
'company_id' => (int) $session['organization_id'],
'organization_id' => (int) $session['organization_id'],
'session_id' => $session['session_id'],
'expires_at' => gmdate('Y-m-d\TH:i:s\Z', $expiresTs),
]);
}
// ── 5) POST /api/demo/reset-dataset ──────────────────────────────────────
public function resetDataset(): void
{
$key = $_SERVER['HTTP_X_INTERNAL_KEY'] ?? '';
$expected = getenv('INTERNAL_DEMO_KEY') ?: (getenv('INTERNAL_EMAIL_KEY') ?: '');
if ($expected === '' || !hash_equals($expected, $key)) {
$this->jsonError('Non autorizzato', 401, 'UNAUTHORIZED');
}
// Reset idempotente del dataset demo (clona la golden #151 in 996001/996002).
// ?scope=demo → solo 996001 (read-only) | ?scope=sandbox → solo 996002 | default: entrambe.
$scope = (string) $this->getParam('scope', '');
$targets = $scope === 'demo' ? [996001 => DemoSeedService::TARGETS[996001]]
: ($scope === 'sandbox' ? [996002 => DemoSeedService::TARGETS[996002]]
: DemoSeedService::TARGETS);
$result = DemoSeedService::seed($targets);
$failed = array_filter($result, fn($v) => str_starts_with($v, 'errore'));
$this->jsonSuccess([
'reset' => empty($failed) ? 'done' : 'partial',
'company_ids' => array_keys($result),
'detail' => $result,
], empty($failed) ? 'Dataset demo reimpostato' : 'Reset demo parziale (vedi detail)', empty($failed) ? 200 : 207);
}
// ═══ Helpers ═════════════════════════════════════════════════════════════
/** Base URL della SPA demo (override via env DEMO_BASE_URL per switch a sottodominio). */
private function demoBase(): string
{
return getenv('DEMO_BASE_URL')
?: ($_SERVER['DEMO_BASE_URL'] ?? '')
?: ($_ENV['DEMO_BASE_URL'] ?? '')
?: self::DEMO_BASE;
}
/** Risolve la sessione da Bearer demo_jwt (preferito) o ?session_id=. */
private function resolveSession(): ?array
{
$token = $this->getBearerToken();
if ($token) {
$payload = $this->verifyJWT($token);
if ($payload && ($payload['scope'] ?? '') === 'demo:read-only' && !empty($payload['demo_session_id'])) {
return $this->loadSession((string) $payload['demo_session_id']);
}
}
$sid = (string) $this->getParam('session_id', '');
return $sid !== '' ? $this->loadSession($sid) : null;
}
private function loadSession(string $sessionId): ?array
{
$s = Database::fetchOne('SELECT * FROM demo_sessions WHERE session_id = ?', [$sessionId]);
if (!$s) return null;
if (strtotime($s['expires_at']) < time()) return null; // scaduta
return $s;
}
/** Rate-limit file-based: 5/min + 50/h per IP. true = consentito. */
private function rateLimit(string $ip): bool
{
if (!is_dir(self::RL_DIR)) @mkdir(self::RL_DIR, 0700, true);
$f = self::RL_DIR . '/' . md5($ip) . '.json';
$now = time();
$hits = is_file($f) ? (json_decode((string) file_get_contents($f), true) ?: []) : [];
$hits = array_values(array_filter($hits, fn($t) => $t > $now - 3600));
$lastMin = count(array_filter($hits, fn($t) => $t > $now - 60));
if ($lastMin >= 5 || count($hits) >= 50) return false;
$hits[] = $now;
@file_put_contents($f, json_encode($hits), LOCK_EX);
return true;
}
private function uuid4(): string
{
$b = random_bytes(16);
$b[6] = chr((ord($b[6]) & 0x0f) | 0x40);
$b[8] = chr((ord($b[8]) & 0x3f) | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));
}
private function clientIp(): string
{
$xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
if ($xff !== '') return trim(explode(',', $xff)[0]);
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
}

Some files were not shown because too many files have changed in this diff Show More