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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- #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>
- 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>
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>
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>