[SEC+UX] Hardening sicurezza + miglioramenti UX pre-audit
SICUREZZA: - index.php: rimosso CORS wildcard in debug mode (solo origini autorizzate) - AuthController: getClientIP() con X-Forwarded-For sicuro (proxy-aware) - AuthController: refresh token con SELECT FOR UPDATE in transazione atomica - AIService: anonimizzazione dati org nei prompt Anthropic API (no nome/fatturato) UX AUDIT-READY: - dashboard.html: gauge rinominato 'Avanzamento implementazione misure Art.21' - incidents.html: decision tree Art.23 con 5 criteri per 'Is Significant?' - policies.html: banner warning obbligatorio su bozze generate da AI - risks.html: tooltip dettagliati scala Likelihood/Impact (ISO 27005) - assessment.html: progress bar % completamento risposta domande DB: - migration 006: indici performance + audit_log immutabile (trigger) + soft delete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
0e78ec24c1
commit
782389849f
@@ -44,6 +44,20 @@
|
||||
<!-- Steps Indicator -->
|
||||
<div class="steps" id="steps-indicator"></div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;">
|
||||
<span style="font-size:0.78rem; font-weight:600; color:var(--gray-600);">Avanzamento risposta domande</span>
|
||||
<span style="font-size:0.78rem; font-weight:700; color:var(--primary);" id="progress-pct">0%</span>
|
||||
</div>
|
||||
<div style="background:var(--gray-200); border-radius:99px; height:8px; overflow:hidden;">
|
||||
<div id="progress-bar-fill" style="height:100%; background:var(--primary); border-radius:99px; transition:width 0.3s ease; width:0%;"></div>
|
||||
</div>
|
||||
<div style="font-size:0.7rem; color:var(--gray-400); margin-top:4px;">
|
||||
<span id="progress-answered">0</span> di <span id="progress-total">0</span> domande con risposta
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Question Card -->
|
||||
<div class="card mb-24">
|
||||
<div class="card-header">
|
||||
@@ -295,6 +309,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgressBar() {
|
||||
const total = questions.length;
|
||||
const answered = Object.values(responses).filter(r => r && r.answer).length;
|
||||
const pct = total > 0 ? Math.round((answered / total) * 100) : 0;
|
||||
|
||||
const pctEl = document.getElementById('progress-pct');
|
||||
const fillEl = document.getElementById('progress-bar-fill');
|
||||
const answeredEl = document.getElementById('progress-answered');
|
||||
const totalEl = document.getElementById('progress-total');
|
||||
|
||||
if (pctEl) pctEl.textContent = pct + '%';
|
||||
if (fillEl) fillEl.style.width = pct + '%';
|
||||
if (answeredEl) answeredEl.textContent = answered;
|
||||
if (totalEl) totalEl.textContent = total;
|
||||
}
|
||||
|
||||
function renderCurrentQuestion() {
|
||||
if (categories.length === 0) return;
|
||||
|
||||
@@ -365,6 +395,7 @@
|
||||
`;
|
||||
|
||||
document.getElementById('question-area').innerHTML = html;
|
||||
updateProgressBar();
|
||||
|
||||
// Navigation buttons
|
||||
document.getElementById('btn-prev').disabled = (currentCategoryIdx === 0 && currentQuestionIdx === 0);
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<div id="compliance-gauge">
|
||||
<div class="spinner-lg" style="margin:40px auto;"></div>
|
||||
</div>
|
||||
<p class="text-muted mt-8" style="font-size:0.8rem;">Punteggio complessivo di conformita' NIS2</p>
|
||||
<p class="text-muted mt-8" style="font-size:0.8rem;">Avanzamento implementazione misure Art.21 NIS2</p>
|
||||
<p class="text-muted" style="font-size:0.7rem; margin-top:2px;">Misura l'implementazione tecnica, non la conformita' legale</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -519,6 +519,37 @@
|
||||
<label class="form-label">Data e ora rilevamento <span class="required">*</span></label>
|
||||
<input type="datetime-local" class="form-input" id="inc-detected-at" value="${dtLocal}">
|
||||
</div>
|
||||
<!-- Decision tree Art.23 NIS2 - significativita' incidente -->
|
||||
<div style="background:var(--gray-50); border:1px solid var(--gray-200); border-radius:var(--border-radius); padding:14px; margin-bottom:16px;">
|
||||
<div style="font-size:0.8rem; font-weight:700; color:var(--gray-700); margin-bottom:10px; display:flex; align-items:center; gap:6px;">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16" style="color:var(--primary);"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/></svg>
|
||||
Criteri Art. 23 NIS2 — Incidente Significativo
|
||||
</div>
|
||||
<p style="font-size:0.75rem; color:var(--gray-500); margin-bottom:10px;">Un incidente e' significativo se soddisfa almeno uno dei criteri seguenti (Considerando 101 NIS2 / D.Lgs. 138/2024 art. 25):</p>
|
||||
<div style="display:grid; gap:6px;">
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:0.8rem; cursor:pointer;">
|
||||
<input type="checkbox" id="crit-users" onchange="evalSignificance()" style="accent-color:var(--primary); width:16px; height:16px;">
|
||||
<span>Ha causato o puo' causare interruzione grave dei servizi per <strong>≥500 utenti</strong></span>
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:0.8rem; cursor:pointer;">
|
||||
<input type="checkbox" id="crit-duration" onchange="evalSignificance()" style="accent-color:var(--primary); width:16px; height:16px;">
|
||||
<span>Durata dell'interruzione superiore a <strong>4 ore</strong></span>
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:0.8rem; cursor:pointer;">
|
||||
<input type="checkbox" id="crit-crossborder" onchange="evalSignificance()" style="accent-color:var(--primary); width:16px; height:16px;">
|
||||
<span>Impatto su altri Stati Membri UE (impatto transfrontaliero)</span>
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:0.8rem; cursor:pointer;">
|
||||
<input type="checkbox" id="crit-attack" onchange="evalSignificance()" style="accent-color:var(--primary); width:16px; height:16px;">
|
||||
<span>E' un attacco cyber o data breach con dati personali coinvolti</span>
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:0.8rem; cursor:pointer;">
|
||||
<input type="checkbox" id="crit-financial" onchange="evalSignificance()" style="accent-color:var(--primary); width:16px; height:16px;">
|
||||
<span>Perdita finanziaria stimata superiore a <strong>€100.000</strong></span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="significance-result" style="margin-top:10px; padding:8px 12px; border-radius:6px; font-size:0.8rem; font-weight:600; display:none;"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-check-inline">
|
||||
<input type="checkbox" id="inc-significant">
|
||||
@@ -1142,6 +1173,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Art.23 Significance Evaluator ────────────────────────────
|
||||
function evalSignificance() {
|
||||
const criteria = [
|
||||
document.getElementById('crit-users'),
|
||||
document.getElementById('crit-duration'),
|
||||
document.getElementById('crit-crossborder'),
|
||||
document.getElementById('crit-attack'),
|
||||
document.getElementById('crit-financial'),
|
||||
].filter(Boolean);
|
||||
|
||||
const checkedCount = criteria.filter(c => c.checked).length;
|
||||
const resultDiv = document.getElementById('significance-result');
|
||||
const sigCheckbox = document.getElementById('inc-significant');
|
||||
|
||||
if (!resultDiv) return;
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (checkedCount >= 1) {
|
||||
resultDiv.style.background = '#fef2f2';
|
||||
resultDiv.style.border = '1px solid #fca5a5';
|
||||
resultDiv.style.color = 'var(--danger)';
|
||||
resultDiv.innerHTML = '⚠ ' + checkedCount + ' criteri soddisfatti — l'incidente e' probabilmente <strong>SIGNIFICATIVO</strong>. Attivare le notifiche Art. 23 (24h/72h/30gg).';
|
||||
if (sigCheckbox) sigCheckbox.checked = true;
|
||||
} else {
|
||||
resultDiv.style.background = '#f0fdf4';
|
||||
resultDiv.style.border = '1px solid #bbf7d0';
|
||||
resultDiv.style.color = 'var(--secondary)';
|
||||
resultDiv.innerHTML = '✓ Nessun criterio soddisfatto — l'incidente appare <strong>NON significativo</strong> ai fini Art. 23. Verificare manualmente prima di confermare.';
|
||||
if (sigCheckbox) sigCheckbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Navigation ───────────────────────────────────────────────
|
||||
function backToList() {
|
||||
document.getElementById('view-detail').classList.add('hidden');
|
||||
|
||||
+1
-2
@@ -19,9 +19,8 @@ $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
|
||||
if (in_array($origin, CORS_ALLOWED_ORIGINS)) {
|
||||
header("Access-Control-Allow-Origin: {$origin}");
|
||||
} elseif (APP_DEBUG) {
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
}
|
||||
// NOTE: No wildcard CORS even in debug mode — use CORS_ALLOWED_ORIGINS in config
|
||||
|
||||
header("Access-Control-Allow-Methods: " . CORS_ALLOWED_METHODS);
|
||||
header("Access-Control-Allow-Headers: " . CORS_ALLOWED_HEADERS);
|
||||
|
||||
@@ -519,6 +519,14 @@
|
||||
<span class="badge ${badgeClass}">${escapeHtml(statusLabel)}</span>
|
||||
${policy.ai_generated ? '<span class="badge badge-info" style="margin-left:6px;">AI Generata</span>' : ''}
|
||||
</div>
|
||||
${policy.ai_generated && (policy.status === 'draft' || policy.status === 'review') ? `
|
||||
<div style="background:#fff7ed; border:1px solid #fed7aa; border-radius:var(--border-radius); padding:12px 16px; margin-top:12px; display:flex; gap:10px; align-items:flex-start;">
|
||||
<svg viewBox="0 0 20 20" fill="#c2410c" width="18" height="18" style="flex-shrink:0; margin-top:2px;"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
<div>
|
||||
<strong style="color:#c2410c; font-size:0.875rem;">Bozza AI — Revisione obbligatoria prima dell'approvazione</strong>
|
||||
<p style="font-size:0.78rem; color:#92400e; margin:4px 0 0;">Questo documento e' stato generato automaticamente da un sistema AI. Prima di approvarlo, verificare che sia conforme alle procedure interne, al quadro normativo applicabile e alla realta' specifica dell'organizzazione. L'AI puo' produrre contenuti imprecisi o incompleti.</p>
|
||||
</div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
<div class="detail-header-actions">
|
||||
${policy.status === 'draft' || policy.status === 'review' ? `
|
||||
|
||||
+26
-12
@@ -442,7 +442,25 @@
|
||||
const TREATMENT_STATUS_LABELS = {
|
||||
planned: 'Pianificato', in_progress: 'In Corso', completed: 'Completato', cancelled: 'Annullato'
|
||||
};
|
||||
// Scala Likelihood (probabilità annua)
|
||||
const LIKELIHOOD_LABELS = ['', 'Molto Basso', 'Basso', 'Medio', 'Alto', 'Molto Alto'];
|
||||
const LIKELIHOOD_DETAILS = [
|
||||
'',
|
||||
'1 - Remota: <1% probabilità annua (evento eccezionale)',
|
||||
'2 - Improbabile: 1-10% (evento raro ma documentato nel settore)',
|
||||
'3 - Possibile: 10-30% (può verificarsi nel corso dell'attività)',
|
||||
'4 - Probabile: 30-70% (ci si aspetta che avvenga almeno una volta l'anno)',
|
||||
'5 - Quasi Certa: >70% (si verificherà con elevata probabilità)',
|
||||
];
|
||||
// Scala Impact (danno economico / utenti impattati)
|
||||
const IMPACT_DETAILS = [
|
||||
'',
|
||||
'1 - Minimo: <€10k danno, <100 utenti, servizio ripristinato in <1h',
|
||||
'2 - Basso: €10k-50k, 100-500 utenti, ripristino <8h',
|
||||
'3 - Significativo: €50k-500k, 500-5000 utenti, ripristino <24h',
|
||||
'4 - Grave: €500k-10M, 5000-50000 utenti, ripristino <7 giorni',
|
||||
'5 - Catastrofico: >€10M, >50000 utenti, servizi critici nazionali impattati',
|
||||
];
|
||||
|
||||
// ── State ────────────────────────────────────────────────────
|
||||
let currentView = 'table';
|
||||
@@ -878,22 +896,16 @@
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Probabilita': <span id="likelihood-label">${LIKELIHOOD_LABELS[likelihood]}</span></label>
|
||||
<label class="form-label">Probabilita' <span style="color:var(--gray-400); font-size:0.75rem; font-weight:400;">(frequenza stimata annua)</span></label>
|
||||
<div class="range-value-display" id="likelihood-value">${likelihood}</div>
|
||||
<input type="range" class="form-range" id="risk-likelihood" min="1" max="5" value="${likelihood}" oninput="updateRiskScore()">
|
||||
<div class="form-range-labels">
|
||||
<span>1 - Molto Basso</span>
|
||||
<span>5 - Molto Alto</span>
|
||||
</div>
|
||||
<div style="font-size:0.75rem; color:var(--gray-500); margin-top:4px; padding:6px 8px; background:var(--gray-50); border-radius:4px;" id="likelihood-detail">${LIKELIHOOD_DETAILS[likelihood]}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Impatto: <span id="impact-label">${LIKELIHOOD_LABELS[impact]}</span></label>
|
||||
<label class="form-label">Impatto <span style="color:var(--gray-400); font-size:0.75rem; font-weight:400;">(danno economico / utenti)</span></label>
|
||||
<div class="range-value-display" id="impact-value">${impact}</div>
|
||||
<input type="range" class="form-range" id="risk-impact" min="1" max="5" value="${impact}" oninput="updateRiskScore()">
|
||||
<div class="form-range-labels">
|
||||
<span>1 - Molto Basso</span>
|
||||
<span>5 - Molto Alto</span>
|
||||
</div>
|
||||
<div style="font-size:0.75rem; color:var(--gray-500); margin-top:4px; padding:6px 8px; background:var(--gray-50); border-radius:4px;" id="impact-detail">${IMPACT_DETAILS[impact]}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -934,8 +946,10 @@
|
||||
|
||||
document.getElementById('likelihood-value').textContent = l;
|
||||
document.getElementById('impact-value').textContent = i;
|
||||
document.getElementById('likelihood-label').textContent = LIKELIHOOD_LABELS[l];
|
||||
document.getElementById('impact-label').textContent = LIKELIHOOD_LABELS[i];
|
||||
const lDetail = document.getElementById('likelihood-detail');
|
||||
const iDetail = document.getElementById('impact-detail');
|
||||
if (lDetail) lDetail.textContent = LIKELIHOOD_DETAILS[l];
|
||||
if (iDetail) iDetail.textContent = IMPACT_DETAILS[i];
|
||||
document.getElementById('score-preview-value').textContent = score;
|
||||
document.getElementById('score-preview-value').style.color = getScoreColorForRisk(score);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user