mihir2007/Cyber-Risk
0
1"""2app.py3------4Production-grade Streamlit frontend dashboard for the AI-Powered Continuous5Cyber Risk Quantification (CRQ) Platform.6 7Connects to the FastAPI backend (default: http://127.0.0.1:8000) and exposes:8- Tab 1: Executive Risk Overview (EAL, VaR 95/99, Loss Exceedance Curve, Riskiest Assets)9- Tab 2: Budget Optimization (MILP Knapsack solver with ROSI and control recommendations)10- Tab 3: Regulatory Compliance (RBI CSF, SEBI CSCRF, NIST CSF 2.0 readiness & gap analysis)11- Tab 4: Asset Inventory (Enterprise IT/OT telemetry, vulnerabilities, and financial loss rates)12"""13 14from __future__ import annotations15 16import json17from typing import Any18 19import pandas as pd20import plotly.express as px21import plotly.graph_objects as go22import requests23import streamlit as st24 25# --------------------------------------------------------------------------- #26# Page Configuration & Global Styling27# --------------------------------------------------------------------------- #28st.set_page_config(29 page_title="Cyber Risk Quantification Platform",30 page_icon="๐ก๏ธ",31 layout="wide",32 initial_sidebar_state="expanded",33)34 35# Custom CSS for executive-grade aesthetics and high-contrast rendering36st.markdown(37 """38 <style>39 /* Metric Card container styling - Explicit light container */40 div[data-testid="stMetric"] {41 background: #FFFFFF !important;42 border: 1px solid #E2E8F0 !important;43 border-radius: 10px !important;44 padding: 16px 20px !important;45 box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06) !important;46 transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease !important;47 }48 div[data-testid="stMetric"]:hover {49 border-color: #CBD5E1 !important;50 box-shadow: 0 4px 12px rgba(15, 23, 42, 0.10) !important;51 transform: translateY(-2px);52 }53 div[data-testid="stMetricLabel"] {54 font-size: 0.88rem !important;55 font-weight: 600 !important;56 color: #475569 !important;57 text-transform: uppercase;58 letter-spacing: 0.05em;59 }60 div[data-testid="stMetricLabel"] p,61 div[data-testid="stMetricLabel"] label,62 div[data-testid="stMetricLabel"] > div {63 color: #475569 !important;64 }65 div[data-testid="stMetricValue"] {66 font-size: 1.85rem !important;67 font-weight: 700 !important;68 color: #0F172A !important;69 }70 div[data-testid="stMetricValue"] > div,71 div[data-testid="stMetricValue"] span {72 color: #0F172A !important;73 }74 75 /* Explicit delta tag styling */76 div[data-testid="stMetricDelta"] {77 font-weight: 600 !important;78 }79 div[data-testid="stMetricDelta"] svg {80 fill: currentColor !important;81 }82 div[data-testid="stMetricDelta"] svg[data-testid="stMetricDeltaIcon-Up"],83 div[data-testid="stMetricDelta"]:has(svg[data-testid="stMetricDeltaIcon-Up"]),84 div[data-testid="stMetricDelta"]:has(svg[data-testid="stMetricDeltaIcon-Up"]) > div {85 color: #16A34A !important;86 }87 div[data-testid="stMetricDelta"] svg[data-testid="stMetricDeltaIcon-Down"],88 div[data-testid="stMetricDelta"]:has(svg[data-testid="stMetricDeltaIcon-Down"]),89 div[data-testid="stMetricDelta"]:has(svg[data-testid="stMetricDeltaIcon-Down"]) > div {90 color: #DC2626 !important;91 }92 93 /* Gaps & Alert Callout Box - High Contrast */94 .gap-card {95 background: #FEF2F2 !important;96 border: 1px solid #FCA5A5 !important;97 border-left: 4px solid #DC2626 !important;98 border-radius: 8px;99 padding: 14px 18px;100 margin-bottom: 12px;101 box-shadow: 0 1px 3px rgba(153, 27, 27, 0.05);102 }103 .gap-header {104 font-weight: 700;105 font-size: 0.95rem;106 color: #991B1B !important;107 display: flex;108 justify-content: space-between;109 }110 .gap-desc {111 color: #7F1D1D !important;112 font-size: 0.88rem;113 margin-top: 6px;114 line-height: 1.4;115 }116 .gap-controls {117 margin-top: 8px;118 font-size: 0.82rem;119 color: #991B1B !important;120 font-weight: 500;121 }122 .gap-controls b {123 color: #7F1D1D !important;124 }125 126 /* Tier Badges */127 .badge-critical {128 background-color: rgba(239, 68, 68, 0.12);129 color: #DC2626;130 border: 1px solid rgba(239, 68, 68, 0.35);131 padding: 2px 8px;132 border-radius: 4px;133 font-weight: 600;134 }135 .badge-medium {136 background-color: rgba(245, 158, 11, 0.12);137 color: #D97706;138 border: 1px solid rgba(245, 158, 11, 0.35);139 padding: 2px 8px;140 border-radius: 4px;141 font-weight: 600;142 }143 .badge-low {144 background-color: rgba(16, 185, 129, 0.12);145 color: #059669;146 border: 1px solid rgba(16, 185, 129, 0.35);147 padding: 2px 8px;148 border-radius: 4px;149 font-weight: 600;150 }151 </style>152 """,153 unsafe_allow_html=True,154)155 156 157 158# --------------------------------------------------------------------------- #159# Helper Functions: Currency Formatting & Safe HTTP Client160# --------------------------------------------------------------------------- #161def format_inr(value: float | int | None) -> str:162 """163 Formats a raw numeric value into Rupee notation:164 - Crores (โน Cr) for values >= โน1,00,00,000 (10M)165 - Lakhs (โน L) for values >= โน1,00,000 (100K)166 - Indian comma grouping for smaller numbers167 """168 if value is None:169 return "โน0.00"170 171 val = float(value)172 sign = "-" if val < 0 else ""173 abs_val = abs(val)174 175 if abs_val >= 10_000_000:176 return f"{sign}โน{abs_val / 10_000_000:.2f} Cr"177 elif abs_val >= 100_000:178 return f"{sign}โน{abs_val / 100_000:.2f} L"179 elif abs_val >= 1_000:180 integer_part, _, decimal_part = f"{abs_val:.2f}".partition(".")181 if len(integer_part) > 3:182 last_three = integer_part[-3:]183 remaining = integer_part[:-3]184 groups = []185 while len(remaining) > 2:186 groups.insert(0, remaining[-2:])187 remaining = remaining[:-2]188 if remaining:189 groups.insert(0, remaining)190 formatted = ",".join(groups + [last_three])191 else:192 formatted = integer_part193 return f"{sign}โน{formatted}.{decimal_part}"194 else:195 return f"{sign}โน{abs_val:.2f}"196 197 198def safe_get(url: str, params: dict[str, Any] | None = None, timeout: float = 12.0) -> tuple[bool, Any, str]:199 """200 Executes a safe GET request with custom timeout and structured error handling.201 Returns: (is_success, response_json_or_none, error_message_string)202 """203 try:204 response = requests.get(url, params=params, timeout=timeout)205 if response.status_code == 200:206 return True, response.json(), ""207 else:208 detail = ""209 try:210 detail = response.json().get("detail", response.text)211 except Exception:212 detail = response.text213 return False, None, f"HTTP {response.status_code}: {detail}"214 except requests.exceptions.ConnectionError:215 return False, None, f"Connection refused to backend at {url}. Ensure FastAPI is running."216 except requests.exceptions.Timeout:217 return False, None, f"Request timed out after {timeout}s while connecting to {url}."218 except requests.exceptions.RequestException as exc:219 return False, None, f"Network error: {str(exc)}"220 221 222def safe_post(url: str, json_data: dict[str, Any] | None = None, timeout: float = 60.0) -> tuple[bool, Any, str]:223 """224 Executes a safe POST request with custom timeout (default 60s) and structured error handling.225 Returns: (is_success, response_json_or_none, error_message_string)226 """227 try:228 response = requests.post(url, json=json_data, timeout=timeout)229 if response.status_code == 200:230 return True, response.json(), ""231 else:232 detail = ""233 try:234 detail = response.json().get("detail", response.text)235 except Exception:236 detail = response.text237 return False, None, f"HTTP {response.status_code}: {detail}"238 except requests.exceptions.ConnectionError:239 return False, None, f"Connection refused to backend at {url}. Ensure FastAPI is running."240 except requests.exceptions.Timeout:241 return False, None, f"Request timed out after {timeout}s while connecting to {url}."242 except requests.exceptions.RequestException as exc:243 return False, None, f"Network error: {str(exc)}"244 245 246@st.cache_data(ttl=300)247def get_cached_budget_optimization(248 backend_url: str, budget_inr: float249) -> tuple[bool, Any, str]:250 """251 Caches budget solver responses keyed on budget_inr with 5-minute TTL252 to prevent redundant backend solves on previously queried spend limits.253 """254 return safe_post(255 f"{backend_url}/api/optimize/budget",256 json_data={"budget_inr": float(budget_inr)},257 timeout=60.0,258 )259 260 261# --------------------------------------------------------------------------- #262# Sidebar: Configuration & Health Telemetry263# --------------------------------------------------------------------------- #264with st.sidebar:265 st.markdown("### ๐ก๏ธ CRQ Platform")266 st.caption("AI-Powered Continuous Cyber Risk Quantification")267 st.markdown("---")268 269 backend_url = st.text_input(270 "FastAPI Backend URL",271 value="http://127.0.0.1:8000",272 help="Base URL of the FastAPI server providing quantification and optimization APIs.",273 ).rstrip("/")274 275 # Check connection health276 is_healthy, health_data, health_err = safe_get(f"{backend_url}/", timeout=3.0)277 278 if is_healthy:279 st.success("๐ข **Backend Connected**")280 with st.expander("Server Telemetry", expanded=False):281 st.json(health_data)282 else:283 st.error("๐ด **Backend Offline**")284 st.caption(f"`{health_err}`")285 st.info("Start the API server via:\n```bash\nuvicorn main:app --reload --port 8000\n```")286 287 st.markdown("---")288 st.markdown("#### Database Administration")289 if st.button("๐ฑ Seed Synthetic Telemetry", use_container_width=True):290 with st.spinner("Seeding enterprise database..."):291 ok, seed_res, seed_err = safe_post(f"{backend_url}/api/seed", timeout=60.0)292 if ok:293 st.toast("Database seeded successfully!", icon="โ
")294 st.cache_data.clear()295 st.rerun()296 else:297 st.error(f"Seeding failed: {seed_err}")298 299 if st.button("๐ฅ Ingest Kaggle CVE Dataset", use_container_width=True):300 with st.spinner("Ingesting Kaggle CVE, CISA KEV & EPSS dataset..."):301 ok, kaggle_res, kaggle_err = safe_post(302 f"{backend_url}/api/seed/kaggle", timeout=60.0303 )304 if ok:305 count = kaggle_res.get("vulnerabilities_ingested", 0)306 mapped = len(kaggle_res.get("mapped_assets", []))307 st.toast(308 f"Ingested {count} Kaggle CVEs across {mapped} assets!",309 icon="โ
",310 )311 st.cache_data.clear()312 st.rerun()313 else:314 st.error(f"Kaggle ingestion failed: {kaggle_err}")315 316 if st.button("๐ Refresh Application Data", use_container_width=True):317 st.cache_data.clear()318 st.rerun()319 320 321 st.markdown("---")322 st.caption("Standards: RBI CSF โข SEBI CSCRF โข NIST CSF 2.0")323 324 325# --------------------------------------------------------------------------- #326# Main Header327# --------------------------------------------------------------------------- #328st.title("๐ก๏ธ Cyber Risk Quantification Platform")329st.markdown(330 "Enterprise financial loss modeling via **Monte Carlo simulations**, "331 "**Dijkstra attack path exposure**, and **MILP budget optimization**."332)333 334# --------------------------------------------------------------------------- #335# Application Tabs336# --------------------------------------------------------------------------- #337tab1, tab2, tab3, tab4 = st.tabs(338 [339 "๐ Executive Risk Overview",340 "๐ฏ Budget Optimization",341 "โ๏ธ Regulatory Compliance",342 "๐ฅ๏ธ Asset Inventory & Lineage",343 ]344)345 346 347# =========================================================================== #348# TAB 1: EXECUTIVE RISK OVERVIEW349# =========================================================================== #350with tab1:351 st.markdown("### ๐ Enterprise Financial Risk Exposure")352 st.caption("Aggregate loss metrics quantified in INR (โน) across 10,000 Monte Carlo stochastic scenarios.")353 354 ok, summary_data, err = safe_get(f"{backend_url}/api/quantification/enterprise")355 356 if not ok:357 st.warning(f"โ ๏ธ Unable to load enterprise quantification: {err}")358 if "No assets found" in (err or ""):359 st.info("The database appears empty. Click **'Seed Synthetic Telemetry'** in the sidebar to populate initial data.")360 else:361 # Indirect Data Leak Detection Alert Callout362 unauth_leaks = [363 f for f in summary_data.get("identified_leak_vectors", [])364 if (not f.get("is_authorized", True) or not f.get("has_user_consent", True) or "Unauthorized" in f.get("detection_status", ""))365 ]366 if unauth_leaks or summary_data.get("unauthorized_subprocessor_count", 0) > 0:367 first_leak = unauth_leaks[0] if unauth_leaks else {}368 origin_h = first_leak.get("origin_hostname", "cust-db-primary")369 inter_h = first_leak.get("intermediary_hostname", "analytics-integration-gw")370 dest_h = first_leak.get("destination_hostname", "third-party-marketing-sync")371 st.error(372 f"โ ๏ธ **Indirect Data Leak Detected:** Node C (`{dest_h}`) is exfiltrating records originating "373 f"from Node A (`{origin_h}`) via unauthorized delegation through Node B (`{inter_h}`)."374 )375 376 # Top KPI Metric Cards377 col1, col2, col3, col4, col5 = st.columns(5)378 379 with col1:380 st.metric(381 label="Expected Annual Loss (EAL)",382 value=format_inr(summary_data.get("total_eal_inr")),383 help="Mean annualized financial loss across direct disruption, data theft, and response costs.",384 )385 with col2:386 st.metric(387 label="95% Value at Risk (VaR 95)",388 value=format_inr(summary_data.get("var_95_inr")),389 help="Maximum expected loss within a single year at a 95% confidence level (1-in-20 year loss).",390 )391 with col3:392 st.metric(393 label="99% Value at Risk (VaR 99)",394 value=format_inr(summary_data.get("var_99_inr")),395 help="Severe catastrophic single-year loss threshold at a 99% confidence level (1-in-100 year event).",396 )397 with col4:398 st.metric(399 label="Regulatory Fine Exposure",400 value=format_inr(summary_data.get("total_regulatory_fine_exposure_inr")),401 help="Total statutory penalty exposure under RBI CSF, SEBI CSCRF, and DPDP Act provisions.",402 )403 with col5:404 shadow_exp = summary_data.get("shadow_leakage_exposure_inr", 0.0)405 unauth_cnt = summary_data.get("unauthorized_subprocessor_count", 0)406 st.metric(407 label="Shadow Leakage Risk Exposure",408 value=format_inr(shadow_exp),409 delta=f"{unauth_cnt} Unauthorized Leak{'s' if unauth_cnt != 1 else ''}" if unauth_cnt > 0 else "0 Leaks",410 delta_color="inverse" if unauth_cnt > 0 else "normal",411 help="Annualized financial loss exposure attributable to unauthorized third-party sub-processor delegation and indirect data leakage under DPDPA Section 8(4).",412 )413 414 st.markdown("---")415 416 # Visualizations: Loss Exceedance Curve & Riskiest Assets417 chart_col1, chart_col2 = st.columns([3, 2])418 419 with chart_col1:420 st.markdown("#### Loss Exceedance Curve (LEC)")421 st.caption("Probability of enterprise losses exceeding designated financial thresholds over varying return horizons.")422 423 lec_points = summary_data.get("loss_exceedance_curve", [])424 if lec_points:425 df_lec = pd.DataFrame(lec_points)426 df_lec["loss_formatted"] = df_lec["loss_inr"].apply(format_inr)427 428 fig_lec = go.Figure()429 fig_lec.add_trace(430 go.Scatter(431 x=df_lec["return_period_years"],432 y=df_lec["loss_inr"],433 mode="lines+markers",434 name="Aggregate Loss Exceedance",435 line=dict(color="#6366F1", width=3),436 marker=dict(size=7, color="#818CF8"),437 fill="tozeroy",438 fillcolor="rgba(99, 102, 241, 0.12)",439 hovertemplate="<b>Return Period:</b> %{x} Years<br><b>Loss:</b> %{customdata}<extra></extra>",440 customdata=df_lec["loss_formatted"],441 )442 )443 fig_lec.update_layout(444 template="plotly_white",445 paper_bgcolor="#FFFFFF",446 plot_bgcolor="#FFFFFF",447 font=dict(color="#1E293B"),448 margin=dict(l=20, r=20, t=30, b=30),449 xaxis=dict(450 title="Return Period (Years)",451 showgrid=True,452 gridcolor="#E2E8F0",453 color="#1E293B",454 type="log",455 ),456 yaxis=dict(457 title="Loss Threshold (โน)",458 showgrid=True,459 gridcolor="#E2E8F0",460 color="#1E293B",461 ),462 height=360,463 )464 st.plotly_chart(fig_lec, use_container_width=True)465 else:466 st.info("No loss exceedance curve data available.")467 468 with chart_col2:469 st.markdown("#### Top Riskiest Assets by Criticality")470 st.caption("Assets contributing highest individual Expected Annual Loss (EAL) in โน.")471 472 top_assets = summary_data.get("top_5_riskiest_assets", [])473 if top_assets:474 df_top = pd.DataFrame(top_assets)475 df_top["formatted_eal"] = df_top["eal_inr"].apply(format_inr)476 477 color_map = {478 "Critical": "#EF4444",479 "Medium": "#F59E0B",480 "Low": "#10B981",481 }482 483 fig_top = px.bar(484 df_top,485 x="eal_inr",486 y="hostname",487 orientation="h",488 color="tier",489 color_discrete_map=color_map,490 labels={"eal_inr": "EAL (โน)", "hostname": "Asset", "tier": "Tier"},491 hover_data={492 "eal_inr": False,493 "formatted_eal": True,494 "asset_type": True,495 "annual_event_frequency": ":.3f",496 },497 )498 fig_top.update_layout(499 template="plotly_white",500 paper_bgcolor="#FFFFFF",501 plot_bgcolor="#FFFFFF",502 font=dict(color="#1E293B"),503 margin=dict(l=20, r=20, t=30, b=30),504 xaxis=dict(showgrid=True, gridcolor="#E2E8F0", color="#1E293B"),505 yaxis=dict(autorange="reversed", color="#1E293B"),506 height=360,507 showlegend=True,508 legend=dict(509 orientation="h",510 yanchor="bottom",511 y=1.02,512 xanchor="right",513 x=1,514 font=dict(color="#1E293B"),515 ),516 )517 st.plotly_chart(fig_top, use_container_width=True)518 else:519 st.info("No asset risk contributions returned.")520 521 522# =========================================================================== #523# TAB 2: BUDGET OPTIMIZATION (MILP KNAPSACK)524# =========================================================================== #525with tab2:526 st.markdown("### ๐ฏ Security Budget Allocation & Knapsack Optimization")527 st.caption("Solves a 0/1 Mixed-Integer Linear Program (MILP) to identify the optimal security investments maximizing ROSI under budget caps.")528 529 if "current_budget" not in st.session_state:530 st.session_state["current_budget"] = 5_000_000531 532 # Budget Optimization Form to eliminate slider request flooding533 with st.form("budget_optimization_form"):534 slider_col, _ = st.columns([3, 1])535 with slider_col:536 selected_budget = st.slider(537 "Target Security Investment Budget (โน)",538 min_value=500_000,539 max_value=15_000_000,540 value=int(st.session_state["current_budget"]),541 step=500_000,542 format="%d",543 help="Select budget ceiling for PuLP Mixed Integer Linear Programming solver.",544 )545 st.markdown(f"**Selected Spend Limit:** `{format_inr(selected_budget)}`")546 547 submitted = st.form_submit_button("๐ Run Portfolio Optimization", type="primary")548 if submitted:549 st.session_state["current_budget"] = selected_budget550 551 active_budget = float(st.session_state["current_budget"])552 553 # Trigger optimization with client-side caching554 with st.spinner("Solving Mixed Integer Linear Program (MILP)..."):555 opt_ok, opt_data, opt_err = get_cached_budget_optimization(556 backend_url, active_budget557 )558 559 if not opt_ok:560 st.warning(f"โ ๏ธ Optimization failed: {opt_err}")561 else:562 # Optimization Metrics563 m1, m2, m3, m4 = st.columns(4)564 with m1:565 st.metric(566 label="Total Capital Deployed",567 value=format_inr(opt_data.get("total_spent_inr")),568 delta=f"Remaining: {format_inr(opt_data.get('remaining_budget_inr'))}",569 delta_color="off",570 )571 with m2:572 st.metric(573 label="Baseline EAL (Pre-Investment)",574 value=format_inr(opt_data.get("baseline_eal_inr")),575 )576 with m3:577 st.metric(578 label="Net Risk Reduction (ฮEAL)",579 value=format_inr(opt_data.get("net_risk_reduction_inr")),580 delta=f"-{format_inr(opt_data.get('net_risk_reduction_inr'))}",581 delta_color="inverse",582 )583 with m4:584 st.metric(585 label="Return on Security Investment (ROSI)",586 value=f"{opt_data.get('rosi_percent', 0.0):.1f}%",587 help="Net Risk Reduction minus Capital Cost divided by Capital Cost.",588 )589 590 st.markdown("---")591 592 # Portfolio Comparison Chart & Selected Controls Table593 p_col1, p_col2 = st.columns([2, 3])594 595 with p_col1:596 st.markdown("#### Portfolio Risk Reduction Comparison")597 comp_df = pd.DataFrame(598 [599 {"Stage": "Baseline Risk", "Amount": opt_data.get("baseline_eal_inr", 0)},600 {"Stage": "Projected Risk", "Amount": opt_data.get("projected_eal_inr", 0)},601 {"Stage": "Capital Deployed", "Amount": opt_data.get("total_spent_inr", 0)},602 ]603 )604 comp_df["Formatted"] = comp_df["Amount"].apply(format_inr)605 606 fig_comp = px.bar(607 comp_df,608 x="Stage",609 y="Amount",610 color="Stage",611 color_discrete_sequence=["#EF4444", "#10B981", "#6366F1"],612 text="Formatted",613 )614 fig_comp.update_layout(615 template="plotly_white",616 paper_bgcolor="#FFFFFF",617 plot_bgcolor="#FFFFFF",618 font=dict(color="#1E293B"),619 showlegend=False,620 height=350,621 yaxis=dict(title="INR (โน)", showgrid=True, gridcolor="#E2E8F0", color="#1E293B"),622 xaxis=dict(color="#1E293B"),623 margin=dict(l=10, r=10, t=20, b=20),624 )625 st.plotly_chart(fig_comp, use_container_width=True)626 627 with p_col2:628 st.markdown("#### Recommended Security Controls")629 controls = opt_data.get("selected_controls", [])630 if controls:631 df_ctrl = pd.DataFrame(controls)632 df_ctrl["Cost"] = df_ctrl["cost_inr"].apply(format_inr)633 df_ctrl["Efficacy"] = df_ctrl["likelihood_reduction"].apply(lambda v: f"{v*100:.0f}%")634 df_ctrl["Marginal Benefit"] = df_ctrl["marginal_eal_reduction_inr"].apply(format_inr)635 636 display_df = df_ctrl[["code", "name", "target_tier", "Cost", "Efficacy", "Marginal Benefit"]].rename(637 columns={638 "code": "Code",639 "name": "Control Name",640 "target_tier": "Scope",641 }642 )643 st.dataframe(display_df, use_container_width=True, hide_index=True)644 else:645 st.info("No security controls fit within the allocated spend constraint.")646 647 648# =========================================================================== #649# TAB 3: REGULATORY COMPLIANCE (RBI / SEBI / NIST)650# =========================================================================== #651with tab3:652 st.markdown("### โ๏ธ Regulatory Readiness & Compliance Gap Audit")653 st.caption("Continuous benchmarking against Reserve Bank of India (RBI CSF), SEBI CSCRF, NIST CSF 2.0, ISO 27001:2022, and ISO 42001:2023 frameworks.")654 655 comp_ok, comp_data, comp_err = safe_get(f"{backend_url}/api/compliance/status")656 657 if not comp_ok:658 st.warning(f"โ ๏ธ Unable to load compliance assessment: {comp_err}")659 else:660 # Compliance KPI Cards661 rbi_pct = comp_data.get("rbi_compliance_index_percent", 0.0)662 sebi_pct = comp_data.get("sebi_compliance_index_percent", 0.0)663 nist_pct = comp_data.get("nist_csf_compliance_index_percent", 0.0)664 iso27001_pct = comp_data.get("iso27001_compliance_index_percent", 0.0)665 iso42001_pct = comp_data.get("iso42001_compliance_index_percent", 0.0)666 overall_pct = comp_data.get("overall_compliance_index_percent", 0.0)667 penalty_exposure = comp_data.get("estimated_regulatory_penalty_exposure_inr", 0.0)668 669 c1, c2, c3, c4, c5, c6 = st.columns(6)670 with c1:671 st.metric(672 label="RBI CSF Readiness",673 value=f"{rbi_pct:.1f}%",674 delta=f"{rbi_pct - 100:.1f}% to target",675 delta_color="normal" if rbi_pct >= 85 else "inverse",676 )677 with c2:678 st.metric(679 label="SEBI CSCRF Readiness",680 value=f"{sebi_pct:.1f}%",681 delta=f"{sebi_pct - 100:.1f}% to target",682 delta_color="normal" if sebi_pct >= 85 else "inverse",683 )684 with c3:685 st.metric(686 label="NIST CSF 2.0 Maturity",687 value=f"{nist_pct:.1f}%",688 delta=f"{nist_pct - 100:.1f}% to target",689 delta_color="normal" if nist_pct >= 85 else "inverse",690 )691 with c4:692 st.metric(693 label="ISO 27001:2022 Readiness",694 value=f"{iso27001_pct:.1f}%",695 delta=f"{iso27001_pct - 100:.1f}% to target",696 delta_color="normal" if iso27001_pct >= 85 else "inverse",697 )698 with c5:699 st.metric(700 label="ISO 42001:2023 AI Readiness",701 value=f"{iso42001_pct:.1f}%",702 delta=f"{iso42001_pct - 100:.1f}% to target",703 delta_color="normal" if iso42001_pct >= 85 else "inverse",704 )705 with c6:706 st.metric(707 label="Estimated Penalty Exposure",708 value=format_inr(penalty_exposure),709 help="Potential regulatory fines for non-compliance with critical directives.",710 )711 712 # Statutory Data Residency & Cross-Border Governance KPI Cards713 st.markdown("<div style='height: 10px;'></div>", unsafe_allow_html=True)714 cb_pct = comp_data.get("cross_border_compliance_index_percent", 100.0)715 rbi_loc_status = comp_data.get("rbi_localization_status", "Compliant")716 717 sb1, sb2, sb3 = st.columns(3)718 with sb1:719 st.metric(720 label="Cross-Border Transfer Readiness",721 value=f"{cb_pct:.1f}%",722 delta="Compliant" if cb_pct >= 90 else f"{100.0 - cb_pct:.1f}% Non-Compliant",723 delta_color="normal" if cb_pct >= 90 else "inverse",724 help="Statutory compliance index of cross-border data transfers evaluated under DPDPA 2023 & RBI directives.",725 )726 with sb2:727 st.metric(728 label="RBI Data Localization Status",729 value="Compliant โ
" if rbi_loc_status == "Compliant" else "Violation Detected ๐จ",730 delta="Domestic Storage Mandate" if rbi_loc_status == "Compliant" else "Non-Compliant Data Location",731 delta_color="normal" if rbi_loc_status == "Compliant" else "inverse",732 help="Storage of Payment System Data within India (RBI Directive April 2018 / Master Direction).",733 )734 with sb3:735 st.metric(736 label="DPDPA Section 16 Safeguards",737 value="Enforced (SCC/BCR)" if cb_pct == 100.0 else "Action Required",738 delta="Valid Safeguards" if cb_pct == 100.0 else "Unapproved Transfer Rails",739 delta_color="normal" if cb_pct == 100.0 else "inverse",740 help="Requires approved legal transfer mechanisms (SCC, BCR, adequacy, or consent) for personal data export.",741 )742 743 # Sub-Processor Consent & Delegation Governance Audit Check (DPDPA Section 8 & ISO 27001 Clause A.5)744 st.markdown("<div style='height: 10px;'></div>", unsafe_allow_html=True)745 gaps_list = comp_data.get("gaps", [])746 lineage_gaps = [747 g for g in gaps_list748 if "Section 8(4)" in g.get("category", "") or "Clause A.5" in g.get("category", "") or "Sub-Processor" in g.get("category", "")749 ]750 751 sub_col1, sub_col2 = st.columns(2)752 with sub_col1:753 has_dpdpa_gap = any("Section 8(4)" in g.get("category", "") for g in lineage_gaps)754 st.metric(755 label="Sub-Processor Governance (DPDPA Sec 8)",756 value="Non-Compliant ๐จ" if has_dpdpa_gap else "Compliant โ
",757 delta="Mandatory Lineage Tracer Missing" if has_dpdpa_gap else "Oversight Active",758 delta_color="inverse" if has_dpdpa_gap else "normal",759 help="DPDPA Section 8(4) statutory duty of data fiduciary to verify and audit downstream sub-processors.",760 )761 with sub_col2:762 has_iso_gap = any("Clause A.5" in g.get("category", "") for g in lineage_gaps)763 st.metric(764 label="ISO 27001 Clause A.5 Delegation Verifier",765 value="Action Required โ ๏ธ" if has_iso_gap else "Governed โ
",766 delta="Zero-Trust Consent Gap" if has_iso_gap else "Consent Enforced",767 delta_color="inverse" if has_iso_gap else "normal",768 help="ISO 27001:2022 Clause A.5 organizational control requirement for sub-processor consent delegation.",769 )770 771 st.markdown("---")772 773 rad_col, gap_col = st.columns([1, 1])774 775 with rad_col:776 st.markdown("#### Framework Readiness Radar")777 radar_categories = ["RBI CSF", "SEBI CSCRF", "NIST CSF 2.0", "ISO 27001", "ISO 42001"]778 radar_values = [rbi_pct, sebi_pct, nist_pct, iso27001_pct, iso42001_pct]779 780 fig_radar = go.Figure()781 # Current compliance trace782 fig_radar.add_trace(783 go.Scatterpolar(784 r=radar_values + [radar_values[0]],785 theta=radar_categories + [radar_categories[0]],786 fill="toself",787 name="Current Readiness",788 line=dict(color="#0284C7", width=2.5),789 fillcolor="rgba(2, 132, 199, 0.20)",790 )791 )792 # Target 100% trace793 fig_radar.add_trace(794 go.Scatterpolar(795 r=[100, 100, 100, 100, 100, 100],796 theta=radar_categories + [radar_categories[0]],797 mode="lines",798 name="Regulatory Target (100%)",799 line=dict(color="#94A3B8", dash="dash", width=1.5),800 )801 )802 fig_radar.update_layout(803 template="plotly_white",804 paper_bgcolor="#FFFFFF",805 plot_bgcolor="#FFFFFF",806 font=dict(color="#1E293B"),807 polar=dict(808 bgcolor="#FFFFFF",809 radialaxis=dict(810 visible=True,811 range=[0, 100],812 showticklabels=True,813 ticks="outside",814 tickfont=dict(color="#1E293B", size=10),815 gridcolor="#E2E8F0",816 linecolor="#CBD5E1",817 ),818 angularaxis=dict(819 gridcolor="#E2E8F0",820 linecolor="#CBD5E1",821 tickfont=dict(color="#1E293B", size=11, family="sans-serif"),822 ),823 ),824 height=380,825 margin=dict(l=30, r=30, t=30, b=30),826 legend=dict(827 orientation="h",828 yanchor="bottom",829 y=-0.22,830 xanchor="center",831 x=0.5,832 font=dict(color="#1E293B"),833 ),834 )835 st.plotly_chart(fig_radar, use_container_width=True)836 837 with gap_col:838 st.markdown("#### Identified Gaps & Actionable Remediations")839 gaps = comp_data.get("gaps", [])840 if gaps:841 for idx, gap in enumerate(gaps):842 rec_controls = ", ".join(gap.get("recommended_control_codes", [])) or "None specified"843 st.markdown(844 f"""845 <div class="gap-card">846 <div class="gap-header">847 <span>โ ๏ธ {gap.get('framework')} โข {gap.get('category')}</span>848 </div>849 <div class="gap-desc">{gap.get('description')}</div>850 <div class="gap-controls"><b>Recommended Fixes:</b> {rec_controls}</div>851 </div>852 """,853 unsafe_allow_html=True,854 )855 else:856 st.success("โ
Zero active regulatory gaps detected. Enterprise meets baseline standards.")857 858 # Cross-Border Data Transfer Audit Register Table859 st.markdown("---")860 st.markdown("#### ๐ Cross-Border Data Transfer & Statutory Jurisdiction Audit")861 st.caption("Live statutory verification of data flows against RBI Payment Data Localization Directive and DPDPA 2023 Section 16 restrictions.")862 863 asset_flow_ok, asset_flow_data, _ = safe_get(f"{backend_url}/api/assets")864 if asset_flow_ok and asset_flow_data:865 audit_rows = []866 for item in asset_flow_data:867 residency = item.get("data_residency_country", "IN")868 cb_enabled = item.get("cross_border_transfer_enabled", False)869 dest = item.get("destination_countries") or "โ"870 mech = item.get("transfer_legal_mechanism") or "None"871 rbi_loc = item.get("is_rbi_localization_compliant", True)872 is_rbi = item.get("is_rbi_regulated", False)873 fin_cnt = item.get("financial_records_count", 0)874 pii_cnt = item.get("pii_records_count", 0)875 is_tp = item.get("is_third_party", False)876 877 is_rbi_viol = (is_rbi and not rbi_loc) or (is_rbi and fin_cnt > 0 and residency != "IN") or (cb_enabled and not rbi_loc)878 is_dpdpa_viol = cb_enabled and pii_cnt > 0 and mech in (None, "None")879 is_tp_viol = is_tp and residency != "IN"880 881 status_label = "Compliant โ
"882 if is_rbi_viol:883 status_label = "RBI Localization Violation ๐จ"884 elif is_dpdpa_viol:885 status_label = "DPDPA Sec 16 Violation โ ๏ธ"886 elif is_tp_viol:887 status_label = "Foreign Vendor Review โน๏ธ"888 889 audit_rows.append({890 "Hostname": item.get("hostname"),891 "Origin Country": f"{residency} ๐ฎ๐ณ" if residency == "IN" else f"{residency} ๐",892 "Cross-Border Transfer": "Active ๐" if cb_enabled else "Domestic Only ๐",893 "Destination Countries": dest,894 "Applied Legal Safeguard": mech,895 "RBI Localization": "Compliant โ
" if (rbi_loc and (not is_rbi or residency == "IN")) else "Non-Compliant ๐จ",896 "Statutory Compliance Status": status_label,897 })898 st.dataframe(pd.DataFrame(audit_rows), use_container_width=True, hide_index=True)899 900 901# =========================================================================== #902# TAB 4: ASSET INVENTORY & TELEMETRY903# =========================================================================== #904with tab4:905 st.markdown("### ๐ฅ๏ธ IT/OT Asset Inventory & Third-Party Risk (TPRM)")906 st.caption("Live enterprise asset register enriched with financial impact metrics, sensitive data counts, ISO compliance scope, and third-party vendor risk.")907 908 asset_ok, asset_data, asset_err = safe_get(f"{backend_url}/api/assets")909 910 if not asset_ok:911 st.warning(f"โ ๏ธ Unable to load asset inventory: {asset_err}")912 else:913 if not asset_data:914 st.info("No assets found. Seed the database to display inventory.")915 else:916 df_assets = pd.DataFrame(asset_data)917 918 # Summary Metrics for Inventory & TPRM919 a1, a2, a3, a4, a5, a6 = st.columns(6)920 with a1:921 st.metric("Total Enterprise Hosts", len(df_assets))922 with a2:923 crit_count = len(df_assets[df_assets["tier"] == "Critical"]) if "tier" in df_assets else 0924 st.metric("Crown Jewel Assets", crit_count)925 with a3:926 tp_count = len(df_assets[df_assets["is_third_party"] == True]) if "is_third_party" in df_assets else 0927 st.metric("Third-Party Vendors", tp_count)928 with a4:929 ai_count = len(df_assets[df_assets["is_iso42001_regulated"] == True]) if "is_iso42001_regulated" in df_assets else 0930 st.metric("AI/ML Pipelines", ai_count)931 with a5:932 total_pii = df_assets["pii_records_count"].sum() if "pii_records_count" in df_assets else 0933 st.metric("Total PII Records", f"{total_pii:,}")934 with a6:935 total_fin = df_assets["financial_records_count"].sum() if "financial_records_count" in df_assets else 0936 st.metric("Total Financial Records", f"{total_fin:,}")937 938 st.markdown("---")939 940 # Filters941 f_col1, f_col2, f_col3, f_col4, f_col5 = st.columns([1, 1.1, 1.1, 1.2, 1.5])942 with f_col1:943 tier_filter = st.selectbox("Criticality Tier", ["All", "Critical", "Medium", "Low"])944 with f_col2:945 class_filter = st.selectbox(946 "Classification Type",947 [948 "All",949 "Core IT",950 "AI/ML Model Pipeline",951 "Third-Party SaaS",952 "Third-Party Vendor API",953 "Cloud Infrastructure",954 "Data Repository",955 ],956 )957 with f_col3:958 std_filter = st.selectbox(959 "Standard & Governance Scope",960 [961 "All",962 "ISO 27001 In-Scope",963 "ISO 42001 In-Scope",964 "Third-Party Only",965 "RBI Regulated",966 "SEBI Regulated",967 ],968 )969 with f_col4:970 residency_scope = st.selectbox(971 "Data Residency Scope",972 [973 "All",974 "Domestic (India Only)",975 "Cross-Border Active",976 "Localization Non-Compliant",977 ],978 )979 with f_col5:980 search_term = st.text_input("Search Hostname, Vendor, Unit, or Country", "").strip().lower()981 982 filtered_df = df_assets.copy()983 if tier_filter != "All":984 filtered_df = filtered_df[filtered_df["tier"] == tier_filter]985 if class_filter != "All" and "classification_type" in filtered_df:986 filtered_df = filtered_df[filtered_df["classification_type"] == class_filter]987 if std_filter == "ISO 27001 In-Scope" and "is_iso27001_regulated" in filtered_df:988 filtered_df = filtered_df[filtered_df["is_iso27001_regulated"] == True]989 elif std_filter == "ISO 42001 In-Scope" and "is_iso42001_regulated" in filtered_df:990 filtered_df = filtered_df[filtered_df["is_iso42001_regulated"] == True]991 elif std_filter == "Third-Party Only" and "is_third_party" in filtered_df:992 filtered_df = filtered_df[filtered_df["is_third_party"] == True]993 elif std_filter == "RBI Regulated" and "is_rbi_regulated" in filtered_df:994 filtered_df = filtered_df[filtered_df["is_rbi_regulated"] == True]995 elif std_filter == "SEBI Regulated" and "is_sebi_regulated" in filtered_df:996 filtered_df = filtered_df[filtered_df["is_sebi_regulated"] == True]997 998 # Data Residency Scope filter999 if residency_scope == "Domestic (India Only)" and "data_residency_country" in filtered_df:1000 filtered_df = filtered_df[1001 (filtered_df["data_residency_country"] == "IN")1002 & (filtered_df["cross_border_transfer_enabled"] == False)1003 ]1004 elif residency_scope == "Cross-Border Active" and "cross_border_transfer_enabled" in filtered_df:1005 filtered_df = filtered_df[1006 (filtered_df["cross_border_transfer_enabled"] == True)1007 | (filtered_df["data_residency_country"] != "IN")1008 ]1009 elif residency_scope == "Localization Non-Compliant" and "is_rbi_localization_compliant" in filtered_df:1010 filtered_df = filtered_df[1011 (filtered_df["is_rbi_localization_compliant"] == False)1012 | ((filtered_df["is_rbi_regulated"] == True) & (filtered_df["data_residency_country"] != "IN"))1013 ]1014 1015 if search_term:1016 host_match = filtered_df["hostname"].astype(str).str.lower().str.contains(search_term)1017 bu_match = filtered_df["business_unit"].astype(str).str.lower().str.contains(search_term)1018 type_match = filtered_df["asset_type"].astype(str).str.lower().str.contains(search_term)1019 class_match = (1020 filtered_df["classification_type"].fillna("").astype(str).str.lower().str.contains(search_term)1021 if "classification_type" in filtered_df1022 else False1023 )1024 vendor_match = (1025 filtered_df["vendor_name"].fillna("").astype(str).str.lower().str.contains(search_term)1026 if "vendor_name" in filtered_df1027 else False1028 )1029 country_match = (1030 filtered_df["data_residency_country"].fillna("").astype(str).str.lower().str.contains(search_term)1031 if "data_residency_country" in filtered_df1032 else False1033 )1034 filtered_df = filtered_df[host_match | bu_match | type_match | class_match | vendor_match | country_match]1035 1036 # Format DataFrame for Display with Third-Party & Residency Indicators1037 display_table = pd.DataFrame()1038 display_table["Hostname"] = filtered_df["hostname"]1039 display_table["Classification"] = (1040 filtered_df["classification_type"] if "classification_type" in filtered_df else "Internal IT"1041 )1042 display_table["Type"] = filtered_df["asset_type"]1043 display_table["Tier"] = filtered_df["tier"]1044 display_table["Residency (ISO)"] = (1045 filtered_df["data_residency_country"].apply(lambda c: f"{c} ๐ฎ๐ณ" if c == "IN" else f"{c} ๐")1046 if "data_residency_country" in filtered_df1047 else "IN ๐ฎ๐ณ"1048 )1049 display_table["Cross-Border Flow"] = (1050 filtered_df["cross_border_transfer_enabled"].apply(lambda b: "Yes ๐" if b else "No")1051 if "cross_border_transfer_enabled" in filtered_df1052 else "No"1053 )1054 display_table["Transfer Mechanism"] = (1055 filtered_df["transfer_legal_mechanism"].fillna("None")1056 if "transfer_legal_mechanism" in filtered_df1057 else "None"1058 )1059 display_table["RBI Localized"] = (1060 filtered_df.apply(1061 lambda row: "Non-Compliant ๐จ"1062 if (not row.get("is_rbi_localization_compliant", True)) or (row.get("is_rbi_regulated", False) and row.get("data_residency_country") != "IN")1063 else ("Compliant โ
" if row.get("is_rbi_regulated", False) else "Domestic ๐ฎ๐ณ"),1064 axis=1,1065 )1066 if "is_rbi_localization_compliant" in filtered_df1067 else "Domestic ๐ฎ๐ณ"1068 )1069 display_table["Third-Party"] = (1070 filtered_df["is_third_party"].apply(lambda b: "External ๐" if b else "Internal ๐ข")1071 if "is_third_party" in filtered_df1072 else "Internal ๐ข"1073 )1074 display_table["Vendor Name"] = (1075 filtered_df["vendor_name"].fillna("โ") if "vendor_name" in filtered_df else "โ"1076 )1077 display_table["Vendor Risk Tier"] = (1078 filtered_df["vendor_risk_tier"].apply(1079 lambda v: f"๐จ {v}"1080 if v == "Tier-1 Critical"1081 else (f"โ ๏ธ {v}" if v == "Tier-2 High" else (f"โน๏ธ {v}" if pd.notna(v) and v else "โ"))1082 )1083 if "vendor_risk_tier" in filtered_df1084 else "โ"1085 )1086 display_table["SOC 2 Status"] = (1087 filtered_df["soc2_attestation"].apply(lambda b: "Attested โ
" if b else "Uncertified โ ๏ธ")1088 if "soc2_attestation" in filtered_df1089 else "Uncertified โ ๏ธ"1090 )1091 display_table["ISO 27001"] = (1092 filtered_df["is_iso27001_regulated"].apply(lambda b: "In-Scope" if b else "Exempt")1093 if "is_iso27001_regulated" in filtered_df1094 else "Exempt"1095 )1096 display_table["ISO 42001"] = (1097 filtered_df["is_iso42001_regulated"].apply(lambda b: "In-Scope ๐ค" if b else "Exempt")1098 if "is_iso42001_regulated" in filtered_df1099 else "Exempt"1100 )1101 display_table["Business Unit"] = filtered_df["business_unit"]1102 display_table["Revenue Loss / Min"] = filtered_df["revenue_per_minute"].apply(format_inr)1103 display_table["PII Records"] = filtered_df["pii_records_count"].apply(lambda n: f"{n:,}")1104 display_table["Financial Records"] = filtered_df["financial_records_count"].apply(lambda n: f"{n:,}")1105 display_table["Network Hops"] = filtered_df["network_hops_from_internet"]1106 display_table["Estimated EAL"] = filtered_df["estimated_eal_inr"].apply(format_inr)1107 1108 st.dataframe(1109 display_table,1110 use_container_width=True,1111 hide_index=True,1112 )1113 1114 # Vulnerability Inspection Drawer1115 with st.expander("๐ Deep Dive: CVEs & Exploitation Scores for Selected Host", expanded=False):1116 host_list = sorted(filtered_df["hostname"].unique().tolist())1117 if host_list:1118 chosen_host = st.selectbox("Select Host", host_list)1119 host_record = next((a for a in asset_data if a["hostname"] == chosen_host), None)1120 if host_record and host_record.get("vulnerabilities"):1121 v_df = pd.DataFrame(host_record["vulnerabilities"])1122 v_display = v_df[["cve_id", "cvss_score", "epss_score", "cisa_kev", "patch_available", "is_patched"]].rename(1123 columns={1124 "cve_id": "CVE ID",1125 "cvss_score": "CVSS 3.1",1126 "epss_score": "EPSS Score",1127 "cisa_kev": "CISA KEV Listed",1128 "patch_available": "Patch Available",1129 "is_patched": "Remediated",1130 }1131 )1132 st.dataframe(v_display, use_container_width=True, hide_index=True)1133 else:1134 st.info(f"No active vulnerabilities mapped to {chosen_host}.")1135 1136 # ---------------------------------------------------------------- #1137 # Unauthorized Data Flow & Lineage Tracer Interactive Table1138 # ---------------------------------------------------------------- #1139 st.markdown("---")1140 st.markdown("#### ๐ต๏ธ Unauthorized Data Flow & Lineage Tracer")1141 st.caption(1142 "Breadth-first taint propagation ($A \\rightarrow B \\rightarrow C$) pinpointing indirect data leakage, "1143 "unconsented sub-processor delegations, and causal liability attribution under DPDPA Section 8(4)."1144 )1145 1146 lineage_ok, lineage_data, _ = safe_get(f"{backend_url}/api/lineage/flows")1147 if not lineage_ok or not lineage_data:1148 # Fallback to summary data identified vectors1149 lineage_data = summary_data.get("identified_leak_vectors", []) if ok and summary_data else []1150 1151 if lineage_data:1152 unauth_count = len([1153 f for f in lineage_data1154 if not f.get("is_authorized", True) or not f.get("has_user_consent", True) or "Unauthorized" in f.get("detection_status", "")1155 ])1156 if unauth_count > 0:1157 st.error(1158 f"๐จ **Critical Indirect Data Leak Identified:** {unauth_count} unauthorized delegation flow(s) detected. "1159 f"Core banking records originating from internal databases are traversing intermediary bridges to unconsented third-party sinks."1160 )1161 else:1162 st.success("โ
All data provenance flows have recorded authorization and user consent boundaries verified.")1163 1164 flow_rows = []1165 for f in lineage_data:1166 origin = f.get("origin_hostname", "โ")1167 inter = f.get("intermediary_hostname") or "โ (Direct)"1168 dest = f.get("destination_hostname", "โ")1169 pii_exposed = f.get("records_exposed_pii", 0)1170 fin_exposed = f.get("records_exposed_financial", 0)1171 is_auth = f.get("is_authorized", True)1172 has_consent = f.get("has_user_consent", True)1173 is_leak = not is_auth or not has_consent or "Unauthorized" in f.get("detection_status", "")1174 1175 consent_status = "Approved โ
" if (is_auth and has_consent) else "Unauthorized Delegation ๐จ"1176 culprit = f"๐จ {dest} (Exfiltration Sink)" if is_leak else "None (Compliant)"1177 1178 flow_rows.append({1179 "Originating Asset (A)": origin,1180 "Intermediate Gateway (B)": inter,1181 "Exfiltration / Sink Asset (C)": dest,1182 "Data Transferred (PII / Financial)": f"{pii_exposed:,} PII / {fin_exposed:,} Financial",1183 "Consent Verification Status": consent_status,1184 "Detection Status": f.get("detection_status", "Legitimate Flow"),1185 "Identified Causal Leak Culprit (C)": culprit,1186 })1187 1188 df_flows = pd.DataFrame(flow_rows)1189 st.dataframe(df_flows, use_container_width=True, hide_index=True)1190 else:1191 st.info("No data lineage flows registered. Ensure attack graph topology and assets are seeded.")1192 