defense-mcp-server 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +471 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/build/core/auto-installer.d.ts +102 -0
- package/build/core/auto-installer.d.ts.map +1 -0
- package/build/core/auto-installer.js +833 -0
- package/build/core/backup-manager.d.ts +63 -0
- package/build/core/backup-manager.d.ts.map +1 -0
- package/build/core/backup-manager.js +189 -0
- package/build/core/changelog.d.ts +75 -0
- package/build/core/changelog.d.ts.map +1 -0
- package/build/core/changelog.js +123 -0
- package/build/core/command-allowlist.d.ts +129 -0
- package/build/core/command-allowlist.d.ts.map +1 -0
- package/build/core/command-allowlist.js +849 -0
- package/build/core/config.d.ts +79 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +193 -0
- package/build/core/dependency-validator.d.ts +106 -0
- package/build/core/dependency-validator.d.ts.map +1 -0
- package/build/core/dependency-validator.js +405 -0
- package/build/core/distro-adapter.d.ts +177 -0
- package/build/core/distro-adapter.d.ts.map +1 -0
- package/build/core/distro-adapter.js +481 -0
- package/build/core/distro.d.ts +68 -0
- package/build/core/distro.d.ts.map +1 -0
- package/build/core/distro.js +457 -0
- package/build/core/encrypted-state.d.ts +76 -0
- package/build/core/encrypted-state.d.ts.map +1 -0
- package/build/core/encrypted-state.js +209 -0
- package/build/core/executor.d.ts +56 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +350 -0
- package/build/core/installer.d.ts +92 -0
- package/build/core/installer.d.ts.map +1 -0
- package/build/core/installer.js +1072 -0
- package/build/core/logger.d.ts +102 -0
- package/build/core/logger.d.ts.map +1 -0
- package/build/core/logger.js +132 -0
- package/build/core/parsers.d.ts +151 -0
- package/build/core/parsers.d.ts.map +1 -0
- package/build/core/parsers.js +479 -0
- package/build/core/policy-engine.d.ts +170 -0
- package/build/core/policy-engine.d.ts.map +1 -0
- package/build/core/policy-engine.js +656 -0
- package/build/core/preflight.d.ts +157 -0
- package/build/core/preflight.d.ts.map +1 -0
- package/build/core/preflight.js +638 -0
- package/build/core/privilege-manager.d.ts +108 -0
- package/build/core/privilege-manager.d.ts.map +1 -0
- package/build/core/privilege-manager.js +363 -0
- package/build/core/rate-limiter.d.ts +67 -0
- package/build/core/rate-limiter.d.ts.map +1 -0
- package/build/core/rate-limiter.js +129 -0
- package/build/core/rollback.d.ts +73 -0
- package/build/core/rollback.d.ts.map +1 -0
- package/build/core/rollback.js +278 -0
- package/build/core/safeguards.d.ts +58 -0
- package/build/core/safeguards.d.ts.map +1 -0
- package/build/core/safeguards.js +448 -0
- package/build/core/sanitizer.d.ts +118 -0
- package/build/core/sanitizer.d.ts.map +1 -0
- package/build/core/sanitizer.js +459 -0
- package/build/core/secure-fs.d.ts +67 -0
- package/build/core/secure-fs.d.ts.map +1 -0
- package/build/core/secure-fs.js +143 -0
- package/build/core/spawn-safe.d.ts +55 -0
- package/build/core/spawn-safe.d.ts.map +1 -0
- package/build/core/spawn-safe.js +146 -0
- package/build/core/sudo-guard.d.ts +145 -0
- package/build/core/sudo-guard.d.ts.map +1 -0
- package/build/core/sudo-guard.js +349 -0
- package/build/core/sudo-session.d.ts +100 -0
- package/build/core/sudo-session.d.ts.map +1 -0
- package/build/core/sudo-session.js +319 -0
- package/build/core/tool-dependencies.d.ts +61 -0
- package/build/core/tool-dependencies.d.ts.map +1 -0
- package/build/core/tool-dependencies.js +571 -0
- package/build/core/tool-registry.d.ts +111 -0
- package/build/core/tool-registry.d.ts.map +1 -0
- package/build/core/tool-registry.js +656 -0
- package/build/core/tool-wrapper.d.ts +73 -0
- package/build/core/tool-wrapper.d.ts.map +1 -0
- package/build/core/tool-wrapper.js +296 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +247 -0
- package/build/tools/access-control.d.ts +9 -0
- package/build/tools/access-control.d.ts.map +1 -0
- package/build/tools/access-control.js +1818 -0
- package/build/tools/api-security.d.ts +12 -0
- package/build/tools/api-security.d.ts.map +1 -0
- package/build/tools/api-security.js +901 -0
- package/build/tools/app-hardening.d.ts +11 -0
- package/build/tools/app-hardening.d.ts.map +1 -0
- package/build/tools/app-hardening.js +768 -0
- package/build/tools/backup.d.ts +8 -0
- package/build/tools/backup.d.ts.map +1 -0
- package/build/tools/backup.js +381 -0
- package/build/tools/cloud-security.d.ts +17 -0
- package/build/tools/cloud-security.d.ts.map +1 -0
- package/build/tools/cloud-security.js +739 -0
- package/build/tools/compliance.d.ts +10 -0
- package/build/tools/compliance.d.ts.map +1 -0
- package/build/tools/compliance.js +1225 -0
- package/build/tools/container-security.d.ts +14 -0
- package/build/tools/container-security.d.ts.map +1 -0
- package/build/tools/container-security.js +788 -0
- package/build/tools/deception.d.ts +13 -0
- package/build/tools/deception.d.ts.map +1 -0
- package/build/tools/deception.js +763 -0
- package/build/tools/dns-security.d.ts +93 -0
- package/build/tools/dns-security.d.ts.map +1 -0
- package/build/tools/dns-security.js +745 -0
- package/build/tools/drift-detection.d.ts +8 -0
- package/build/tools/drift-detection.d.ts.map +1 -0
- package/build/tools/drift-detection.js +326 -0
- package/build/tools/ebpf-security.d.ts +15 -0
- package/build/tools/ebpf-security.d.ts.map +1 -0
- package/build/tools/ebpf-security.js +294 -0
- package/build/tools/encryption.d.ts +9 -0
- package/build/tools/encryption.d.ts.map +1 -0
- package/build/tools/encryption.js +1667 -0
- package/build/tools/firewall.d.ts +9 -0
- package/build/tools/firewall.d.ts.map +1 -0
- package/build/tools/firewall.js +1398 -0
- package/build/tools/hardening.d.ts +10 -0
- package/build/tools/hardening.d.ts.map +1 -0
- package/build/tools/hardening.js +2654 -0
- package/build/tools/ids.d.ts +9 -0
- package/build/tools/ids.d.ts.map +1 -0
- package/build/tools/ids.js +624 -0
- package/build/tools/incident-response.d.ts +10 -0
- package/build/tools/incident-response.d.ts.map +1 -0
- package/build/tools/incident-response.js +1180 -0
- package/build/tools/logging.d.ts +12 -0
- package/build/tools/logging.d.ts.map +1 -0
- package/build/tools/logging.js +454 -0
- package/build/tools/malware.d.ts +10 -0
- package/build/tools/malware.d.ts.map +1 -0
- package/build/tools/malware.js +532 -0
- package/build/tools/meta.d.ts +11 -0
- package/build/tools/meta.d.ts.map +1 -0
- package/build/tools/meta.js +2278 -0
- package/build/tools/network-defense.d.ts +12 -0
- package/build/tools/network-defense.d.ts.map +1 -0
- package/build/tools/network-defense.js +760 -0
- package/build/tools/patch-management.d.ts +3 -0
- package/build/tools/patch-management.d.ts.map +1 -0
- package/build/tools/patch-management.js +708 -0
- package/build/tools/process-security.d.ts +12 -0
- package/build/tools/process-security.d.ts.map +1 -0
- package/build/tools/process-security.js +784 -0
- package/build/tools/reporting.d.ts +11 -0
- package/build/tools/reporting.d.ts.map +1 -0
- package/build/tools/reporting.js +559 -0
- package/build/tools/secrets.d.ts +9 -0
- package/build/tools/secrets.d.ts.map +1 -0
- package/build/tools/secrets.js +596 -0
- package/build/tools/siem-integration.d.ts +18 -0
- package/build/tools/siem-integration.d.ts.map +1 -0
- package/build/tools/siem-integration.js +754 -0
- package/build/tools/sudo-management.d.ts +18 -0
- package/build/tools/sudo-management.d.ts.map +1 -0
- package/build/tools/sudo-management.js +737 -0
- package/build/tools/supply-chain-security.d.ts +8 -0
- package/build/tools/supply-chain-security.d.ts.map +1 -0
- package/build/tools/supply-chain-security.js +256 -0
- package/build/tools/threat-intel.d.ts +22 -0
- package/build/tools/threat-intel.d.ts.map +1 -0
- package/build/tools/threat-intel.js +749 -0
- package/build/tools/vulnerability-management.d.ts +11 -0
- package/build/tools/vulnerability-management.d.ts.map +1 -0
- package/build/tools/vulnerability-management.js +667 -0
- package/build/tools/waf.d.ts +12 -0
- package/build/tools/waf.d.ts.map +1 -0
- package/build/tools/waf.js +843 -0
- package/build/tools/wireless-security.d.ts +19 -0
- package/build/tools/wireless-security.d.ts.map +1 -0
- package/build/tools/wireless-security.js +826 -0
- package/build/tools/zero-trust-network.d.ts +8 -0
- package/build/tools/zero-trust-network.d.ts.map +1 -0
- package/build/tools/zero-trust-network.js +367 -0
- package/docs/SAFEGUARDS.md +518 -0
- package/docs/TOOLS-REFERENCE.md +665 -0
- package/package.json +87 -0
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process security tools for Kali Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool: process_security
|
|
5
|
+
* Actions: audit_running, check_capabilities, check_namespaces, detect_anomalies, cgroup_audit
|
|
6
|
+
*
|
|
7
|
+
* Inspects running processes for security concerns including privilege escalation,
|
|
8
|
+
* capability abuse, namespace isolation, anomalous behavior, and cgroup resource limits.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { spawnSafe } from "../core/spawn-safe.js";
|
|
12
|
+
import { createTextContent, createErrorContent, } from "../core/parsers.js";
|
|
13
|
+
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
14
|
+
/** Processes known to legitimately run as root */
|
|
15
|
+
const KNOWN_SAFE_ROOT_PROCESSES = new Set([
|
|
16
|
+
"init", "systemd", "sshd", "cron", "crond", "rsyslogd", "journald",
|
|
17
|
+
"udevd", "dbus-daemon", "NetworkManager", "polkitd", "accounts-daemon",
|
|
18
|
+
"login", "getty", "agetty", "kthreadd", "ksoftirqd", "kworker",
|
|
19
|
+
"rcu_sched", "rcu_bh", "migration", "watchdog", "dockerd", "containerd",
|
|
20
|
+
"snapd", "multipathd", "irqbalance", "thermald", "udisksd",
|
|
21
|
+
"packagekitd", "gdm", "lightdm", "sddm",
|
|
22
|
+
]);
|
|
23
|
+
/** Linux capabilities considered dangerous */
|
|
24
|
+
const DANGEROUS_CAPABILITIES = new Set([
|
|
25
|
+
"cap_sys_admin", "cap_sys_ptrace", "cap_net_raw", "cap_sys_module",
|
|
26
|
+
"cap_dac_override", "cap_setuid", "cap_setgid",
|
|
27
|
+
]);
|
|
28
|
+
/** Maximum number of processes to inspect to avoid overwhelming output */
|
|
29
|
+
const MAX_PROCESSES = 100;
|
|
30
|
+
/**
|
|
31
|
+
* Run a command via spawnSafe and collect output as a promise.
|
|
32
|
+
* Handles errors gracefully — returns error info instead of throwing.
|
|
33
|
+
*/
|
|
34
|
+
async function runCommand(command, args, timeoutMs = 30_000) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
let child;
|
|
37
|
+
try {
|
|
38
|
+
child = spawnSafe(command, args);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
42
|
+
resolve({ stdout: "", stderr: msg, exitCode: -1 });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
let stdout = "";
|
|
46
|
+
let stderr = "";
|
|
47
|
+
let resolved = false;
|
|
48
|
+
const timer = setTimeout(() => {
|
|
49
|
+
if (!resolved) {
|
|
50
|
+
resolved = true;
|
|
51
|
+
child.kill("SIGTERM");
|
|
52
|
+
resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
|
|
53
|
+
}
|
|
54
|
+
}, timeoutMs);
|
|
55
|
+
child.stdout?.on("data", (data) => {
|
|
56
|
+
stdout += data.toString();
|
|
57
|
+
});
|
|
58
|
+
child.stderr?.on("data", (data) => {
|
|
59
|
+
stderr += data.toString();
|
|
60
|
+
});
|
|
61
|
+
child.on("close", (code) => {
|
|
62
|
+
if (!resolved) {
|
|
63
|
+
resolved = true;
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
child.on("error", (err) => {
|
|
69
|
+
if (!resolved) {
|
|
70
|
+
resolved = true;
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
resolve({ stdout, stderr: err.message, exitCode: -1 });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse `ps auxf` output into structured process info.
|
|
79
|
+
*/
|
|
80
|
+
function parsePsOutput(output) {
|
|
81
|
+
const lines = output.trim().split("\n");
|
|
82
|
+
if (lines.length < 2)
|
|
83
|
+
return [];
|
|
84
|
+
// Skip header line
|
|
85
|
+
const processes = [];
|
|
86
|
+
for (let i = 1; i < lines.length && processes.length < MAX_PROCESSES; i++) {
|
|
87
|
+
const line = lines[i];
|
|
88
|
+
// ps aux format: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
|
89
|
+
const parts = line.trim().split(/\s+/);
|
|
90
|
+
if (parts.length < 11)
|
|
91
|
+
continue;
|
|
92
|
+
processes.push({
|
|
93
|
+
user: parts[0],
|
|
94
|
+
pid: parseInt(parts[1], 10),
|
|
95
|
+
cpu: parseFloat(parts[2]),
|
|
96
|
+
mem: parseFloat(parts[3]),
|
|
97
|
+
vsz: parseInt(parts[4], 10),
|
|
98
|
+
rss: parseInt(parts[5], 10),
|
|
99
|
+
tty: parts[6],
|
|
100
|
+
stat: parts[7],
|
|
101
|
+
start: parts[8],
|
|
102
|
+
time: parts[9],
|
|
103
|
+
command: parts.slice(10).join(" "),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return processes;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get the base process name from a full command path.
|
|
110
|
+
*/
|
|
111
|
+
function getProcessName(command) {
|
|
112
|
+
// Remove leading path modifiers like `\_` from ps tree output
|
|
113
|
+
const cleaned = command.replace(/^[\\|_ ]+/, "");
|
|
114
|
+
// Extract binary name from full path or command
|
|
115
|
+
const parts = cleaned.split(/\s+/);
|
|
116
|
+
const binary = parts[0] || "";
|
|
117
|
+
// Remove path prefix
|
|
118
|
+
const name = binary.split("/").pop() || binary;
|
|
119
|
+
// Remove common suffixes
|
|
120
|
+
return name.replace(/:\s*$/, "");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if a process is running from an unusual path.
|
|
124
|
+
*/
|
|
125
|
+
function isUnusualPath(command) {
|
|
126
|
+
const cleaned = command.replace(/^[\\|_ ]+/, "").split(/\s+/)[0] || "";
|
|
127
|
+
if (!cleaned.startsWith("/"))
|
|
128
|
+
return false; // relative or just binary name — not unusual
|
|
129
|
+
const safePrefixes = ["/usr/", "/bin/", "/sbin/", "/opt/", "/lib/", "/snap/"];
|
|
130
|
+
return !safePrefixes.some((prefix) => cleaned.startsWith(prefix));
|
|
131
|
+
}
|
|
132
|
+
// ── Action implementations ─────────────────────────────────────────────────────
|
|
133
|
+
async function auditRunning(pid, filter, showAll) {
|
|
134
|
+
const sections = [];
|
|
135
|
+
const findings = [];
|
|
136
|
+
sections.push("🔍 Process Security Audit");
|
|
137
|
+
sections.push("=".repeat(50));
|
|
138
|
+
// Get process list
|
|
139
|
+
const psResult = await runCommand("ps", ["auxf"]);
|
|
140
|
+
if (psResult.exitCode !== 0) {
|
|
141
|
+
sections.push(`\n⚠️ Failed to get process list: ${psResult.stderr}`);
|
|
142
|
+
return { sections, findings };
|
|
143
|
+
}
|
|
144
|
+
let processes = parsePsOutput(psResult.stdout);
|
|
145
|
+
// Filter by PID if specified
|
|
146
|
+
if (pid !== undefined) {
|
|
147
|
+
processes = processes.filter((p) => p.pid === pid);
|
|
148
|
+
if (processes.length === 0) {
|
|
149
|
+
sections.push(`\n⚠️ No process found with PID ${pid}`);
|
|
150
|
+
return { sections, findings };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Filter by name pattern
|
|
154
|
+
if (filter) {
|
|
155
|
+
const pattern = new RegExp(filter, "i");
|
|
156
|
+
processes = processes.filter((p) => pattern.test(p.command));
|
|
157
|
+
}
|
|
158
|
+
sections.push(`\nTotal processes analyzed: ${processes.length}`);
|
|
159
|
+
// Check for processes running as root that shouldn't be
|
|
160
|
+
const rootProcesses = processes.filter((p) => p.user === "root");
|
|
161
|
+
const suspiciousRoot = rootProcesses.filter((p) => {
|
|
162
|
+
const name = getProcessName(p.command);
|
|
163
|
+
return !KNOWN_SAFE_ROOT_PROCESSES.has(name) && !name.startsWith("[");
|
|
164
|
+
});
|
|
165
|
+
if (suspiciousRoot.length > 0) {
|
|
166
|
+
sections.push("\n── Unusual Root Processes ──");
|
|
167
|
+
for (const p of suspiciousRoot) {
|
|
168
|
+
sections.push(` PID ${p.pid}: ${p.command}`);
|
|
169
|
+
findings.push({
|
|
170
|
+
severity: "MEDIUM",
|
|
171
|
+
category: "root_process",
|
|
172
|
+
message: `Process running as root: ${getProcessName(p.command)}`,
|
|
173
|
+
pid: p.pid,
|
|
174
|
+
process: p.command,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Check for high resource usage
|
|
179
|
+
const highCpu = processes.filter((p) => p.cpu > 90);
|
|
180
|
+
const highMem = processes.filter((p) => p.mem > 50);
|
|
181
|
+
if (highCpu.length > 0) {
|
|
182
|
+
sections.push("\n── High CPU Usage (>90%) ──");
|
|
183
|
+
for (const p of highCpu) {
|
|
184
|
+
sections.push(` PID ${p.pid} (${p.cpu}% CPU): ${p.command}`);
|
|
185
|
+
findings.push({
|
|
186
|
+
severity: "HIGH",
|
|
187
|
+
category: "high_resource",
|
|
188
|
+
message: `High CPU usage: ${p.cpu}%`,
|
|
189
|
+
pid: p.pid,
|
|
190
|
+
process: p.command,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (highMem.length > 0) {
|
|
195
|
+
sections.push("\n── High Memory Usage (>50%) ──");
|
|
196
|
+
for (const p of highMem) {
|
|
197
|
+
sections.push(` PID ${p.pid} (${p.mem}% MEM): ${p.command}`);
|
|
198
|
+
findings.push({
|
|
199
|
+
severity: "HIGH",
|
|
200
|
+
category: "high_resource",
|
|
201
|
+
message: `High memory usage: ${p.mem}%`,
|
|
202
|
+
pid: p.pid,
|
|
203
|
+
process: p.command,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Check for processes from unusual paths
|
|
208
|
+
const unusualPath = processes.filter((p) => isUnusualPath(p.command));
|
|
209
|
+
if (unusualPath.length > 0) {
|
|
210
|
+
sections.push("\n── Processes from Unusual Paths ──");
|
|
211
|
+
for (const p of unusualPath) {
|
|
212
|
+
sections.push(` PID ${p.pid}: ${p.command}`);
|
|
213
|
+
findings.push({
|
|
214
|
+
severity: "MEDIUM",
|
|
215
|
+
category: "unusual_path",
|
|
216
|
+
message: `Process running from unusual path`,
|
|
217
|
+
pid: p.pid,
|
|
218
|
+
process: p.command,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Check for deleted executables
|
|
223
|
+
const pidsToCheck = processes.slice(0, 50).map((p) => p.pid);
|
|
224
|
+
const deletedExe = [];
|
|
225
|
+
for (const checkPid of pidsToCheck) {
|
|
226
|
+
const exeResult = await runCommand("ls", ["-la", `/proc/${checkPid}/exe`]);
|
|
227
|
+
if (exeResult.exitCode === 0 && exeResult.stdout.includes("(deleted)")) {
|
|
228
|
+
const proc = processes.find((p) => p.pid === checkPid);
|
|
229
|
+
if (proc)
|
|
230
|
+
deletedExe.push(proc);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (deletedExe.length > 0) {
|
|
234
|
+
sections.push("\n── Processes with Deleted Executables ──");
|
|
235
|
+
for (const p of deletedExe) {
|
|
236
|
+
sections.push(` ⛔ PID ${p.pid}: ${p.command}`);
|
|
237
|
+
findings.push({
|
|
238
|
+
severity: "CRITICAL",
|
|
239
|
+
category: "deleted_exe",
|
|
240
|
+
message: `Process running with deleted executable`,
|
|
241
|
+
pid: p.pid,
|
|
242
|
+
process: p.command,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Summary
|
|
247
|
+
if (!showAll && findings.length === 0) {
|
|
248
|
+
sections.push("\n✅ No suspicious processes detected.");
|
|
249
|
+
}
|
|
250
|
+
else if (showAll) {
|
|
251
|
+
sections.push("\n── All Processes ──");
|
|
252
|
+
for (const p of processes.slice(0, 50)) {
|
|
253
|
+
sections.push(` ${p.user}\t${p.pid}\t${p.cpu}%\t${p.mem}%\t${p.command}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { sections, findings };
|
|
257
|
+
}
|
|
258
|
+
async function checkCapabilities(pid, filter) {
|
|
259
|
+
const sections = [];
|
|
260
|
+
const findings = [];
|
|
261
|
+
sections.push("🔐 Process Capabilities Check");
|
|
262
|
+
sections.push("=".repeat(50));
|
|
263
|
+
if (pid !== undefined) {
|
|
264
|
+
// Check specific PID capabilities
|
|
265
|
+
const capsResult = await runCommand("getpcaps", [String(pid)]);
|
|
266
|
+
if (capsResult.exitCode !== 0) {
|
|
267
|
+
sections.push(`\n⚠️ Failed to get capabilities for PID ${pid}: ${capsResult.stderr}`);
|
|
268
|
+
return { sections, findings };
|
|
269
|
+
}
|
|
270
|
+
sections.push(`\nCapabilities for PID ${pid}:`);
|
|
271
|
+
sections.push(` ${capsResult.stdout.trim()}`);
|
|
272
|
+
// Check for dangerous capabilities
|
|
273
|
+
const capsLower = capsResult.stdout.toLowerCase();
|
|
274
|
+
for (const cap of DANGEROUS_CAPABILITIES) {
|
|
275
|
+
if (capsLower.includes(cap)) {
|
|
276
|
+
findings.push({
|
|
277
|
+
severity: "HIGH",
|
|
278
|
+
category: "dangerous_capability",
|
|
279
|
+
message: `Process has dangerous capability: ${cap}`,
|
|
280
|
+
pid,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Scan processes for elevated capabilities
|
|
287
|
+
const psResult = await runCommand("ps", ["-eo", "pid"]);
|
|
288
|
+
if (psResult.exitCode !== 0) {
|
|
289
|
+
sections.push("\n⚠️ Failed to list processes");
|
|
290
|
+
return { sections, findings };
|
|
291
|
+
}
|
|
292
|
+
const pids = psResult.stdout.trim().split("\n")
|
|
293
|
+
.slice(1) // skip header
|
|
294
|
+
.map((l) => l.trim())
|
|
295
|
+
.filter((l) => l && /^\d+$/.test(l))
|
|
296
|
+
.slice(0, MAX_PROCESSES);
|
|
297
|
+
sections.push(`\nScanning ${pids.length} processes for elevated capabilities...`);
|
|
298
|
+
const elevatedProcesses = [];
|
|
299
|
+
for (const checkPid of pids) {
|
|
300
|
+
// Filter by name pattern if specified
|
|
301
|
+
if (filter) {
|
|
302
|
+
const cmdResult = await runCommand("cat", [`/proc/${checkPid}/comm`]);
|
|
303
|
+
if (cmdResult.exitCode !== 0)
|
|
304
|
+
continue;
|
|
305
|
+
const pattern = new RegExp(filter, "i");
|
|
306
|
+
if (!pattern.test(cmdResult.stdout.trim()))
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const statusResult = await runCommand("cat", [`/proc/${checkPid}/status`]);
|
|
310
|
+
if (statusResult.exitCode !== 0)
|
|
311
|
+
continue;
|
|
312
|
+
const capEffMatch = statusResult.stdout.match(/CapEff:\s*([0-9a-fA-F]+)/);
|
|
313
|
+
if (!capEffMatch)
|
|
314
|
+
continue;
|
|
315
|
+
const capHex = capEffMatch[1];
|
|
316
|
+
// Skip processes with no effective capabilities
|
|
317
|
+
if (capHex === "0000000000000000")
|
|
318
|
+
continue;
|
|
319
|
+
// Decode capabilities
|
|
320
|
+
const decodeResult = await runCommand("capsh", [`--decode=${capHex}`]);
|
|
321
|
+
const decoded = decodeResult.exitCode === 0 ? decodeResult.stdout.trim() : `hex:${capHex}`;
|
|
322
|
+
elevatedProcesses.push({ pid: checkPid, caps: capHex, decoded });
|
|
323
|
+
// Check for dangerous capabilities
|
|
324
|
+
const decodedLower = decoded.toLowerCase();
|
|
325
|
+
for (const cap of DANGEROUS_CAPABILITIES) {
|
|
326
|
+
if (decodedLower.includes(cap)) {
|
|
327
|
+
findings.push({
|
|
328
|
+
severity: "HIGH",
|
|
329
|
+
category: "dangerous_capability",
|
|
330
|
+
message: `PID ${checkPid} has dangerous capability: ${cap}`,
|
|
331
|
+
pid: parseInt(checkPid, 10),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (elevatedProcesses.length > 0) {
|
|
337
|
+
sections.push(`\n── Processes with Elevated Capabilities (${elevatedProcesses.length}) ──`);
|
|
338
|
+
for (const ep of elevatedProcesses) {
|
|
339
|
+
sections.push(` PID ${ep.pid}: ${ep.decoded}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
sections.push("\n✅ No processes with elevated capabilities found.");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return { sections, findings };
|
|
347
|
+
}
|
|
348
|
+
async function checkNamespaces(pid, _filter) {
|
|
349
|
+
const sections = [];
|
|
350
|
+
const findings = [];
|
|
351
|
+
sections.push("📦 Process Namespace Analysis");
|
|
352
|
+
sections.push("=".repeat(50));
|
|
353
|
+
if (pid !== undefined) {
|
|
354
|
+
// Show namespace details for specific PID
|
|
355
|
+
const nsResult = await runCommand("ls", ["-la", `/proc/${pid}/ns/`]);
|
|
356
|
+
if (nsResult.exitCode !== 0) {
|
|
357
|
+
sections.push(`\n⚠️ Cannot read namespaces for PID ${pid}: ${nsResult.stderr}`);
|
|
358
|
+
return { sections, findings };
|
|
359
|
+
}
|
|
360
|
+
sections.push(`\nNamespace details for PID ${pid}:`);
|
|
361
|
+
sections.push(nsResult.stdout.trim());
|
|
362
|
+
// Compare with init namespace (PID 1)
|
|
363
|
+
const initNsResult = await runCommand("ls", ["-la", "/proc/1/ns/"]);
|
|
364
|
+
if (initNsResult.exitCode === 0) {
|
|
365
|
+
sections.push("\n── Namespace Comparison with init (PID 1) ──");
|
|
366
|
+
const pidNsLines = nsResult.stdout.trim().split("\n");
|
|
367
|
+
const initNsLines = initNsResult.stdout.trim().split("\n");
|
|
368
|
+
const pidNsMap = new Map();
|
|
369
|
+
for (const line of pidNsLines) {
|
|
370
|
+
const match = line.match(/(\w+)\s*->\s*\w+:\[(\d+)\]/);
|
|
371
|
+
if (match)
|
|
372
|
+
pidNsMap.set(match[1], match[2]);
|
|
373
|
+
}
|
|
374
|
+
const initNsMap = new Map();
|
|
375
|
+
for (const line of initNsLines) {
|
|
376
|
+
const match = line.match(/(\w+)\s*->\s*\w+:\[(\d+)\]/);
|
|
377
|
+
if (match)
|
|
378
|
+
initNsMap.set(match[1], match[2]);
|
|
379
|
+
}
|
|
380
|
+
for (const [nsType, nsId] of pidNsMap) {
|
|
381
|
+
const initId = initNsMap.get(nsType);
|
|
382
|
+
const inRootNs = initId === nsId;
|
|
383
|
+
const icon = inRootNs ? "⚠️" : "✅";
|
|
384
|
+
sections.push(` ${icon} ${nsType}: ${inRootNs ? "in root namespace" : "isolated"} (${nsId})`);
|
|
385
|
+
if (inRootNs && !["user", "cgroup"].includes(nsType)) {
|
|
386
|
+
findings.push({
|
|
387
|
+
severity: "MEDIUM",
|
|
388
|
+
category: "namespace_not_isolated",
|
|
389
|
+
message: `PID ${pid} shares ${nsType} namespace with init`,
|
|
390
|
+
pid,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
// List all namespaces via lsns
|
|
398
|
+
const lsnsResult = await runCommand("lsns", []);
|
|
399
|
+
if (lsnsResult.exitCode !== 0) {
|
|
400
|
+
// Retry with sudo
|
|
401
|
+
const sudoLsnsResult = await runCommand("sudo", ["lsns"]);
|
|
402
|
+
if (sudoLsnsResult.exitCode !== 0) {
|
|
403
|
+
sections.push("\n⚠️ Cannot list namespaces (lsns not available or permission denied)");
|
|
404
|
+
return { sections, findings };
|
|
405
|
+
}
|
|
406
|
+
sections.push("\n── Active Namespaces ──");
|
|
407
|
+
sections.push(sudoLsnsResult.stdout.trim());
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
sections.push("\n── Active Namespaces ──");
|
|
411
|
+
sections.push(lsnsResult.stdout.trim());
|
|
412
|
+
}
|
|
413
|
+
// Parse lsns output for analysis
|
|
414
|
+
const lsnsOutput = lsnsResult.exitCode === 0 ? lsnsResult.stdout : "";
|
|
415
|
+
const nsLines = lsnsOutput.trim().split("\n").slice(1); // skip header
|
|
416
|
+
// Look for namespace sharing patterns
|
|
417
|
+
const nsCountMap = new Map();
|
|
418
|
+
for (const line of nsLines) {
|
|
419
|
+
const parts = line.trim().split(/\s+/);
|
|
420
|
+
if (parts.length >= 4) {
|
|
421
|
+
const nsType = parts[1];
|
|
422
|
+
const nprocs = parseInt(parts[2], 10);
|
|
423
|
+
if (nsType && !isNaN(nprocs)) {
|
|
424
|
+
nsCountMap.set(nsType, (nsCountMap.get(nsType) || 0) + nprocs);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (nsCountMap.size > 0) {
|
|
429
|
+
sections.push("\n── Namespace Summary ──");
|
|
430
|
+
for (const [nsType, count] of nsCountMap) {
|
|
431
|
+
sections.push(` ${nsType}: ${count} processes`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return { sections, findings };
|
|
436
|
+
}
|
|
437
|
+
async function detectAnomalies(pid, filter) {
|
|
438
|
+
const sections = [];
|
|
439
|
+
const findings = [];
|
|
440
|
+
sections.push("🚨 Process Anomaly Detection");
|
|
441
|
+
sections.push("=".repeat(50));
|
|
442
|
+
// Get process list for analysis
|
|
443
|
+
const psResult = await runCommand("ps", ["-eo", "pid,ppid,user,comm"]);
|
|
444
|
+
if (psResult.exitCode !== 0) {
|
|
445
|
+
sections.push("\n⚠️ Failed to get process list");
|
|
446
|
+
return { sections, findings };
|
|
447
|
+
}
|
|
448
|
+
const psLines = psResult.stdout.trim().split("\n").slice(1);
|
|
449
|
+
let processList = psLines.map((line) => {
|
|
450
|
+
const parts = line.trim().split(/\s+/);
|
|
451
|
+
return {
|
|
452
|
+
pid: parseInt(parts[0], 10),
|
|
453
|
+
ppid: parseInt(parts[1], 10),
|
|
454
|
+
user: parts[2] || "",
|
|
455
|
+
comm: parts[3] || "",
|
|
456
|
+
};
|
|
457
|
+
}).filter((p) => !isNaN(p.pid));
|
|
458
|
+
// Filter by PID or name
|
|
459
|
+
if (pid !== undefined) {
|
|
460
|
+
processList = processList.filter((p) => p.pid === pid);
|
|
461
|
+
}
|
|
462
|
+
if (filter) {
|
|
463
|
+
const pattern = new RegExp(filter, "i");
|
|
464
|
+
processList = processList.filter((p) => pattern.test(p.comm));
|
|
465
|
+
}
|
|
466
|
+
// Limit to prevent overwhelming output
|
|
467
|
+
processList = processList.slice(0, MAX_PROCESSES);
|
|
468
|
+
// 1. Check for deleted binaries
|
|
469
|
+
sections.push("\n── Deleted Binary Check ──");
|
|
470
|
+
let deletedCount = 0;
|
|
471
|
+
for (const proc of processList) {
|
|
472
|
+
const exeResult = await runCommand("ls", ["-la", `/proc/${proc.pid}/exe`]);
|
|
473
|
+
if (exeResult.exitCode === 0 && exeResult.stdout.includes("(deleted)")) {
|
|
474
|
+
deletedCount++;
|
|
475
|
+
sections.push(` ⛔ PID ${proc.pid} (${proc.comm}): executable deleted`);
|
|
476
|
+
findings.push({
|
|
477
|
+
severity: "CRITICAL",
|
|
478
|
+
category: "deleted_binary",
|
|
479
|
+
message: `Process ${proc.comm} running with deleted binary`,
|
|
480
|
+
pid: proc.pid,
|
|
481
|
+
process: proc.comm,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (deletedCount === 0)
|
|
486
|
+
sections.push(" ✅ No processes with deleted binaries");
|
|
487
|
+
// 2. Cross-reference network connections with expected services
|
|
488
|
+
sections.push("\n── Unexpected Network Connections ──");
|
|
489
|
+
const ssResult = await runCommand("ss", ["-tlnp"]);
|
|
490
|
+
if (ssResult.exitCode === 0) {
|
|
491
|
+
const ssLines = ssResult.stdout.trim().split("\n").slice(1);
|
|
492
|
+
for (const line of ssLines) {
|
|
493
|
+
sections.push(` ${line.trim()}`);
|
|
494
|
+
}
|
|
495
|
+
if (ssLines.length === 0) {
|
|
496
|
+
sections.push(" ✅ No listening TCP connections");
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
sections.push(" ⚠️ Could not check network connections");
|
|
501
|
+
}
|
|
502
|
+
// 3. Check for shell spawning from non-shell parents
|
|
503
|
+
sections.push("\n── Shell Spawning Analysis ──");
|
|
504
|
+
const shellNames = new Set(["sh", "bash", "zsh", "dash", "fish", "csh", "ksh"]);
|
|
505
|
+
const shellProcesses = processList.filter((p) => shellNames.has(p.comm));
|
|
506
|
+
let suspiciousShellCount = 0;
|
|
507
|
+
for (const shell of shellProcesses) {
|
|
508
|
+
const parent = processList.find((p) => p.pid === shell.ppid);
|
|
509
|
+
if (parent && !shellNames.has(parent.comm) && parent.comm !== "login" &&
|
|
510
|
+
parent.comm !== "sshd" && parent.comm !== "su" && parent.comm !== "sudo" &&
|
|
511
|
+
parent.comm !== "screen" && parent.comm !== "tmux" && parent.comm !== "script" &&
|
|
512
|
+
parent.comm !== "getty" && parent.comm !== "agetty" && parent.comm !== "systemd") {
|
|
513
|
+
suspiciousShellCount++;
|
|
514
|
+
sections.push(` ⚠️ PID ${shell.pid} (${shell.comm}) spawned by ${parent.comm} (PID ${parent.pid})`);
|
|
515
|
+
findings.push({
|
|
516
|
+
severity: "HIGH",
|
|
517
|
+
category: "suspicious_shell",
|
|
518
|
+
message: `Shell ${shell.comm} spawned by non-shell parent ${parent.comm}`,
|
|
519
|
+
pid: shell.pid,
|
|
520
|
+
process: shell.comm,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (suspiciousShellCount === 0)
|
|
525
|
+
sections.push(" ✅ No suspicious shell spawning detected");
|
|
526
|
+
// 4. Check for open file descriptors to sensitive files
|
|
527
|
+
sections.push("\n── Sensitive File Access ──");
|
|
528
|
+
const sensitiveFiles = ["/etc/shadow", "/etc/passwd", ".ssh/id_rsa", ".ssh/id_ed25519", "private.key"];
|
|
529
|
+
const checkProcs = processList.slice(0, 20); // limit deep inspection
|
|
530
|
+
let sensitiveAccessCount = 0;
|
|
531
|
+
for (const proc of checkProcs) {
|
|
532
|
+
const fdResult = await runCommand("ls", ["-la", `/proc/${proc.pid}/fd/`]);
|
|
533
|
+
if (fdResult.exitCode !== 0)
|
|
534
|
+
continue;
|
|
535
|
+
for (const sf of sensitiveFiles) {
|
|
536
|
+
if (fdResult.stdout.includes(sf)) {
|
|
537
|
+
sensitiveAccessCount++;
|
|
538
|
+
sections.push(` ⚠️ PID ${proc.pid} (${proc.comm}) has open fd to ${sf}`);
|
|
539
|
+
findings.push({
|
|
540
|
+
severity: "HIGH",
|
|
541
|
+
category: "sensitive_file_access",
|
|
542
|
+
message: `Process ${proc.comm} accessing sensitive file: ${sf}`,
|
|
543
|
+
pid: proc.pid,
|
|
544
|
+
process: proc.comm,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (sensitiveAccessCount === 0)
|
|
550
|
+
sections.push(" ✅ No suspicious sensitive file access");
|
|
551
|
+
// 5. Check for suspicious environment variables
|
|
552
|
+
sections.push("\n── Suspicious Environment Variables ──");
|
|
553
|
+
let suspiciousEnvCount = 0;
|
|
554
|
+
for (const proc of checkProcs) {
|
|
555
|
+
const envResult = await runCommand("cat", [`/proc/${proc.pid}/environ`]);
|
|
556
|
+
if (envResult.exitCode !== 0)
|
|
557
|
+
continue;
|
|
558
|
+
const envStr = envResult.stdout;
|
|
559
|
+
// Check for base64-encoded data, reverse shell indicators
|
|
560
|
+
if (envStr.includes("bash -i") || envStr.includes("/dev/tcp") ||
|
|
561
|
+
envStr.includes("nc -e") || envStr.includes("mkfifo") ||
|
|
562
|
+
envStr.includes("PAYLOAD") || envStr.includes("SHELLCODE")) {
|
|
563
|
+
suspiciousEnvCount++;
|
|
564
|
+
sections.push(` ⛔ PID ${proc.pid} (${proc.comm}): suspicious environment variables detected`);
|
|
565
|
+
findings.push({
|
|
566
|
+
severity: "CRITICAL",
|
|
567
|
+
category: "suspicious_env",
|
|
568
|
+
message: `Process ${proc.comm} has suspicious environment variables (possible reverse shell/payload)`,
|
|
569
|
+
pid: proc.pid,
|
|
570
|
+
process: proc.comm,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (suspiciousEnvCount === 0)
|
|
575
|
+
sections.push(" ✅ No suspicious environment variables detected");
|
|
576
|
+
return { sections, findings };
|
|
577
|
+
}
|
|
578
|
+
async function cgroupAudit(pid, _filter) {
|
|
579
|
+
const sections = [];
|
|
580
|
+
const findings = [];
|
|
581
|
+
sections.push("📊 Cgroup Resource Audit");
|
|
582
|
+
sections.push("=".repeat(50));
|
|
583
|
+
if (pid !== undefined) {
|
|
584
|
+
// Inspect specific process cgroup membership
|
|
585
|
+
const cgroupResult = await runCommand("cat", [`/proc/${pid}/cgroup`]);
|
|
586
|
+
if (cgroupResult.exitCode !== 0) {
|
|
587
|
+
sections.push(`\n⚠️ Cannot read cgroup for PID ${pid}: ${cgroupResult.stderr}`);
|
|
588
|
+
return { sections, findings };
|
|
589
|
+
}
|
|
590
|
+
sections.push(`\nCgroup membership for PID ${pid}:`);
|
|
591
|
+
sections.push(cgroupResult.stdout.trim());
|
|
592
|
+
// Check cgroup version
|
|
593
|
+
const cgroupLines = cgroupResult.stdout.trim().split("\n");
|
|
594
|
+
const isCgroupV2 = cgroupLines.some((l) => l.startsWith("0::"));
|
|
595
|
+
sections.push(`\n Cgroup version: ${isCgroupV2 ? "v2 (unified)" : "v1 (legacy)"}`);
|
|
596
|
+
// Check if process has resource limits
|
|
597
|
+
if (isCgroupV2) {
|
|
598
|
+
const cgroupPath = cgroupLines.find((l) => l.startsWith("0::"))?.split("::")[1] || "";
|
|
599
|
+
if (cgroupPath) {
|
|
600
|
+
const memMaxResult = await runCommand("cat", [`/sys/fs/cgroup${cgroupPath}/memory.max`]);
|
|
601
|
+
if (memMaxResult.exitCode === 0) {
|
|
602
|
+
const memMax = memMaxResult.stdout.trim();
|
|
603
|
+
sections.push(` Memory limit: ${memMax === "max" ? "unlimited ⚠️" : memMax}`);
|
|
604
|
+
if (memMax === "max") {
|
|
605
|
+
findings.push({
|
|
606
|
+
severity: "LOW",
|
|
607
|
+
category: "no_memory_limit",
|
|
608
|
+
message: `PID ${pid} has no memory limit`,
|
|
609
|
+
pid,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const cpuMaxResult = await runCommand("cat", [`/sys/fs/cgroup${cgroupPath}/cpu.max`]);
|
|
614
|
+
if (cpuMaxResult.exitCode === 0) {
|
|
615
|
+
const cpuMax = cpuMaxResult.stdout.trim();
|
|
616
|
+
sections.push(` CPU limit: ${cpuMax === "max 100000" ? "unlimited ⚠️" : cpuMax}`);
|
|
617
|
+
if (cpuMax.startsWith("max")) {
|
|
618
|
+
findings.push({
|
|
619
|
+
severity: "LOW",
|
|
620
|
+
category: "no_cpu_limit",
|
|
621
|
+
message: `PID ${pid} has no CPU limit`,
|
|
622
|
+
pid,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
// System-wide cgroup overview
|
|
631
|
+
const cgroupsResult = await runCommand("cat", ["/proc/cgroups"]);
|
|
632
|
+
if (cgroupsResult.exitCode === 0) {
|
|
633
|
+
sections.push("\n── Available Cgroup Controllers ──");
|
|
634
|
+
sections.push(cgroupsResult.stdout.trim());
|
|
635
|
+
}
|
|
636
|
+
// Check cgroup hierarchy
|
|
637
|
+
const cgHierarchy = await runCommand("systemd-cgls", ["--no-pager"]);
|
|
638
|
+
if (cgHierarchy.exitCode === 0) {
|
|
639
|
+
// Limit output length
|
|
640
|
+
const lines = cgHierarchy.stdout.trim().split("\n");
|
|
641
|
+
sections.push("\n── Cgroup Hierarchy (truncated) ──");
|
|
642
|
+
sections.push(lines.slice(0, 50).join("\n"));
|
|
643
|
+
if (lines.length > 50)
|
|
644
|
+
sections.push(` ... (${lines.length - 50} more lines)`);
|
|
645
|
+
}
|
|
646
|
+
// Resource usage overview
|
|
647
|
+
const cgtopResult = await runCommand("systemd-cgtop", ["-b", "-n", "1"]);
|
|
648
|
+
if (cgtopResult.exitCode === 0) {
|
|
649
|
+
const lines = cgtopResult.stdout.trim().split("\n");
|
|
650
|
+
sections.push("\n── Cgroup Resource Usage ──");
|
|
651
|
+
sections.push(lines.slice(0, 30).join("\n"));
|
|
652
|
+
}
|
|
653
|
+
// Check for cgroup v1 vs v2
|
|
654
|
+
const mountResult = await runCommand("cat", ["/proc/self/cgroup"]);
|
|
655
|
+
if (mountResult.exitCode === 0) {
|
|
656
|
+
const isCgroupV2 = mountResult.stdout.includes("0::");
|
|
657
|
+
sections.push(`\n Cgroup version: ${isCgroupV2 ? "v2 (unified)" : "v1 (legacy)"}`);
|
|
658
|
+
if (!isCgroupV2) {
|
|
659
|
+
findings.push({
|
|
660
|
+
severity: "INFO",
|
|
661
|
+
category: "cgroup_v1",
|
|
662
|
+
message: "System using cgroup v1 — consider upgrading to v2 for better security",
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return { sections, findings };
|
|
668
|
+
}
|
|
669
|
+
// ── Format helpers ─────────────────────────────────────────────────────────────
|
|
670
|
+
function formatFindings(findings) {
|
|
671
|
+
if (findings.length === 0)
|
|
672
|
+
return "\n✅ No security findings.";
|
|
673
|
+
const lines = ["\n── Security Findings Summary ──"];
|
|
674
|
+
const bySeverity = {};
|
|
675
|
+
for (const f of findings) {
|
|
676
|
+
if (!bySeverity[f.severity])
|
|
677
|
+
bySeverity[f.severity] = [];
|
|
678
|
+
bySeverity[f.severity].push(f);
|
|
679
|
+
}
|
|
680
|
+
const severityOrder = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"];
|
|
681
|
+
const icons = {
|
|
682
|
+
CRITICAL: "⛔",
|
|
683
|
+
HIGH: "🔴",
|
|
684
|
+
MEDIUM: "🟠",
|
|
685
|
+
LOW: "🟡",
|
|
686
|
+
INFO: "ℹ️",
|
|
687
|
+
};
|
|
688
|
+
for (const sev of severityOrder) {
|
|
689
|
+
const items = bySeverity[sev];
|
|
690
|
+
if (!items || items.length === 0)
|
|
691
|
+
continue;
|
|
692
|
+
lines.push(`\n ${icons[sev]} ${sev} (${items.length}):`);
|
|
693
|
+
for (const f of items) {
|
|
694
|
+
const pidStr = f.pid ? ` [PID ${f.pid}]` : "";
|
|
695
|
+
lines.push(` - ${f.message}${pidStr}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return lines.join("\n");
|
|
699
|
+
}
|
|
700
|
+
function formatAsJson(action, findings, rawSections) {
|
|
701
|
+
return JSON.stringify({
|
|
702
|
+
action,
|
|
703
|
+
timestamp: new Date().toISOString(),
|
|
704
|
+
findingsCount: findings.length,
|
|
705
|
+
findings: findings.map((f) => ({
|
|
706
|
+
severity: f.severity,
|
|
707
|
+
category: f.category,
|
|
708
|
+
message: f.message,
|
|
709
|
+
pid: f.pid || null,
|
|
710
|
+
process: f.process || null,
|
|
711
|
+
})),
|
|
712
|
+
rawOutput: rawSections.join("\n"),
|
|
713
|
+
}, null, 2);
|
|
714
|
+
}
|
|
715
|
+
// ── Registration entry point ───────────────────────────────────────────────
|
|
716
|
+
export function registerProcessSecurityTools(server) {
|
|
717
|
+
server.tool("process_security", "Process security analysis: audit running processes, check capabilities, inspect namespaces, detect anomalies, and audit cgroup resource limits.", {
|
|
718
|
+
action: z
|
|
719
|
+
.enum(["audit_running", "check_capabilities", "check_namespaces", "detect_anomalies", "cgroup_audit"])
|
|
720
|
+
.describe("Action: audit_running=audit processes, check_capabilities=inspect Linux capabilities, check_namespaces=namespace isolation, detect_anomalies=comprehensive anomaly detection, cgroup_audit=resource limit audit"),
|
|
721
|
+
pid: z
|
|
722
|
+
.number()
|
|
723
|
+
.optional()
|
|
724
|
+
.describe("Specific process ID to inspect"),
|
|
725
|
+
filter: z
|
|
726
|
+
.string()
|
|
727
|
+
.optional()
|
|
728
|
+
.describe("Filter processes by name pattern (regex)"),
|
|
729
|
+
show_all: z
|
|
730
|
+
.boolean()
|
|
731
|
+
.optional()
|
|
732
|
+
.default(false)
|
|
733
|
+
.describe("Show all processes or only suspicious ones"),
|
|
734
|
+
output_format: z
|
|
735
|
+
.enum(["text", "json"])
|
|
736
|
+
.optional()
|
|
737
|
+
.default("text")
|
|
738
|
+
.describe("Output format: text or json"),
|
|
739
|
+
}, async (params) => {
|
|
740
|
+
const { action, pid, filter, show_all, output_format } = params;
|
|
741
|
+
try {
|
|
742
|
+
let result;
|
|
743
|
+
switch (action) {
|
|
744
|
+
case "audit_running":
|
|
745
|
+
result = await auditRunning(pid, filter, show_all ?? false);
|
|
746
|
+
break;
|
|
747
|
+
case "check_capabilities":
|
|
748
|
+
result = await checkCapabilities(pid, filter);
|
|
749
|
+
break;
|
|
750
|
+
case "check_namespaces":
|
|
751
|
+
result = await checkNamespaces(pid, filter);
|
|
752
|
+
break;
|
|
753
|
+
case "detect_anomalies":
|
|
754
|
+
result = await detectAnomalies(pid, filter);
|
|
755
|
+
break;
|
|
756
|
+
case "cgroup_audit":
|
|
757
|
+
result = await cgroupAudit(pid, filter);
|
|
758
|
+
break;
|
|
759
|
+
default:
|
|
760
|
+
return {
|
|
761
|
+
content: [createErrorContent(`Unknown action: ${action}`)],
|
|
762
|
+
isError: true,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
// Append findings summary
|
|
766
|
+
result.sections.push(formatFindings(result.findings));
|
|
767
|
+
if (output_format === "json") {
|
|
768
|
+
return {
|
|
769
|
+
content: [createTextContent(formatAsJson(action, result.findings, result.sections))],
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
content: [createTextContent(result.sections.join("\n"))],
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
catch (err) {
|
|
777
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
778
|
+
return {
|
|
779
|
+
content: [createErrorContent(`Process security check failed: ${msg}`)],
|
|
780
|
+
isError: true,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
}
|