diff --git a/app.py b/app.py index f2a10ce..a7a528b 100644 --- a/app.py +++ b/app.py @@ -4096,44 +4096,117 @@ def _read_text(path): # LSM status matrix (best effort, distro dependent). apparmor_raw = _read_text("/sys/module/apparmor/parameters/enabled") selinux_enforce = _read_text("/sys/fs/selinux/enforce") + selinux_mode = _read_text("/sys/fs/selinux/enforce") + selinux_policy = _read_text("/sys/fs/selinux/policyvers") yama_scope = _read_text("/proc/sys/kernel/yama/ptrace_scope") bpf_unpriv = _read_text("/proc/sys/kernel/unprivileged_bpf_disabled") landlock_present = os.path.exists("/sys/kernel/security/landlock") ima_present = os.path.exists("/sys/kernel/security/ima") + + # Check for BPF LSM (modern trend). + bpf_lsm_present = os.path.exists("/sys/kernel/security/bpf") + try: + lsm_list_raw = _read_text("/sys/kernel/security/lsm") + active_lsms = [x.strip() for x in lsm_list_raw.split(",")] if lsm_list_raw else [] + stacking_enabled = len([x for x in active_lsms if x in {"selinux", "apparmor", "bpf"}]) > 1 + except Exception: + active_lsms = [] + stacking_enabled = False + lsm_status = [ { "name": "AppArmor", "status": "enforcing" if apparmor_raw.lower().startswith("y") else ("disabled" if apparmor_raw else "unknown"), - "detail": apparmor_raw or "n/a" + "detail": apparmor_raw or "n/a", + "type": "policy_engine" }, { "name": "SELinux", "status": "enforcing" if selinux_enforce == "1" else ("disabled" if selinux_enforce == "0" else "unknown"), - "detail": selinux_enforce or "n/a" + "detail": selinux_enforce or "n/a", + "type": "policy_engine", + "policy_version": selinux_policy or "n/a" + }, + { + "name": "BPF LSM", + "status": "present" if bpf_lsm_present else "absent", + "detail": "eBPF-based LSM" if bpf_lsm_present else "n/a", + "type": "policy_engine" + }, + { + "name": "LSM Stacking", + "status": "enabled" if stacking_enabled else "disabled", + "detail": ",".join(active_lsms[:3]) if active_lsms else "n/a", + "type": "stacking" }, { "name": "Yama ptrace", "status": "hardened" if yama_scope in {"2", "3"} else ("relaxed" if yama_scope in {"0", "1"} else "unknown"), - "detail": yama_scope or "n/a" + "detail": yama_scope or "n/a", + "type": "restriction" }, { "name": "unprivileged bpf", "status": "blocked" if bpf_unpriv == "1" else ("allowed" if bpf_unpriv == "0" else "unknown"), - "detail": bpf_unpriv or "n/a" + "detail": bpf_unpriv or "n/a", + "type": "restriction" }, { "name": "Landlock", "status": "present" if landlock_present else "absent", - "detail": "sysfs" if landlock_present else "n/a" + "detail": "sysfs" if landlock_present else "n/a", + "type": "restriction" }, { "name": "IMA/EVM", "status": "present" if ima_present else "absent", - "detail": "sysfs" if ima_present else "n/a" + "detail": "sysfs" if ima_present else "n/a", + "type": "integrity" } ] + + # LSM engines detail for security core visualization. + lsm_engines = [] + if apparmor_raw.lower().startswith("y"): + lsm_engines.append({ + "name": "AppArmor", + "type": "policy_engine", + "status": "enforcing", + "hooks": ["file_open", "bprm_check", "socket_connect"], + "decisions_per_sec": random.randint(8, 45) + }) + if selinux_enforce == "1": + lsm_engines.append({ + "name": "SELinux", + "type": "policy_engine", + "status": "enforcing", + "hooks": ["file_open", "bprm_check", "socket_connect", "inode_create"], + "decisions_per_sec": random.randint(12, 52) + }) + if bpf_lsm_present: + lsm_engines.append({ + "name": "BPF LSM", + "type": "policy_engine", + "status": "enforcing", + "hooks": ["file_open", "bprm_check", "socket_connect"], + "decisions_per_sec": random.randint(5, 28) + }) # Capabilities drift (CapEff/CapPrm from /proc//status). + # Full capabilities map (all 40+ capabilities). + all_capabilities_map = { + 0: "CAP_CHOWN", 1: "CAP_DAC_OVERRIDE", 2: "CAP_DAC_READ_SEARCH", 3: "CAP_FOWNER", + 4: "CAP_FSETID", 5: "CAP_KILL", 6: "CAP_SETGID", 7: "CAP_SETUID", + 8: "CAP_SETPCAP", 9: "CAP_LINUX_IMMUTABLE", 10: "CAP_NET_BIND_SERVICE", + 11: "CAP_NET_BROADCAST", 12: "CAP_NET_ADMIN", 13: "CAP_NET_RAW", 14: "CAP_IPC_LOCK", + 15: "CAP_IPC_OWNER", 16: "CAP_SYS_MODULE", 17: "CAP_SYS_RAWIO", 18: "CAP_SYS_CHROOT", + 19: "CAP_SYS_PTRACE", 20: "CAP_SYS_PACCT", 21: "CAP_SYS_ADMIN", 22: "CAP_SYS_BOOT", + 23: "CAP_SYS_NICE", 24: "CAP_SYS_RESOURCE", 25: "CAP_SYS_TIME", 26: "CAP_SYS_TTY_CONFIG", + 27: "CAP_MKNOD", 28: "CAP_LEASE", 29: "CAP_AUDIT_WRITE", 30: "CAP_AUDIT_CONTROL", + 31: "CAP_SETFCAP", 32: "CAP_MAC_OVERRIDE", 33: "CAP_MAC_ADMIN", 34: "CAP_SYSLOG", + 35: "CAP_WAKE_ALARM", 36: "CAP_BLOCK_SUSPEND", 37: "CAP_AUDIT_READ", 38: "CAP_PERFMON", + 39: "CAP_BPF", 40: "CAP_CHECKPOINT_RESTORE" + } dangerous_caps = { 12: "CAP_NET_ADMIN", 16: "CAP_SYS_MODULE", @@ -4144,6 +4217,71 @@ def _read_text(path): } capabilities_rows = [] seccomp_counts = {"none": 0, "strict": 0, "filter": 0, "unknown": 0} + seccomp_processes = [] # For security core visualization. + capabilities_processes = [] # For security core visualization. + + # Common syscalls for seccomp visualization. + common_syscalls = [ + "read", "write", "open", "close", "stat", "fstat", "lstat", "poll", "lseek", + "mmap", "mprotect", "munmap", "brk", "rt_sigaction", "rt_sigprocmask", + "rt_sigreturn", "ioctl", "pread64", "pwrite64", "readv", "writev", + "access", "pipe", "select", "sched_yield", "mremap", "msync", "mincore", + "madvise", "shmget", "shmat", "shmctl", "dup", "dup2", "pause", "nanosleep", + "getitimer", "alarm", "setitimer", "getpid", "sendfile", "socket", "connect", + "accept", "sendto", "recvfrom", "sendmsg", "recvmsg", "shutdown", "bind", + "listen", "getsockname", "getpeername", "socketpair", "setsockopt", "getsockopt", + "clone", "fork", "vfork", "execve", "exit", "wait4", "kill", "uname", + "semget", "semop", "semctl", "shmdt", "msgget", "msgsnd", "msgrcv", "msgctl", + "fcntl", "flock", "fsync", "fdatasync", "truncate", "ftruncate", "getdents", + "getcwd", "chdir", "fchdir", "rename", "mkdir", "rmdir", "creat", "link", + "unlink", "symlink", "readlink", "chmod", "fchmod", "chown", "fchown", + "lchown", "umask", "gettimeofday", "getrlimit", "getrusage", "sysinfo", + "times", "ptrace", "getuid", "syslog", "getgid", "setuid", "setgid", + "geteuid", "getegid", "setpgid", "getppid", "getpgrp", "setsid", "setreuid", + "setregid", "getgroups", "setgroups", "setresuid", "getresuid", "setresgid", + "getresgid", "getpgid", "setfsuid", "setfsgid", "getsid", "capget", "capset", + "rt_sigpending", "rt_sigtimedwait", "rt_sigqueueinfo", "rt_sigsuspend", + "sigaltstack", "utime", "mknod", "uselib", "personality", "ustat", "statfs", + "fstatfs", "sysfs", "getpriority", "setpriority", "sched_setparam", + "sched_getparam", "sched_setscheduler", "sched_getscheduler", + "sched_get_priority_max", "sched_get_priority_min", "sched_rr_get_interval", + "mlock", "munlock", "mlockall", "munlockall", "vhangup", "modify_ldt", + "pivot_root", "prctl", "arch_prctl", "adjtimex", "setrlimit", "chroot", + "sync", "acct", "settimeofday", "mount", "umount2", "swapon", "swapoff", + "reboot", "sethostname", "setdomainname", "iopl", "ioperm", "create_module", + "init_module", "delete_module", "get_kernel_syms", "query_module", "quotactl", + "nfsservctl", "getpmsg", "putpmsg", "afs_syscall", "tuxcall", "security", + "gettid", "readahead", "setxattr", "lsetxattr", "fsetxattr", "getxattr", + "lgetxattr", "fgetxattr", "listxattr", "llistxattr", "flistxattr", + "removexattr", "lremovexattr", "fremovexattr", "tkill", "time", "futex", + "sched_setaffinity", "sched_getaffinity", "set_thread_area", "io_setup", + "io_destroy", "io_getevents", "io_submit", "io_cancel", "get_thread_area", + "lookup_dcookie", "epoll_create", "epoll_ctl_old", "epoll_wait_old", + "remap_file_pages", "getdents64", "set_tid_address", "restart_syscall", + "semtimedop", "fadvise64", "timer_create", "timer_settime", "timer_gettime", + "timer_getoverrun", "timer_delete", "clock_settime", "clock_gettime", + "clock_getres", "clock_nanosleep", "exit_group", "epoll_wait", "epoll_ctl", + "tgkill", "utimes", "vserver", "mbind", "set_mempolicy", "get_mempolicy", + "mq_open", "mq_unlink", "mq_timedsend", "mq_timedreceive", "mq_notify", + "mq_getsetattr", "kexec_load", "waitid", "add_key", "request_key", "keyctl", + "ioprio_set", "ioprio_get", "inotify_init", "inotify_add_watch", + "inotify_rm_watch", "migrate_pages", "openat", "mkdirat", "mknodat", + "fchownat", "futimesat", "newfstatat", "unlinkat", "renameat", "linkat", + "symlinkat", "readlinkat", "fchmodat", "faccessat", "pselect6", "ppoll", + "unshare", "set_robust_list", "get_robust_list", "splice", "tee", + "sync_file_range", "vmsplice", "move_pages", "utimensat", "epoll_pwait", + "signalfd", "timerfd_create", "eventfd", "fallocate", "timerfd_settime", + "timerfd_gettime", "accept4", "signalfd4", "eventfd2", "epoll_create1", + "dup3", "pipe2", "inotify_init1", "preadv", "pwritev", "rt_tgsigqueueinfo", + "perf_event_open", "recvmmsg", "fanotify_init", "fanotify_mark", + "prlimit64", "name_to_handle_at", "open_by_handle_at", "clock_adjtime", + "syncfs", "sendmmsg", "setns", "getcpu", "process_vm_readv", + "process_vm_writev", "kcmp", "finit_module", "sched_setattr", + "sched_getattr", "renameat2", "seccomp", "getrandom", "memfd_create", + "kexec_file_load", "bpf", "execveat", "userfaultfd", "membarrier", + "mlock2", "copy_file_range", "preadv2", "pwritev2", "pkey_mprotect", + "pkey_alloc", "pkey_free", "statx", "io_pgetevents", "rseq" + ] for row in process_rows[:180]: pid = int(row.get("pid") or 0) @@ -4173,6 +4311,36 @@ def _read_text(path): pass seccomp_counts[seccomp_mode] = seccomp_counts.get(seccomp_mode, 0) + 1 + + # Collect seccomp details for security core visualization. + if seccomp_mode in {"filter", "strict"}: + # Heuristic: generate allowed/blocked syscalls based on process type. + allowed_syscalls = [] + blocked_syscalls = [] + proc_name_lower = str(row.get("name", "")).lower() + if "nginx" in proc_name_lower or "apache" in proc_name_lower: + allowed_syscalls = ["read", "write", "open", "close", "socket", "accept", "send", "recv", "epoll_wait", "fstat"] + blocked_syscalls = ["ptrace", "mount", "umount", "sys_module", "bpf", "keyctl"] + elif "sshd" in proc_name_lower: + allowed_syscalls = ["read", "write", "open", "close", "socket", "accept", "send", "recv", "fork", "execve"] + blocked_syscalls = ["mount", "umount", "sys_module", "bpf"] + elif "docker" in proc_name_lower or "containerd" in proc_name_lower: + allowed_syscalls = ["read", "write", "open", "close", "socket", "clone", "unshare", "mount", "umount"] + blocked_syscalls = ["sys_module", "bpf"] + else: + # Generic: allow common syscalls, block dangerous ones. + allowed_syscalls = common_syscalls[:40] # First 40 common syscalls + blocked_syscalls = ["ptrace", "mount", "umount", "sys_module", "bpf", "keyctl", "kexec_load"] + + seccomp_processes.append({ + "pid": pid, + "name": row.get("name", "unknown"), + "mode": seccomp_mode, + "allowed_syscalls": allowed_syscalls[:20], # Limit for visualization + "blocked_syscalls": blocked_syscalls, + "sandbox_level": "strict" if seccomp_mode == "strict" else "filter" + }) + if not cap_eff_hex: continue try: @@ -4181,7 +4349,21 @@ def _read_text(path): except Exception: continue + # Collect all capabilities (not just dangerous ones) for security core visualization. + all_caps = [all_capabilities_map.get(bit, f"CAP_{bit}") for bit in range(41) if (cap_eff_val & (1 << bit))] matched = [name for bit, name in dangerous_caps.items() if (cap_eff_val & (1 << bit))] + + # Store capabilities as "keys" for visualization. + capabilities_processes.append({ + "pid": pid, + "name": row.get("name", "unknown"), + "user": row.get("user", ""), + "capabilities": all_caps[:15], # Limit for visualization + "dangerous_caps": matched, + "cap_eff_hex": cap_eff_hex, + "has_keys": len(all_caps) > 0 + }) + if not matched: continue risk = min(100, 20 + len(matched) * 16 + (10 if row.get("user") == "root" else 0)) @@ -4247,6 +4429,13 @@ def _read_text(path): "capabilities_drift": capabilities_drift, "seccomp_coverage": seccomp_coverage }, + "security_core": { + "lsm_engines": lsm_engines, + "seccomp_processes": seccomp_processes[:12], # Top 12 for visualization + "capabilities_processes": capabilities_processes[:12], # Top 12 for visualization + "stacking_enabled": stacking_enabled, + "active_lsms": active_lsms + }, "meta": { "decisions_per_sec": decisions_per_sec, "events": current_events, @@ -4255,7 +4444,7 @@ def _read_text(path): "suspicious": sum(1 for p in trust_graph if p.get("trust") == "suspicious"), "blocked": sum(1 for p in trust_graph if p.get("trust") == "blocked"), "seccomp_coverage_percent": seccomp_coverage.get("coverage_percent", 0.0), - "mode": "live-heuristic-v1" + "mode": "live-heuristic-v2" } } diff --git a/index.html b/index.html index 69200c4..d99be4a 100755 --- a/index.html +++ b/index.html @@ -101,7 +101,7 @@

Linux Kernel Ring 0 Visualization

- + diff --git a/static/js/security-belt.js b/static/js/security-belt.js index 06431e3..6046e53 100644 --- a/static/js/security-belt.js +++ b/static/js/security-belt.js @@ -1,7 +1,7 @@ -// Security Subsystem Visualization (Stage 3) -// Version: 10 +// Security Subsystem Visualization (Stage 4: Kernel Security Core) +// Version: 11 -debugLog('🛡️ security-belt.js v10: Script loading...'); +debugLog('🛡️ security-belt.js v11: Script loading...'); class SecuritySubsystemVisualization { constructor() { @@ -634,6 +634,140 @@ class SecuritySubsystemVisualization { }); } + drawSecurityCore(x, y, w, h, telemetry) { + this.drawPanel(x, y, w, h, 'KERNEL SECURITY CORE'); + const core = telemetry?.security_core || {}; + const lsmEngines = Array.isArray(core.lsm_engines) ? core.lsm_engines : []; + const seccompProcs = Array.isArray(core.seccomp_processes) ? core.seccomp_processes : []; + const capProcs = Array.isArray(core.capabilities_processes) ? core.capabilities_processes : []; + const stacking = Boolean(core.stacking_enabled); + + // Draw LSM engines section (left side). + this.ctx.fillStyle = '#9ed4ff'; + this.ctx.font = '10px "Share Tech Mono", monospace'; + this.ctx.fillText('LSM POLICY ENGINES', x + 14, y + 38); + if (stacking) { + this.ctx.fillStyle = '#60d69d'; + this.ctx.fillText('STACKING: ON', x + w - 120, y + 38); + } + + lsmEngines.forEach((engine, idx) => { + const yy = y + 52 + idx * 32; + const name = String(engine.name || 'LSM'); + const status = String(engine.status || 'unknown'); + const decisions = Number(engine.decisions_per_sec || 0); + const hooks = Array.isArray(engine.hooks) ? engine.hooks : []; + + this.drawRoundedRect(x + 12, yy - 14, Math.floor(w * 0.48), 26, 4); + this.ctx.fillStyle = status === 'enforcing' ? 'rgba(32, 52, 81, 0.72)' : 'rgba(10, 14, 20, 0.46)'; + this.ctx.fill(); + this.ctx.strokeStyle = status === 'enforcing' ? 'rgba(124, 178, 255, 0.65)' : 'rgba(112, 123, 140, 0.28)'; + this.ctx.lineWidth = 0.9; + this.ctx.stroke(); + + this.ctx.fillStyle = status === 'enforcing' ? '#d9ecff' : 'rgba(185, 200, 220, 0.62)'; + this.ctx.font = '10px "Share Tech Mono", monospace'; + this.ctx.fillText(name, x + 16, yy); + this.ctx.fillStyle = status === 'enforcing' ? '#60d69d' : '#8a9cea'; + this.ctx.fillText(`${decisions}/s`, x + Math.floor(w * 0.28), yy); + this.ctx.fillStyle = 'rgba(185, 200, 220, 0.72)'; + this.ctx.font = '8px "Share Tech Mono", monospace'; + this.ctx.fillText(hooks.slice(0, 2).join(', '), x + 16, yy + 12); + }); + + // Draw seccomp section (top right). + const seccompY = y + 52; + this.ctx.fillStyle = '#9ed4ff'; + this.ctx.font = '10px "Share Tech Mono", monospace'; + this.ctx.fillText('SECCOMP SANDBOX', x + Math.floor(w * 0.52), seccompY - 14); + + seccompProcs.slice(0, 3).forEach((proc, idx) => { + const yy = seccompY + idx * 32; + const pid = Number(proc.pid || 0); + const name = String(proc.name || 'proc').slice(0, 10); + const mode = String(proc.mode || 'none'); + const allowed = Array.isArray(proc.allowed_syscalls) ? proc.allowed_syscalls : []; + const blocked = Array.isArray(proc.blocked_syscalls) ? proc.blocked_syscalls : []; + + this.drawRoundedRect(x + Math.floor(w * 0.52), yy - 14, Math.floor(w * 0.46), 26, 4); + this.ctx.fillStyle = mode === 'strict' ? 'rgba(32, 52, 81, 0.72)' : (mode === 'filter' ? 'rgba(22, 34, 52, 0.56)' : 'rgba(10, 14, 20, 0.46)'); + this.ctx.fill(); + this.ctx.strokeStyle = mode === 'strict' ? '#60d69d' : (mode === 'filter' ? '#f4c977' : 'rgba(112, 123, 140, 0.28)'); + this.ctx.lineWidth = 0.9; + this.ctx.stroke(); + + this.ctx.fillStyle = '#d8e5f7'; + this.ctx.font = '9px "Share Tech Mono", monospace'; + this.ctx.fillText(`${name}:${pid}`, x + Math.floor(w * 0.54), yy); + this.ctx.fillStyle = mode === 'strict' ? '#60d69d' : '#f4c977'; + this.ctx.fillText(`${allowed.length} allow`, x + Math.floor(w * 0.54), yy + 12); + this.ctx.fillStyle = '#eb7e7e'; + this.ctx.fillText(`${blocked.length} block`, x + Math.floor(w * 0.72), yy + 12); + }); + + // Draw capabilities section (bottom). + const capY = y + Math.floor(h * 0.48); + this.ctx.fillStyle = '#9ed4ff'; + this.ctx.font = '10px "Share Tech Mono", monospace'; + this.ctx.fillText('CAPABILITIES (GRANULAR RIGHTS)', x + 14, capY); + + capProcs.slice(0, 4).forEach((proc, idx) => { + const yy = capY + 18 + idx * 28; + const pid = Number(proc.pid || 0); + const name = String(proc.name || 'proc').slice(0, 10); + const caps = Array.isArray(proc.capabilities) ? proc.capabilities : []; + const dangerous = Array.isArray(proc.dangerous_caps) ? proc.dangerous_caps : []; + + this.drawRoundedRect(x + 12, yy - 12, w - 24, 22, 4); + this.ctx.fillStyle = dangerous.length > 0 ? 'rgba(32, 52, 81, 0.72)' : 'rgba(10, 14, 20, 0.46)'; + this.ctx.fill(); + this.ctx.strokeStyle = dangerous.length > 0 ? '#eb7e7e' : 'rgba(112, 123, 140, 0.28)'; + this.ctx.lineWidth = 0.9; + this.ctx.stroke(); + + this.ctx.fillStyle = '#d8e5f7'; + this.ctx.font = '9px "Share Tech Mono", monospace'; + this.ctx.fillText(`${name}:${pid}`, x + 16, yy + 2); + + // Draw capability "keys" as small squares. + const keySize = 6; + const keySpacing = 8; + let keyX = x + Math.floor(w * 0.28); + caps.slice(0, 12).forEach((cap, capIdx) => { + const isDangerous = dangerous.includes(cap); + this.ctx.fillStyle = isDangerous ? '#eb7e7e' : (cap.startsWith('CAP_SYS') ? '#f4c977' : '#60d69d'); + this.ctx.fillRect(keyX + capIdx * keySpacing, yy + 6, keySize, keySize); + this.ctx.strokeStyle = '#0e1621'; + this.ctx.lineWidth = 0.5; + this.ctx.strokeRect(keyX + capIdx * keySpacing, yy + 6, keySize, keySize); + }); + + if (dangerous.length > 0) { + this.ctx.fillStyle = '#eb7e7e'; + this.ctx.font = '8px "Share Tech Mono", monospace'; + this.ctx.fillText(`⚠ ${dangerous.slice(0, 2).join(', ')}`, x + Math.floor(w * 0.78), yy + 2); + } + }); + + // Draw enforcement flow arrow (center). + const flowY = y + Math.floor(h * 0.42); + const flowX = x + Math.floor(w * 0.5); + this.ctx.strokeStyle = 'rgba(124, 178, 255, 0.45)'; + this.ctx.lineWidth = 1.2; + this.ctx.beginPath(); + this.ctx.moveTo(flowX - 40, flowY); + this.ctx.lineTo(flowX + 40, flowY); + this.ctx.stroke(); + this.ctx.beginPath(); + this.ctx.moveTo(flowX + 35, flowY - 3); + this.ctx.lineTo(flowX + 40, flowY); + this.ctx.lineTo(flowX + 35, flowY + 3); + this.ctx.stroke(); + this.ctx.fillStyle = 'rgba(180, 196, 220, 0.86)'; + this.ctx.font = '8px "Share Tech Mono", monospace'; + this.ctx.fillText('ENFORCEMENT FLOW', flowX - 38, flowY - 6); + } + drawHeaderStats(telemetry) { const meta = telemetry?.meta || {}; this.ctx.fillStyle = 'rgba(179, 203, 232, 0.92)'; @@ -700,7 +834,8 @@ class SecuritySubsystemVisualization { const gap = 16; const panelTop = 172; const toolsH = Math.max(128, Math.min(176, Math.floor(h * 0.22))); - const panelH = Math.max(220, h - panelTop - toolsH - gap - 24); + const coreH = Math.max(240, Math.floor(h * 0.28)); + const panelH = Math.max(220, h - panelTop - toolsH - coreH - gap * 3 - 24); const leftW = Math.max(440, Math.floor(w * 0.42)); const centerW = Math.max(330, Math.floor(w * 0.26)); const rightW = Math.max(310, w - leftW - centerW - gap * 4); @@ -712,6 +847,7 @@ class SecuritySubsystemVisualization { const toolsW = Math.max(220, Math.floor((w - gap * 4) / 3)); const tools2X = leftX + toolsW + gap; const tools3X = tools2X + toolsW + gap; + const coreY = toolsY + toolsH + gap; this.drawDecisionPipeline(leftX, panelTop, leftW, panelH, this.telemetry); this.drawTrustGraph(centerX, panelTop, centerW, panelH, this.telemetry); @@ -719,6 +855,7 @@ class SecuritySubsystemVisualization { this.drawLsmStatusCard(leftX, toolsY, toolsW, toolsH, this.telemetry); this.drawCapabilitiesCard(tools2X, toolsY, toolsW, toolsH, this.telemetry); this.drawSeccompCoverageCard(tools3X, toolsY, toolsW, toolsH, this.telemetry); + this.drawSecurityCore(leftX, coreY, w - gap * 2, coreH, this.telemetry); } animate() { diff --git a/templates/linux-security-subsystem.html b/templates/linux-security-subsystem.html index 967cc73..048e8c3 100644 --- a/templates/linux-security-subsystem.html +++ b/templates/linux-security-subsystem.html @@ -111,7 +111,7 @@

Attack Surface Map

- +