Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 196 additions & 7 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<pid>/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",
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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))
Expand Down Expand Up @@ -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,
Expand All @@ -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"
}
}

Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ <h2>Linux Kernel Ring 0 Visualization</h2>
<script src="/static/js/network-stack.js?v=6"></script>
<script src="/static/js/devices-belt.js?v=14"></script>
<script src="/static/js/crypto-belt.js?v=31"></script>
<script src="/static/js/security-belt.js?v=10"></script>
<script src="/static/js/security-belt.js?v=11"></script>
<script src="/static/js/filesystem-map.js?v=10"></script>
<script src="/static/js/kernel-context-menu.js?v=26"></script>
<!-- 3D visualization script - DISABLED -->
Expand Down
Loading
Loading