feat: comprehensive security, performance, UX and deployment audit

Security:
- CORS restricted to explicit methods/headers, configurable via CORS_ORIGINS env
- Auth added to /reports/{rid}/html and /compliance-report endpoints
- Ownership check on report downloads
- Rate limiting on login (10 attempts/5min per IP with threading.Lock)
- Non-root container user (agent via gosu entrypoint)
- Nginx security headers (X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy, Permissions-Policy)
- .gitignore and .dockerignore for secret leak prevention
- .env.example with documentation

Performance:
- 16 SQLite indexes on foreign keys and frequently queried columns
- Pagination on chat messages (100), reports (50), audit log (100)
- subprocess.run wrapped in run_in_executor (3 async handlers)
- asyncio.wait_for timeouts on GenAI calls (300s) and Chromium PDF (120s)
- Thread pool reduced to 10 workers
- Code splitting with React.lazy (bundle: 1.3MB -> ~550KB initial)
- React.memo on TreeItem (recursive compartment tree)

Error handling:
- 13 bare except clauses replaced with logged Exception handlers
- Graceful shutdown handler (terminates subprocesses + executor)
- File upload validation (50MB max, extension whitelist per endpoint)
- Health check expanded (version, uptime, db_ok)
- Cache-Control on /api/genai/models (1h)
- Auto-cleanup audit_log > 30 days

Dead code removed:
- DownloadsPage.tsx, StubPage.tsx, MfaPage.tsx (moved to UsersPage)
- Legacy frontend/ directory
- 19 unused i18n keys, dist/ removed from git tracking

UX & i18n:
- 8 alert() calls replaced with styled error states (TerraformPage)
- 50+ hardcoded Portuguese strings localized to i18n (pt/en) across 11 files
- aria-label on all icon-only buttons (Chat, Explorer, Sidebar)
- focus-visible CSS for keyboard navigation
- Responsive grid fix (360px -> 280px for mobile)
- aria-hidden on decorative SVGs

Deployment:
- Docker resource limits (backend 4G, frontend 512M)
- Log rotation (json-file, 10MB x 3)
- Terraform version parameterized via ARG
This commit is contained in:
nogueiraguh
2026-03-20 13:48:30 -03:00
parent 61fbb3861d
commit fbcb1966d5
35 changed files with 1301 additions and 4903 deletions

View File

@@ -536,45 +536,59 @@ function CustomTooltip({ active, payload, label }: { active?: boolean; payload?:
export default function ReportsPage() {
const { t } = useI18n();
const ociCfg = useAppStore((s) => s.ociCfg);
const ociRegions = useAppStore((s) => s.ociRegions);
const adbCfg = useAppStore((s) => s.adbCfg);
const store = useAppStore();
const ociCfg = store.ociCfg;
const ociRegions = store.ociRegions;
const adbCfg = store.adbCfg;
/* ── Reports list ── */
const [reports, setReports] = useState<Report[]>([]);
const [reportsLoading, setReportsLoading] = useState(true);
/* ── Form state ── */
const [formOpen, setFormOpen] = useState(true);
const [ociVal, setOciVal] = useState('');
const [level, setLevel] = useState<1 | 2>(2);
const [selectedRegions, setSelectedRegions] = useState<string[]>([]);
const [obp, setObp] = useState(true);
const [raw, setRaw] = useState(false);
const [redact, setRedact] = useState(true);
/* ── Form state (from store) ── */
const formOpen = store.rptFormOpen;
const setFormOpen = store.setRptFormOpen;
const ociVal = store.rptOciVal;
const setOciVal = store.setRptOciVal;
const level = store.rptLevel;
const setLevel = store.setRptLevel;
const selectedRegions = store.rptSelectedRegions;
const setSelectedRegions = store.setRptSelectedRegions;
const obp = store.rptObp;
const setObp = store.setRptObp;
const raw = store.rptRaw;
const setRaw = store.setRptRaw;
const redact = store.rptRedact;
const setRedact = store.setRptRedact;
/* ── Tracking / progress ── */
const [trackingId, setTrackingId] = useState<string | null>(null);
/* ── Tracking / progress (from store) ── */
const trackingId = store.rptTrackingId;
const setTrackingId = store.setRptTrackingId;
const [progress, setProgress] = useState<ReportProgress | null>(null);
const [logExpanded, setLogExpanded] = useState(false);
/* ── Selected report + KPI ── */
const [selectedRid, setSelectedRid] = useState('');
/* ── Selected report + KPI (from store) ── */
const selectedRid = store.rptSelectedRid;
const setSelectedRid = store.setRptSelectedRid;
const [kpi, setKpi] = useState<ReportSummary | null>(null);
const [kpiLoading, setKpiLoading] = useState(false);
/* ── Report files ── */
const [files, setFiles] = useState<ReportFile[]>([]);
/* ── Embedding state ── */
const [embedAdb, setEmbedAdb] = useState('');
const [embedTable, setEmbedTable] = useState('');
/* ── Embedding state (from store) ── */
const embedAdb = store.rptEmbedAdb;
const setEmbedAdb = store.setRptEmbedAdb;
const embedTable = store.rptEmbedTable;
const setEmbedTable = store.setRptEmbedTable;
const [embedLoading, setEmbedLoading] = useState(false);
const [embedMsg, setEmbedMsg] = useState<{ type: 's' | 'e'; text: string } | null>(null);
/* ── HTML iframe ── */
const [showIframe, setShowIframe] = useState(false);
const [showCompliance, setShowCompliance] = useState(false);
/* ── HTML iframe (from store) ── */
const showIframe = store.rptShowIframe;
const setShowIframe = store.setRptShowIframe;
const showCompliance = store.rptShowCompliance;
const setShowCompliance = store.setRptShowCompliance;
const [complianceReady, setComplianceReady] = useState(false);
const [complianceGenerating, setComplianceGenerating] = useState(false);
@@ -1305,16 +1319,16 @@ export default function ReportsPage() {
<div className="flex items-center gap-2">
{showCompliance ? <Eye size={14} style={{ color: 'var(--gn)' }} /> : <EyeOff size={14} style={{ color: 'var(--t4)' }} />}
<span className="text-[.76rem] font-semibold" style={{ color: 'var(--t2)' }}>
LAD A-Team CIS Compliance Report
{t('rpt.complianceTitle')}
</span>
{complianceReady && (
<span className="text-[.6rem] px-2 py-0.5 rounded-full font-semibold" style={{ background: 'var(--gnl)', color: 'var(--gn)' }}>
+ Remediation
{t('rpt.complianceRemediation')}
</span>
)}
{complianceGenerating && (
<span className="text-[.6rem] px-2 py-0.5 rounded-full font-semibold flex items-center gap-1" style={{ background: 'var(--bll)', color: 'var(--bl)' }}>
<Loader2 size={10} className="animate-spin" /> Gerando...
<Loader2 size={10} className="animate-spin" /> {t('rpt.complianceGeneratingShort')}
</span>
)}
</div>
@@ -1338,7 +1352,7 @@ export default function ReportsPage() {
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[.68rem] font-medium cursor-pointer transition-colors no-underline"
style={{ background: 'var(--ac)', color: '#fff' }}
>
<Download size={12} /> Download ZIP
<Download size={12} /> {t('rpt.complianceDownloadZip')}
</a>
<button
onClick={startComplianceGeneration}
@@ -1346,26 +1360,26 @@ export default function ReportsPage() {
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[.68rem] font-medium cursor-pointer transition-colors"
style={{ background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)' }}
>
<RefreshCw size={12} /> Regenerar
<RefreshCw size={12} /> {t('rpt.complianceRegenerate')}
</button>
</div>
</div>
) : complianceGenerating ? (
<div className="flex flex-col items-center justify-center py-16 gap-3">
<Loader2 size={32} className="animate-spin" style={{ color: 'var(--gn)' }} />
<p className="text-[.82rem] font-medium" style={{ color: 'var(--t2)' }}>Gerando Compliance Report...</p>
<p className="text-[.7rem]" style={{ color: 'var(--t4)' }}>Buscando remediações na Knowledge Base CIS</p>
<p className="text-[.82rem] font-medium" style={{ color: 'var(--t2)' }}>{t('rpt.complianceGenerating')}</p>
<p className="text-[.7rem]" style={{ color: 'var(--t4)' }}>{t('rpt.complianceFetchingKB')}</p>
</div>
) : (
<div className="flex flex-col items-center justify-center py-12 gap-3">
<ShieldCheck size={32} style={{ color: 'var(--t4)', opacity: 0.5 }} />
<p className="text-[.78rem]" style={{ color: 'var(--t3)' }}>Report ainda não gerado</p>
<p className="text-[.78rem]" style={{ color: 'var(--t3)' }}>{t('rpt.complianceNotGenerated')}</p>
<button
onClick={startComplianceGeneration}
className="flex items-center gap-2 px-4 py-2 rounded-lg text-[.76rem] font-semibold cursor-pointer"
style={{ background: 'var(--gn)', color: '#fff' }}
>
<ShieldCheck size={14} /> Gerar Compliance Report
<ShieldCheck size={14} /> {t('rpt.complianceGenerate')}
</button>
</div>
)