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
291 changes: 291 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,16 @@ def security_page_legacy():
"""Legacy path redirect to Linux security subsystem page."""
return redirect('/linux-security-subsystem', code=301)

@app.route('/linux-processes-subsystem')
def linux_processes_subsystem_page():
"""SEO-friendly Linux processes subsystem page."""
return render_template('linux-processes-subsystem.html')

@app.route('/processes')
def processes_page_legacy():
"""Legacy path redirect to Linux processes subsystem page."""
return redirect('/linux-processes-subsystem', code=301)

@app.route('/api/syscalls-realtime')
def syscalls_realtime():
"""API for real-time system calls"""
Expand Down Expand Up @@ -4448,6 +4458,279 @@ def _read_text(path):
}
}

def collect_processes_realtime():
"""
Processes subsystem telemetry focused on:
- syscall interception signals
- network tracing
- security hooks
"""
lsm_raw = ""
try:
with open("/sys/kernel/security/lsm", "r", encoding="utf-8", errors="ignore") as f:
lsm_raw = str(f.read().strip())
except Exception:
lsm_raw = ""
active_lsms = [x.strip() for x in lsm_raw.split(",") if x.strip()]

yama_scope = ""
try:
with open("/proc/sys/kernel/yama/ptrace_scope", "r", encoding="utf-8", errors="ignore") as f:
yama_scope = str(f.read().strip())
except Exception:
yama_scope = ""

syscall_nodes = []
seccomp_modes = {"none": 0, "strict": 0, "filter": 0, "unknown": 0}
for proc in psutil.process_iter(["pid", "ppid", "name", "username", "cpu_percent", "memory_percent", "num_threads"]):
try:
pid = int(proc.info.get("pid") or 0)
if pid <= 0:
continue
ppid = int(proc.info.get("ppid") or 0)
name = str(proc.info.get("name") or "unknown")
user = str(proc.info.get("username") or "")
cpu = float(proc.info.get("cpu_percent") or 0.0)
mem = float(proc.info.get("memory_percent") or 0.0)
threads = int(proc.info.get("num_threads") or 0)
fd_count = 0
try:
fd_count = int(proc.num_fds() or 0)
except Exception:
fd_count = 0
seccomp_mode = "unknown"
with open(f"/proc/{pid}/status", "r", encoding="utf-8", errors="ignore") as f:
for ln in f:
if ln.startswith("Seccomp:"):
raw = ln.split(":", 1)[1].strip()
if raw == "0":
seccomp_mode = "none"
elif raw == "1":
seccomp_mode = "strict"
elif raw == "2":
seccomp_mode = "filter"
else:
seccomp_mode = "unknown"
break
seccomp_modes[seccomp_mode] = seccomp_modes.get(seccomp_mode, 0) + 1
syscall_pressure = min(100, int(cpu * 1.5 + threads * 0.35 + mem * 0.8))
syscall_nodes.append({
"pid": pid,
"ppid": ppid,
"name": name,
"user": user,
"fd_count": fd_count,
"syscall_pressure": syscall_pressure,
"seccomp_mode": seccomp_mode
})
except Exception:
continue
syscall_nodes.sort(key=lambda x: x.get("syscall_pressure", 0), reverse=True)
syscall_nodes = syscall_nodes[:14]

network_nodes = {}
try:
for conn in psutil.net_connections(kind="inet"):
pid = int(getattr(conn, "pid", 0) or 0)
if pid <= 0:
continue
remote_ip = ""
try:
raddr = getattr(conn, "raddr", None)
if raddr and len(raddr) >= 1:
remote_ip = str(raddr[0])
except Exception:
remote_ip = ""
status = str(getattr(conn, "status", "") or "").upper()
bucket = network_nodes.get(pid)
if not bucket:
proc_name = "unknown"
try:
proc_name = psutil.Process(pid).name()
except Exception:
proc_name = "unknown"
bucket = {
"pid": pid,
"name": proc_name,
"connections": 0,
"remote_ips": set(),
"states": {}
}
network_nodes[pid] = bucket
bucket["connections"] += 1
if remote_ip:
bucket["remote_ips"].add(remote_ip)
if status:
bucket["states"][status] = bucket["states"].get(status, 0) + 1
except Exception:
pass

network_tracing = []
for _, row in network_nodes.items():
states_sorted = sorted(row["states"].items(), key=lambda kv: kv[1], reverse=True)
top_state = states_sorted[0][0] if states_sorted else "UNKNOWN"
network_tracing.append({
"pid": int(row["pid"]),
"name": str(row["name"]),
"connections": int(row["connections"]),
"unique_peers": int(len(row["remote_ips"])),
"peer_sample": sorted(list(row["remote_ips"]))[:4],
"top_state": top_state
})
network_tracing.sort(key=lambda x: (x.get("connections", 0), x.get("unique_peers", 0)), reverse=True)
network_tracing = network_tracing[:14]

security_hooks = [
{
"name": "LSM stack",
"status": "active" if active_lsms else "unknown",
"detail": ",".join(active_lsms[:4]) if active_lsms else "n/a"
},
{
"name": "SELinux/AppArmor engines",
"status": "active" if any(x in {"selinux", "apparmor"} for x in active_lsms) else "inactive",
"detail": "policy-enforcement-path"
},
{
"name": "BPF LSM",
"status": "active" if "bpf" in active_lsms else "inactive",
"detail": "dynamic-policy-hook"
},
{
"name": "seccomp filter gate",
"status": "active" if (seccomp_modes.get("filter", 0) + seccomp_modes.get("strict", 0)) > 0 else "inactive",
"detail": f"filter:{seccomp_modes.get('filter', 0)} strict:{seccomp_modes.get('strict', 0)}"
},
{
"name": "Yama ptrace scope",
"status": "hardened" if yama_scope in {"2", "3"} else ("relaxed" if yama_scope in {"0", "1"} else "unknown"),
"detail": yama_scope or "n/a"
}
]

# Neural graph model: nodes=processes, edges=behavior interactions.
node_pool = {}
for row in syscall_nodes[:16]:
pid = int(row.get("pid") or 0)
if pid <= 0:
continue
node_pool[pid] = {
"pid": pid,
"ppid": int(row.get("ppid") or 0),
"name": str(row.get("name") or "unknown"),
"user": str(row.get("user") or ""),
"syscall_pressure": int(row.get("syscall_pressure") or 0),
"fd_count": int(row.get("fd_count") or 0),
"seccomp_mode": str(row.get("seccomp_mode") or "unknown"),
"connections": 0,
"unique_peers": 0
}
for row in network_tracing[:16]:
pid = int(row.get("pid") or 0)
if pid <= 0:
continue
if pid not in node_pool:
node_pool[pid] = {
"pid": pid,
"ppid": 0,
"name": str(row.get("name") or "unknown"),
"user": "",
"syscall_pressure": 0,
"fd_count": 0,
"seccomp_mode": "unknown",
"connections": 0,
"unique_peers": 0
}
node_pool[pid]["connections"] = int(row.get("connections") or 0)
node_pool[pid]["unique_peers"] = int(row.get("unique_peers") or 0)

edges = []
edge_keys = set()
network_by_pid = {int(r.get("pid") or 0): r for r in network_tracing}
node_pids = sorted(node_pool.keys())

def _add_edge(src_pid, dst_pid, edge_type, weight):
src = int(src_pid or 0)
dst = int(dst_pid or 0)
if src <= 0 or dst <= 0 or src == dst:
return
if src not in node_pool or dst not in node_pool:
return
pair = tuple(sorted((src, dst)))
key = (pair[0], pair[1], edge_type)
if key in edge_keys:
return
edge_keys.add(key)
edges.append({
"source": src,
"target": dst,
"type": edge_type,
"weight": float(max(0.1, min(1.0, weight)))
})

# IPC edges: parent-child links inside the sampled set.
for pid, node in node_pool.items():
ppid = int(node.get("ppid") or 0)
if ppid in node_pool:
_add_edge(pid, ppid, "ipc", 0.72)

# Syscalls edges: close-pressure processes likely competing on kernel hooks.
sorted_by_pressure = sorted(node_pool.values(), key=lambda n: n.get("syscall_pressure", 0), reverse=True)
for i in range(len(sorted_by_pressure) - 1):
a = sorted_by_pressure[i]
b = sorted_by_pressure[i + 1]
diff = abs(int(a.get("syscall_pressure", 0)) - int(b.get("syscall_pressure", 0)))
weight = 1.0 - min(0.8, diff / 100.0)
_add_edge(int(a.get("pid")), int(b.get("pid")), "syscalls", weight)

# Network edges: connect nodes that share at least one peer sample.
for i in range(len(node_pids)):
for j in range(i + 1, len(node_pids)):
pa = node_pids[i]
pb = node_pids[j]
ra = network_by_pid.get(pa) or {}
rb = network_by_pid.get(pb) or {}
sa = set(ra.get("peer_sample") or [])
sb = set(rb.get("peer_sample") or [])
if sa and sb and (sa & sb):
_add_edge(pa, pb, "network", 0.88)

# File access edges: processes with high FD count and same user.
for i in range(len(node_pids)):
for j in range(i + 1, len(node_pids)):
na = node_pool[node_pids[i]]
nb = node_pool[node_pids[j]]
if not na.get("user") or na.get("user") != nb.get("user"):
continue
fa = int(na.get("fd_count") or 0)
fb = int(nb.get("fd_count") or 0)
if fa >= 16 and fb >= 16:
_add_edge(int(na.get("pid")), int(nb.get("pid")), "file_access", 0.64)

nodes = list(node_pool.values())[:18]
edges = edges[:64]

return {
"timestamp": datetime.utcnow().isoformat() + "Z",
"syscalls_interception": syscall_nodes,
"network_tracing": network_tracing,
"security_hooks": security_hooks,
"neural_graph": {
"nodes": nodes,
"edges": edges
},
"meta": {
"processes_sampled": len(syscall_nodes),
"network_processes": len(network_tracing),
"seccomp_filter_percent": round(
(seccomp_modes.get("filter", 0) + seccomp_modes.get("strict", 0))
* 100.0 / max(1, sum(seccomp_modes.values())),
2
),
"mode": "live-heuristic-v1"
}
}

@app.route('/api/kernel-dna')
def kernel_dna():
"""API endpoint for Kernel DNA visualization data"""
Expand All @@ -4473,6 +4756,14 @@ def security_realtime():
except Exception as e:
return jsonify({'error': str(e)}), 500

@app.route('/api/processes-realtime')
def processes_realtime():
"""Realtime-ish processes interaction feed for processes visualization."""
try:
return jsonify(collect_processes_realtime())
except Exception as e:
return jsonify({'error': str(e)}), 500

@app.route('/api/frontend-logs', methods=['POST', 'OPTIONS'])
def ingest_frontend_logs():
"""Receive frontend logs in ECS-like JSON and append to local JSONL file."""
Expand Down
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ <h2>Linux Kernel Ring 0 Visualization</h2>
<script src="/static/js/ui-chrome.js?v=1"></script>
<script src="/static/js/active-connections.js?v=4"></script>
<script src="/static/js/nginx_files.js?v=2"></script>
<script src="/static/js/right-semicircle-menu.js?v=44"></script>
<script src="/static/js/right-semicircle-menu.js?v=46"></script>
<script src="/static/js/kernel-dna.js?v=23"></script>
<script src="/static/js/network-stack.js?v=6"></script>
<script src="/static/js/devices-belt.js?v=14"></script>
Expand All @@ -122,6 +122,6 @@ <h2>Linux Kernel Ring 0 Visualization</h2>
// console.error('❌ Proc3DVisualization class NOT loaded!');
// }
</script>
<script src="/static/js/main.js?v=169"></script>
<script src="/static/js/main.js?v=171"></script>
</body>
</html>
Loading
Loading