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
140 changes: 140 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4085,6 +4085,140 @@ def classify_trust(score):
{"name": "suspicious-processes", "value": int(suspicious_processes), "severity": "high" if suspicious_processes > 6 else "medium"}
]

# Stage 3: kernel security tools insights.
def _read_text(path):
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
return str(f.read().strip())
except Exception:
return ""

# LSM status matrix (best effort, distro dependent).
apparmor_raw = _read_text("/sys/module/apparmor/parameters/enabled")
selinux_enforce = _read_text("/sys/fs/selinux/enforce")
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")
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"
},
{
"name": "SELinux",
"status": "enforcing" if selinux_enforce == "1" else ("disabled" if selinux_enforce == "0" else "unknown"),
"detail": selinux_enforce or "n/a"
},
{
"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"
},
{
"name": "unprivileged bpf",
"status": "blocked" if bpf_unpriv == "1" else ("allowed" if bpf_unpriv == "0" else "unknown"),
"detail": bpf_unpriv or "n/a"
},
{
"name": "Landlock",
"status": "present" if landlock_present else "absent",
"detail": "sysfs" if landlock_present else "n/a"
},
{
"name": "IMA/EVM",
"status": "present" if ima_present else "absent",
"detail": "sysfs" if ima_present else "n/a"
}
]

# Capabilities drift (CapEff/CapPrm from /proc/<pid>/status).
dangerous_caps = {
12: "CAP_NET_ADMIN",
16: "CAP_SYS_MODULE",
17: "CAP_SYS_RAWIO",
19: "CAP_SYS_PTRACE",
21: "CAP_SYS_ADMIN",
39: "CAP_BPF"
}
capabilities_rows = []
seccomp_counts = {"none": 0, "strict": 0, "filter": 0, "unknown": 0}

for row in process_rows[:180]:
pid = int(row.get("pid") or 0)
if pid <= 0:
continue
cap_eff_hex = ""
cap_prm_hex = ""
seccomp_mode = "unknown"
try:
with open(f"/proc/{pid}/status", "r", encoding="utf-8", errors="ignore") as f:
for ln in f:
if ln.startswith("CapEff:"):
cap_eff_hex = ln.split(":", 1)[1].strip()
elif ln.startswith("CapPrm:"):
cap_prm_hex = ln.split(":", 1)[1].strip()
elif ln.startswith("Seccomp:"):
seccomp_raw = ln.split(":", 1)[1].strip()
if seccomp_raw == "0":
seccomp_mode = "none"
elif seccomp_raw == "1":
seccomp_mode = "strict"
elif seccomp_raw == "2":
seccomp_mode = "filter"
else:
seccomp_mode = "unknown"
except Exception:
pass

seccomp_counts[seccomp_mode] = seccomp_counts.get(seccomp_mode, 0) + 1
if not cap_eff_hex:
continue
try:
cap_eff_val = int(cap_eff_hex, 16)
cap_prm_val = int(cap_prm_hex or "0", 16)
except Exception:
continue

matched = [name for bit, name in dangerous_caps.items() if (cap_eff_val & (1 << bit))]
if not matched:
continue
risk = min(100, 20 + len(matched) * 16 + (10 if row.get("user") == "root" else 0))
capabilities_rows.append({
"pid": pid,
"name": row.get("name", "unknown"),
"user": row.get("user", ""),
"seccomp": seccomp_mode,
"cap_eff": cap_eff_hex,
"cap_prm": cap_prm_hex or "0",
"dangerous": matched[:4],
"risk_score": int(risk)
})

capabilities_rows.sort(key=lambda x: (x.get("risk_score", 0), len(x.get("dangerous", []))), reverse=True)
capabilities_drift = capabilities_rows[:8]

# Seccomp coverage summary + top unsandboxed risky processes.
total_seccomp_sample = max(1, sum(seccomp_counts.values()))
unsandboxed = [r for r in capabilities_rows if r.get("seccomp") == "none"]
unsandboxed.sort(key=lambda x: x.get("risk_score", 0), reverse=True)
seccomp_coverage = {
"none": int(seccomp_counts.get("none", 0)),
"strict": int(seccomp_counts.get("strict", 0)),
"filter": int(seccomp_counts.get("filter", 0)),
"unknown": int(seccomp_counts.get("unknown", 0)),
"coverage_percent": round((seccomp_counts.get("filter", 0) + seccomp_counts.get("strict", 0)) * 100.0 / total_seccomp_sample, 2),
"high_risk_unsandboxed": [
{
"pid": int(r.get("pid", 0)),
"name": str(r.get("name", "unknown")),
"risk_score": int(r.get("risk_score", 0))
}
for r in unsandboxed[:6]
]
}

prev_ts = SECURITY_PREV["timestamp"]
prev_events = int(SECURITY_PREV["events"] or 0)
current_events = len(lanes)
Expand All @@ -4108,13 +4242,19 @@ def classify_trust(score):
},
"trust_graph": trust_graph,
"attack_surface": attack_surface,
"security_tools": {
"lsm_status": lsm_status,
"capabilities_drift": capabilities_drift,
"seccomp_coverage": seccomp_coverage
},
"meta": {
"decisions_per_sec": decisions_per_sec,
"events": current_events,
"trusted": sum(1 for p in trust_graph if p.get("trust") == "trusted"),
"observe": sum(1 for p in trust_graph if p.get("trust") == "observe"),
"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"
}
}
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=6"></script>
<script src="/static/js/security-belt.js?v=10"></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
144 changes: 137 additions & 7 deletions static/js/security-belt.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Security Subsystem Visualization (Stage 1)
// Version: 6
// Security Subsystem Visualization (Stage 3)
// Version: 10

debugLog('🛡️ security-belt.js v6: Script loading...');
debugLog('🛡️ security-belt.js v10: Script loading...');

class SecuritySubsystemVisualization {
constructor() {
Expand All @@ -22,6 +22,7 @@ class SecuritySubsystemVisualization {
this.selectedProcessFilter = null;
this.hoveredProcessPid = null;
this.trustNodeHitAreas = [];
this.capabilityRowHitAreas = [];
this.canvasClickHandler = null;
this.canvasMouseMoveHandler = null;
}
Expand Down Expand Up @@ -85,7 +86,7 @@ class SecuritySubsystemVisualization {
z-index: 1001;
text-shadow: 0 0 8px rgba(180, 210, 255, 0.25);
`;
title.textContent = 'KERNEL SECURITY SUBSYSTEM (stage 1)';
title.textContent = 'KERNEL SECURITY SUBSYSTEM (stage 3)';
this.container.appendChild(title);
this.overlayNodes.push(title);

Expand All @@ -100,7 +101,7 @@ class SecuritySubsystemVisualization {
font-size: 11px;
z-index: 1001;
`;
legend.textContent = 'threat decision pipeline + process trust graph + attack surface map';
legend.textContent = 'pipeline + trust + attack surface + lsm/capabilities/seccomp tools';
this.container.appendChild(legend);
this.overlayNodes.push(legend);

Expand Down Expand Up @@ -265,6 +266,14 @@ class SecuritySubsystemVisualization {
break;
}
}
if (!matched) {
for (const row of this.capabilityRowHitAreas) {
if (x >= row.x && x <= row.x + row.w && y >= row.y && y <= row.y + row.h) {
matched = row;
break;
}
}
}
if (!matched) return;
const pid = Number(matched.pid || 0);
if (this.selectedProcessFilter && Number(this.selectedProcessFilter.pid || 0) === pid) {
Expand Down Expand Up @@ -294,6 +303,14 @@ class SecuritySubsystemVisualization {
break;
}
}
if (!hovered) {
for (const row of this.capabilityRowHitAreas) {
if (x >= row.x && x <= row.x + row.w && y >= row.y && y <= row.y + row.h) {
hovered = Number(row.pid || 0);
break;
}
}
}
this.hoveredProcessPid = hovered;
this.canvas.style.cursor = hovered ? 'pointer' : 'default';
}
Expand Down Expand Up @@ -513,6 +530,110 @@ class SecuritySubsystemVisualization {
});
}

drawLsmStatusCard(x, y, w, h, telemetry) {
this.drawPanel(x, y, w, h, 'LSM STATUS MATRIX');
const rows = Array.isArray(telemetry?.security_tools?.lsm_status)
? telemetry.security_tools.lsm_status.slice(0, 6)
: [];
rows.forEach((row, idx) => {
const yy = y + 40 + idx * 22;
const status = String(row.status || 'unknown').toLowerCase();
let color = '#8a9cea';
if (status.includes('enforcing') || status.includes('blocked') || status.includes('hardened') || status.includes('present')) color = '#60d69d';
if (status.includes('relaxed')) color = '#f4c977';
if (status.includes('disabled') || status.includes('absent')) color = '#eb7e7e';
this.ctx.fillStyle = 'rgba(10, 14, 20, 0.46)';
this.ctx.fillRect(x + 12, yy - 12, w - 24, 18);
this.ctx.fillStyle = '#cfdced';
this.ctx.font = '10px "Share Tech Mono", monospace';
this.ctx.fillText(String(row.name || 'lsm'), x + 16, yy);
this.ctx.fillStyle = color;
this.ctx.fillText(String(row.status || 'unknown').toUpperCase(), x + Math.floor(w * 0.56), yy);
this.ctx.fillStyle = 'rgba(185, 200, 220, 0.7)';
this.ctx.fillText(String(row.detail || ''), x + w - 74, yy);
});
}

drawCapabilitiesCard(x, y, w, h, telemetry) {
this.drawPanel(x, y, w, h, 'CAPABILITIES DRIFT');
this.capabilityRowHitAreas = [];
const rows = Array.isArray(telemetry?.security_tools?.capabilities_drift)
? telemetry.security_tools.capabilities_drift.slice(0, 6)
: [];
rows.forEach((row, idx) => {
const yy = y + 40 + idx * 22;
const pid = Number(row.pid || 0);
const risk = Number(row.risk_score || 0);
const danger = Array.isArray(row.dangerous) ? row.dangerous : [];
const color = risk >= 70 ? '#eb7e7e' : (risk >= 45 ? '#f4c977' : '#8a9cea');
const isSelected = this.selectedProcessFilter && Number(this.selectedProcessFilter.pid || 0) === pid;
const isHovered = Number(this.hoveredProcessPid || 0) === pid;
const rowX = x + 12;
const rowY = yy - 12;
const rowW = w - 24;
const rowH = 18;
this.ctx.fillStyle = isSelected ? 'rgba(32, 52, 81, 0.62)' : (isHovered ? 'rgba(22, 34, 52, 0.56)' : 'rgba(10, 14, 20, 0.46)');
this.ctx.fillRect(rowX, rowY, rowW, rowH);
if (isSelected || isHovered) {
this.ctx.strokeStyle = isSelected ? 'rgba(124, 178, 255, 0.95)' : 'rgba(112, 152, 216, 0.72)';
this.ctx.lineWidth = isSelected ? 1.2 : 0.9;
this.ctx.strokeRect(rowX + 0.5, rowY + 0.5, rowW - 1, rowH - 1);
}
this.ctx.fillStyle = '#d8e5f7';
this.ctx.font = '10px "Share Tech Mono", monospace';
this.ctx.fillText(`${String(row.name || 'proc').slice(0, 10)}:${pid || 0}`, x + 16, yy);
this.ctx.fillStyle = color;
this.ctx.fillText(`risk ${risk}`, x + Math.floor(w * 0.40), yy);
this.ctx.fillStyle = 'rgba(185, 200, 220, 0.72)';
this.ctx.fillText(danger.slice(0, 2).join(','), x + Math.floor(w * 0.56), yy);
this.capabilityRowHitAreas.push({
x: rowX,
y: rowY,
w: rowW,
h: rowH,
pid,
name: row.name || 'unknown'
});
});
}

drawSeccompCoverageCard(x, y, w, h, telemetry) {
this.drawPanel(x, y, w, h, 'SECCOMP COVERAGE');
const cov = telemetry?.security_tools?.seccomp_coverage || {};
const none = Number(cov.none || 0);
const strict = Number(cov.strict || 0);
const filter = Number(cov.filter || 0);
const unknown = Number(cov.unknown || 0);
const coverage = Number(cov.coverage_percent || 0);

this.ctx.fillStyle = 'rgba(179, 203, 232, 0.9)';
this.ctx.font = '11px "Share Tech Mono", monospace';
this.ctx.fillText(`coverage ${coverage.toFixed(2)}%`, x + 14, y + 42);
this.ctx.fillText(`filter:${filter} strict:${strict} none:${none} unknown:${unknown}`, x + 14, y + 60);

const barX = x + 14;
const barY = y + 72;
const barW = w - 28;
const barH = 12;
this.ctx.fillStyle = 'rgba(12, 16, 22, 0.62)';
this.ctx.fillRect(barX, barY, barW, barH);
const fillW = Math.max(0, Math.min(barW, (coverage / 100) * barW));
this.ctx.fillStyle = coverage >= 80 ? '#60d69d' : (coverage >= 50 ? '#f4c977' : '#eb7e7e');
this.ctx.fillRect(barX, barY, fillW, barH);

const unsandboxed = Array.isArray(cov.high_risk_unsandboxed) ? cov.high_risk_unsandboxed.slice(0, 4) : [];
unsandboxed.forEach((row, idx) => {
const yy = y + 102 + idx * 20;
this.ctx.fillStyle = 'rgba(10, 14, 20, 0.46)';
this.ctx.fillRect(x + 12, yy - 11, w - 24, 16);
this.ctx.fillStyle = '#d8e5f7';
this.ctx.font = '10px "Share Tech Mono", monospace';
this.ctx.fillText(`${String(row.name || 'proc').slice(0, 14)}:${row.pid || 0}`, x + 16, yy);
this.ctx.fillStyle = '#eb7e7e';
this.ctx.fillText(`risk ${Number(row.risk_score || 0)}`, x + w - 88, yy);
});
}

drawHeaderStats(telemetry) {
const meta = telemetry?.meta || {};
this.ctx.fillStyle = 'rgba(179, 203, 232, 0.92)';
Expand All @@ -525,12 +646,13 @@ class SecuritySubsystemVisualization {
500,
114
);
this.ctx.fillText(`seccomp coverage ${Number(meta.seccomp_coverage_percent || 0).toFixed(2)}%`, 930, 114);
this.ctx.fillStyle = 'rgba(180, 196, 220, 0.86)';
const selected = this.selectedProcessFilter;
if (selected) {
this.ctx.fillText(`selected process: ${selected.name}:${selected.pid} (click again to clear)`, 26, 132);
} else {
this.ctx.fillText('tip: click a process node in PROCESS TRUST GRAPH to filter pipeline', 26, 132);
this.ctx.fillText('tip: click a process node or CAPABILITIES DRIFT row to filter pipeline', 26, 132);
}
}

Expand Down Expand Up @@ -577,18 +699,26 @@ class SecuritySubsystemVisualization {

const gap = 16;
const panelTop = 172;
const panelH = Math.max(340, h - panelTop - 24);
const toolsH = Math.max(128, Math.min(176, Math.floor(h * 0.22)));
const panelH = Math.max(220, h - panelTop - toolsH - gap - 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);

const leftX = gap;
const centerX = leftX + leftW + gap;
const rightX = centerX + centerW + gap;
const toolsY = panelTop + panelH + gap;
const toolsW = Math.max(220, Math.floor((w - gap * 4) / 3));
const tools2X = leftX + toolsW + gap;
const tools3X = tools2X + toolsW + gap;

this.drawDecisionPipeline(leftX, panelTop, leftW, panelH, this.telemetry);
this.drawTrustGraph(centerX, panelTop, centerW, panelH, this.telemetry);
this.drawAttackSurface(rightX, panelTop, rightW, panelH, this.telemetry);
this.drawLsmStatusCard(leftX, toolsY, toolsW, toolsH, this.telemetry);
this.drawCapabilitiesCard(tools2X, toolsY, toolsW, toolsH, this.telemetry);
this.drawSeccompCoverageCard(tools3X, toolsY, toolsW, toolsH, this.telemetry);
}

animate() {
Expand Down
2 changes: 1 addition & 1 deletion templates/linux-security-subsystem.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ <h2>Attack Surface Map</h2>

<script src="/static/js/safe-utils.js?v=2"></script>
<script src="/static/js/frontend-logger.js?v=2"></script>
<script src="/static/js/security-belt.js?v=6"></script>
<script src="/static/js/security-belt.js?v=10"></script>
<script>
(function startSecurityPage() {
if (typeof window.SecuritySubsystemVisualization !== 'function') {
Expand Down
Loading