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.
Files changed (186) hide show
  1. package/CHANGELOG.md +471 -0
  2. package/LICENSE +21 -0
  3. package/README.md +242 -0
  4. package/build/core/auto-installer.d.ts +102 -0
  5. package/build/core/auto-installer.d.ts.map +1 -0
  6. package/build/core/auto-installer.js +833 -0
  7. package/build/core/backup-manager.d.ts +63 -0
  8. package/build/core/backup-manager.d.ts.map +1 -0
  9. package/build/core/backup-manager.js +189 -0
  10. package/build/core/changelog.d.ts +75 -0
  11. package/build/core/changelog.d.ts.map +1 -0
  12. package/build/core/changelog.js +123 -0
  13. package/build/core/command-allowlist.d.ts +129 -0
  14. package/build/core/command-allowlist.d.ts.map +1 -0
  15. package/build/core/command-allowlist.js +849 -0
  16. package/build/core/config.d.ts +79 -0
  17. package/build/core/config.d.ts.map +1 -0
  18. package/build/core/config.js +193 -0
  19. package/build/core/dependency-validator.d.ts +106 -0
  20. package/build/core/dependency-validator.d.ts.map +1 -0
  21. package/build/core/dependency-validator.js +405 -0
  22. package/build/core/distro-adapter.d.ts +177 -0
  23. package/build/core/distro-adapter.d.ts.map +1 -0
  24. package/build/core/distro-adapter.js +481 -0
  25. package/build/core/distro.d.ts +68 -0
  26. package/build/core/distro.d.ts.map +1 -0
  27. package/build/core/distro.js +457 -0
  28. package/build/core/encrypted-state.d.ts +76 -0
  29. package/build/core/encrypted-state.d.ts.map +1 -0
  30. package/build/core/encrypted-state.js +209 -0
  31. package/build/core/executor.d.ts +56 -0
  32. package/build/core/executor.d.ts.map +1 -0
  33. package/build/core/executor.js +350 -0
  34. package/build/core/installer.d.ts +92 -0
  35. package/build/core/installer.d.ts.map +1 -0
  36. package/build/core/installer.js +1072 -0
  37. package/build/core/logger.d.ts +102 -0
  38. package/build/core/logger.d.ts.map +1 -0
  39. package/build/core/logger.js +132 -0
  40. package/build/core/parsers.d.ts +151 -0
  41. package/build/core/parsers.d.ts.map +1 -0
  42. package/build/core/parsers.js +479 -0
  43. package/build/core/policy-engine.d.ts +170 -0
  44. package/build/core/policy-engine.d.ts.map +1 -0
  45. package/build/core/policy-engine.js +656 -0
  46. package/build/core/preflight.d.ts +157 -0
  47. package/build/core/preflight.d.ts.map +1 -0
  48. package/build/core/preflight.js +638 -0
  49. package/build/core/privilege-manager.d.ts +108 -0
  50. package/build/core/privilege-manager.d.ts.map +1 -0
  51. package/build/core/privilege-manager.js +363 -0
  52. package/build/core/rate-limiter.d.ts +67 -0
  53. package/build/core/rate-limiter.d.ts.map +1 -0
  54. package/build/core/rate-limiter.js +129 -0
  55. package/build/core/rollback.d.ts +73 -0
  56. package/build/core/rollback.d.ts.map +1 -0
  57. package/build/core/rollback.js +278 -0
  58. package/build/core/safeguards.d.ts +58 -0
  59. package/build/core/safeguards.d.ts.map +1 -0
  60. package/build/core/safeguards.js +448 -0
  61. package/build/core/sanitizer.d.ts +118 -0
  62. package/build/core/sanitizer.d.ts.map +1 -0
  63. package/build/core/sanitizer.js +459 -0
  64. package/build/core/secure-fs.d.ts +67 -0
  65. package/build/core/secure-fs.d.ts.map +1 -0
  66. package/build/core/secure-fs.js +143 -0
  67. package/build/core/spawn-safe.d.ts +55 -0
  68. package/build/core/spawn-safe.d.ts.map +1 -0
  69. package/build/core/spawn-safe.js +146 -0
  70. package/build/core/sudo-guard.d.ts +145 -0
  71. package/build/core/sudo-guard.d.ts.map +1 -0
  72. package/build/core/sudo-guard.js +349 -0
  73. package/build/core/sudo-session.d.ts +100 -0
  74. package/build/core/sudo-session.d.ts.map +1 -0
  75. package/build/core/sudo-session.js +319 -0
  76. package/build/core/tool-dependencies.d.ts +61 -0
  77. package/build/core/tool-dependencies.d.ts.map +1 -0
  78. package/build/core/tool-dependencies.js +571 -0
  79. package/build/core/tool-registry.d.ts +111 -0
  80. package/build/core/tool-registry.d.ts.map +1 -0
  81. package/build/core/tool-registry.js +656 -0
  82. package/build/core/tool-wrapper.d.ts +73 -0
  83. package/build/core/tool-wrapper.d.ts.map +1 -0
  84. package/build/core/tool-wrapper.js +296 -0
  85. package/build/index.d.ts +3 -0
  86. package/build/index.d.ts.map +1 -0
  87. package/build/index.js +247 -0
  88. package/build/tools/access-control.d.ts +9 -0
  89. package/build/tools/access-control.d.ts.map +1 -0
  90. package/build/tools/access-control.js +1818 -0
  91. package/build/tools/api-security.d.ts +12 -0
  92. package/build/tools/api-security.d.ts.map +1 -0
  93. package/build/tools/api-security.js +901 -0
  94. package/build/tools/app-hardening.d.ts +11 -0
  95. package/build/tools/app-hardening.d.ts.map +1 -0
  96. package/build/tools/app-hardening.js +768 -0
  97. package/build/tools/backup.d.ts +8 -0
  98. package/build/tools/backup.d.ts.map +1 -0
  99. package/build/tools/backup.js +381 -0
  100. package/build/tools/cloud-security.d.ts +17 -0
  101. package/build/tools/cloud-security.d.ts.map +1 -0
  102. package/build/tools/cloud-security.js +739 -0
  103. package/build/tools/compliance.d.ts +10 -0
  104. package/build/tools/compliance.d.ts.map +1 -0
  105. package/build/tools/compliance.js +1225 -0
  106. package/build/tools/container-security.d.ts +14 -0
  107. package/build/tools/container-security.d.ts.map +1 -0
  108. package/build/tools/container-security.js +788 -0
  109. package/build/tools/deception.d.ts +13 -0
  110. package/build/tools/deception.d.ts.map +1 -0
  111. package/build/tools/deception.js +763 -0
  112. package/build/tools/dns-security.d.ts +93 -0
  113. package/build/tools/dns-security.d.ts.map +1 -0
  114. package/build/tools/dns-security.js +745 -0
  115. package/build/tools/drift-detection.d.ts +8 -0
  116. package/build/tools/drift-detection.d.ts.map +1 -0
  117. package/build/tools/drift-detection.js +326 -0
  118. package/build/tools/ebpf-security.d.ts +15 -0
  119. package/build/tools/ebpf-security.d.ts.map +1 -0
  120. package/build/tools/ebpf-security.js +294 -0
  121. package/build/tools/encryption.d.ts +9 -0
  122. package/build/tools/encryption.d.ts.map +1 -0
  123. package/build/tools/encryption.js +1667 -0
  124. package/build/tools/firewall.d.ts +9 -0
  125. package/build/tools/firewall.d.ts.map +1 -0
  126. package/build/tools/firewall.js +1398 -0
  127. package/build/tools/hardening.d.ts +10 -0
  128. package/build/tools/hardening.d.ts.map +1 -0
  129. package/build/tools/hardening.js +2654 -0
  130. package/build/tools/ids.d.ts +9 -0
  131. package/build/tools/ids.d.ts.map +1 -0
  132. package/build/tools/ids.js +624 -0
  133. package/build/tools/incident-response.d.ts +10 -0
  134. package/build/tools/incident-response.d.ts.map +1 -0
  135. package/build/tools/incident-response.js +1180 -0
  136. package/build/tools/logging.d.ts +12 -0
  137. package/build/tools/logging.d.ts.map +1 -0
  138. package/build/tools/logging.js +454 -0
  139. package/build/tools/malware.d.ts +10 -0
  140. package/build/tools/malware.d.ts.map +1 -0
  141. package/build/tools/malware.js +532 -0
  142. package/build/tools/meta.d.ts +11 -0
  143. package/build/tools/meta.d.ts.map +1 -0
  144. package/build/tools/meta.js +2278 -0
  145. package/build/tools/network-defense.d.ts +12 -0
  146. package/build/tools/network-defense.d.ts.map +1 -0
  147. package/build/tools/network-defense.js +760 -0
  148. package/build/tools/patch-management.d.ts +3 -0
  149. package/build/tools/patch-management.d.ts.map +1 -0
  150. package/build/tools/patch-management.js +708 -0
  151. package/build/tools/process-security.d.ts +12 -0
  152. package/build/tools/process-security.d.ts.map +1 -0
  153. package/build/tools/process-security.js +784 -0
  154. package/build/tools/reporting.d.ts +11 -0
  155. package/build/tools/reporting.d.ts.map +1 -0
  156. package/build/tools/reporting.js +559 -0
  157. package/build/tools/secrets.d.ts +9 -0
  158. package/build/tools/secrets.d.ts.map +1 -0
  159. package/build/tools/secrets.js +596 -0
  160. package/build/tools/siem-integration.d.ts +18 -0
  161. package/build/tools/siem-integration.d.ts.map +1 -0
  162. package/build/tools/siem-integration.js +754 -0
  163. package/build/tools/sudo-management.d.ts +18 -0
  164. package/build/tools/sudo-management.d.ts.map +1 -0
  165. package/build/tools/sudo-management.js +737 -0
  166. package/build/tools/supply-chain-security.d.ts +8 -0
  167. package/build/tools/supply-chain-security.d.ts.map +1 -0
  168. package/build/tools/supply-chain-security.js +256 -0
  169. package/build/tools/threat-intel.d.ts +22 -0
  170. package/build/tools/threat-intel.d.ts.map +1 -0
  171. package/build/tools/threat-intel.js +749 -0
  172. package/build/tools/vulnerability-management.d.ts +11 -0
  173. package/build/tools/vulnerability-management.d.ts.map +1 -0
  174. package/build/tools/vulnerability-management.js +667 -0
  175. package/build/tools/waf.d.ts +12 -0
  176. package/build/tools/waf.d.ts.map +1 -0
  177. package/build/tools/waf.js +843 -0
  178. package/build/tools/wireless-security.d.ts +19 -0
  179. package/build/tools/wireless-security.d.ts.map +1 -0
  180. package/build/tools/wireless-security.js +826 -0
  181. package/build/tools/zero-trust-network.d.ts +8 -0
  182. package/build/tools/zero-trust-network.d.ts.map +1 -0
  183. package/build/tools/zero-trust-network.js +367 -0
  184. package/docs/SAFEGUARDS.md +518 -0
  185. package/docs/TOOLS-REFERENCE.md +665 -0
  186. package/package.json +87 -0
@@ -0,0 +1,768 @@
1
+ /**
2
+ * Application Hardening tools for Kali Defense MCP Server.
3
+ *
4
+ * Detects running applications, assesses their security posture,
5
+ * and applies hardening measures while preserving functionality.
6
+ *
7
+ * Registers 1 tool: app_harden (actions: audit, recommend, firewall, systemd).
8
+ */
9
+ import { z } from "zod";
10
+ import { executeCommand } from "../core/executor.js";
11
+ import { getConfig } from "../core/config.js";
12
+ import { createTextContent, createErrorContent, } from "../core/parsers.js";
13
+ import { logChange, createChangeEntry } from "../core/changelog.js";
14
+ const APP_PROFILES = {
15
+ qbittorrent: {
16
+ name: "qBittorrent",
17
+ processNames: ["qbittorrent", "qbittorrent-nox"],
18
+ category: "torrent",
19
+ typicalPorts: [
20
+ { port: 8080, protocol: "tcp", purpose: "WebUI" },
21
+ { port: 6881, protocol: "tcp", purpose: "BitTorrent TCP" },
22
+ { port: 6881, protocol: "udp", purpose: "BitTorrent UDP/DHT" },
23
+ { port: 6771, protocol: "udp", purpose: "Local Peer Discovery" },
24
+ ],
25
+ requiredPorts: [
26
+ { port: 6881, protocol: "tcp", purpose: "BitTorrent incoming connections" },
27
+ { port: 6881, protocol: "udp", purpose: "BitTorrent DHT" },
28
+ ],
29
+ localhostOnlyPorts: [
30
+ { port: 8080, protocol: "tcp", purpose: "WebUI — restrict to localhost or LAN" },
31
+ ],
32
+ systemdHardening: {
33
+ ProtectSystem: "strict",
34
+ ProtectHome: "read-only",
35
+ PrivateTmp: "true",
36
+ NoNewPrivileges: "true",
37
+ ProtectKernelTunables: "true",
38
+ ProtectKernelModules: "true",
39
+ ProtectControlGroups: "true",
40
+ RestrictNamespaces: "true",
41
+ RestrictRealtime: "true",
42
+ MemoryDenyWriteExecute: "true",
43
+ },
44
+ writablePaths: ["/var/lib/qbittorrent", "/home/*/Downloads"],
45
+ readablePaths: ["/etc/qbittorrent"],
46
+ recommendations: [
47
+ "Bind WebUI to 127.0.0.1 or LAN IP only (not 0.0.0.0)",
48
+ "Enable WebUI authentication with a strong password",
49
+ "Enable HTTPS for WebUI if accessible over network",
50
+ "Restrict listening port to a single port",
51
+ "Disable UPnP/NAT-PMP to prevent automatic port forwarding",
52
+ "Disable DHT, PEX, and LPD if using private trackers only",
53
+ "Set connection limits (global max: 200, per-torrent: 50)",
54
+ "Enable protocol encryption (forced)",
55
+ "Run as a dedicated non-root user",
56
+ "Use a VPN or SOCKS5 proxy for torrent traffic",
57
+ "Create firewall rules to restrict torrent traffic to VPN interface",
58
+ ],
59
+ riskLevel: "high",
60
+ securityConcerns: [
61
+ "Exposes multiple ports to the internet for peer connections",
62
+ "DHT/PEX can leak IP address to peers",
63
+ "WebUI may be accessible from external networks if misconfigured",
64
+ "Local Peer Discovery broadcasts on LAN",
65
+ "UPnP can automatically open router ports",
66
+ "Downloaded files may contain malware",
67
+ ],
68
+ },
69
+ nginx: {
70
+ name: "Nginx",
71
+ processNames: ["nginx"],
72
+ category: "web-server",
73
+ typicalPorts: [
74
+ { port: 80, protocol: "tcp", purpose: "HTTP" },
75
+ { port: 443, protocol: "tcp", purpose: "HTTPS" },
76
+ ],
77
+ requiredPorts: [{ port: 443, protocol: "tcp", purpose: "HTTPS" }],
78
+ localhostOnlyPorts: [],
79
+ systemdHardening: {
80
+ ProtectSystem: "full",
81
+ PrivateTmp: "true",
82
+ NoNewPrivileges: "true",
83
+ ProtectKernelTunables: "true",
84
+ ProtectKernelModules: "true",
85
+ ProtectControlGroups: "true",
86
+ },
87
+ writablePaths: ["/var/log/nginx", "/var/cache/nginx", "/run/nginx"],
88
+ readablePaths: ["/etc/nginx", "/var/www"],
89
+ recommendations: [
90
+ "Disable HTTP (port 80) or redirect to HTTPS",
91
+ "Enable HSTS (Strict-Transport-Security header)",
92
+ "Disable server_tokens to hide version info",
93
+ "Set X-Content-Type-Options: nosniff",
94
+ "Set X-Frame-Options: DENY or SAMEORIGIN",
95
+ "Configure Content-Security-Policy header",
96
+ "Set client_max_body_size to a reasonable limit",
97
+ "Enable rate limiting for login/API endpoints",
98
+ "Use TLS 1.2+ only with strong cipher suites",
99
+ ],
100
+ riskLevel: "high",
101
+ securityConcerns: [
102
+ "Directly exposed to the internet",
103
+ "Misconfigured virtual hosts can leak internal services",
104
+ "Default configs may expose server version",
105
+ ],
106
+ },
107
+ sshd: {
108
+ name: "OpenSSH Server",
109
+ processNames: ["sshd"],
110
+ category: "remote-access",
111
+ typicalPorts: [{ port: 22, protocol: "tcp", purpose: "SSH" }],
112
+ requiredPorts: [{ port: 22, protocol: "tcp", purpose: "SSH" }],
113
+ localhostOnlyPorts: [],
114
+ systemdHardening: {
115
+ ProtectSystem: "strict",
116
+ PrivateTmp: "true",
117
+ NoNewPrivileges: "true",
118
+ ProtectKernelTunables: "true",
119
+ ProtectKernelModules: "true",
120
+ },
121
+ writablePaths: ["/var/log", "/run/sshd"],
122
+ readablePaths: ["/etc/ssh"],
123
+ recommendations: [
124
+ "Disable root login (PermitRootLogin no)",
125
+ "Use key-based authentication only (PasswordAuthentication no)",
126
+ "Set MaxAuthTries to 3",
127
+ "Set LoginGraceTime to 30",
128
+ "Disable X11Forwarding unless needed",
129
+ "Use AllowUsers/AllowGroups to restrict access",
130
+ "Enable fail2ban for SSH brute-force protection",
131
+ "Use Ed25519 or RSA-4096 host keys only",
132
+ ],
133
+ riskLevel: "critical",
134
+ securityConcerns: [
135
+ "Primary remote access vector — #1 brute-force target",
136
+ "Root login may be enabled by default",
137
+ "Password authentication vulnerable to brute-force",
138
+ ],
139
+ },
140
+ postgresql: {
141
+ name: "PostgreSQL",
142
+ processNames: ["postgres", "postgresql"],
143
+ category: "database",
144
+ typicalPorts: [{ port: 5432, protocol: "tcp", purpose: "PostgreSQL" }],
145
+ requiredPorts: [],
146
+ localhostOnlyPorts: [{ port: 5432, protocol: "tcp", purpose: "Database — bind to localhost" }],
147
+ systemdHardening: {
148
+ ProtectSystem: "full",
149
+ PrivateTmp: "true",
150
+ NoNewPrivileges: "true",
151
+ ProtectKernelTunables: "true",
152
+ },
153
+ writablePaths: ["/var/lib/postgresql", "/var/log/postgresql", "/run/postgresql"],
154
+ readablePaths: ["/etc/postgresql"],
155
+ recommendations: [
156
+ "Bind to 127.0.0.1 only (listen_addresses = 'localhost')",
157
+ "Use pg_hba.conf to restrict client authentication",
158
+ "Disable trust authentication — use scram-sha-256",
159
+ "Set strong password for postgres superuser",
160
+ "Enable SSL for remote connections",
161
+ "Restrict CREATE DATABASE and SUPERUSER privileges",
162
+ ],
163
+ riskLevel: "critical",
164
+ securityConcerns: [
165
+ "Contains sensitive application data",
166
+ "Default trust authentication allows unauthenticated local access",
167
+ "Superuser access grants full system control",
168
+ ],
169
+ },
170
+ mysql: {
171
+ name: "MySQL/MariaDB",
172
+ processNames: ["mysqld", "mariadbd", "mariadb"],
173
+ category: "database",
174
+ typicalPorts: [{ port: 3306, protocol: "tcp", purpose: "MySQL" }],
175
+ requiredPorts: [],
176
+ localhostOnlyPorts: [{ port: 3306, protocol: "tcp", purpose: "Database — bind to localhost" }],
177
+ systemdHardening: {
178
+ ProtectSystem: "full",
179
+ PrivateTmp: "true",
180
+ NoNewPrivileges: "true",
181
+ },
182
+ writablePaths: ["/var/lib/mysql", "/var/log/mysql", "/run/mysqld"],
183
+ readablePaths: ["/etc/mysql"],
184
+ recommendations: [
185
+ "Run mysql_secure_installation",
186
+ "Bind to 127.0.0.1 only",
187
+ "Remove anonymous users and test database",
188
+ "Disable remote root login",
189
+ "Use strong passwords for all accounts",
190
+ "Enable SSL for remote connections",
191
+ ],
192
+ riskLevel: "critical",
193
+ securityConcerns: [
194
+ "Contains sensitive application data",
195
+ "Default installation may have anonymous users",
196
+ "Remote root login may be enabled",
197
+ ],
198
+ },
199
+ redis: {
200
+ name: "Redis",
201
+ processNames: ["redis-server"],
202
+ category: "database",
203
+ typicalPorts: [{ port: 6379, protocol: "tcp", purpose: "Redis" }],
204
+ requiredPorts: [],
205
+ localhostOnlyPorts: [{ port: 6379, protocol: "tcp", purpose: "Redis — MUST be localhost only" }],
206
+ systemdHardening: {
207
+ ProtectSystem: "strict",
208
+ PrivateTmp: "true",
209
+ NoNewPrivileges: "true",
210
+ ProtectKernelTunables: "true",
211
+ MemoryDenyWriteExecute: "true",
212
+ },
213
+ writablePaths: ["/var/lib/redis", "/var/log/redis", "/run/redis"],
214
+ readablePaths: ["/etc/redis"],
215
+ recommendations: [
216
+ "Bind to 127.0.0.1 ONLY (never expose to network)",
217
+ "Set a strong requirepass password",
218
+ "Disable dangerous commands (FLUSHALL, CONFIG, DEBUG, EVAL)",
219
+ "Enable protected-mode",
220
+ "Set maxmemory limit",
221
+ "Run as dedicated redis user",
222
+ ],
223
+ riskLevel: "critical",
224
+ securityConcerns: [
225
+ "No authentication by default",
226
+ "Exposed Redis = full server compromise",
227
+ "EVAL command allows Lua code execution",
228
+ ],
229
+ },
230
+ cups: {
231
+ name: "CUPS Print Server",
232
+ processNames: ["cupsd"],
233
+ category: "other",
234
+ typicalPorts: [{ port: 631, protocol: "tcp", purpose: "CUPS WebUI/IPP" }],
235
+ requiredPorts: [],
236
+ localhostOnlyPorts: [{ port: 631, protocol: "tcp", purpose: "CUPS — localhost only" }],
237
+ systemdHardening: {
238
+ ProtectSystem: "full",
239
+ PrivateTmp: "true",
240
+ NoNewPrivileges: "true",
241
+ },
242
+ writablePaths: ["/var/spool/cups", "/var/log/cups", "/run/cups"],
243
+ readablePaths: ["/etc/cups"],
244
+ recommendations: [
245
+ "Disable if no printers are connected",
246
+ "Bind WebUI to 127.0.0.1 only",
247
+ "Require authentication for admin operations",
248
+ "Disable remote printer sharing unless needed",
249
+ "Disable Bonjour/mDNS printer advertising",
250
+ ],
251
+ riskLevel: "medium",
252
+ securityConcerns: [
253
+ "WebUI may be accessible from network",
254
+ "Historical CVEs in CUPS",
255
+ "Unnecessary attack surface if no printers used",
256
+ ],
257
+ },
258
+ avahi: {
259
+ name: "Avahi mDNS/DNS-SD",
260
+ processNames: ["avahi-daemon"],
261
+ category: "dns",
262
+ typicalPorts: [{ port: 5353, protocol: "udp", purpose: "mDNS" }],
263
+ requiredPorts: [],
264
+ localhostOnlyPorts: [],
265
+ systemdHardening: {
266
+ ProtectSystem: "strict",
267
+ PrivateTmp: "true",
268
+ NoNewPrivileges: "true",
269
+ ProtectKernelTunables: "true",
270
+ },
271
+ writablePaths: ["/run/avahi-daemon"],
272
+ readablePaths: ["/etc/avahi"],
273
+ recommendations: [
274
+ "Disable entirely if not using network printer/service discovery",
275
+ "If needed, restrict to specific interfaces only",
276
+ "Disable publishing of local services",
277
+ "Set disable-user-service-publishing=yes",
278
+ ],
279
+ riskLevel: "medium",
280
+ securityConcerns: [
281
+ "Broadcasts service information on the local network",
282
+ "Can be used for network reconnaissance",
283
+ "Unnecessary on servers and most workstations",
284
+ ],
285
+ },
286
+ mongodb: {
287
+ name: "MongoDB",
288
+ processNames: ["mongod", "mongos"],
289
+ category: "database",
290
+ typicalPorts: [{ port: 27017, protocol: "tcp", purpose: "MongoDB" }],
291
+ requiredPorts: [],
292
+ localhostOnlyPorts: [{ port: 27017, protocol: "tcp", purpose: "MongoDB — bind to localhost" }],
293
+ systemdHardening: {
294
+ ProtectSystem: "full",
295
+ PrivateTmp: "true",
296
+ NoNewPrivileges: "true",
297
+ },
298
+ writablePaths: ["/var/lib/mongodb", "/var/log/mongodb"],
299
+ readablePaths: ["/etc/mongod.conf"],
300
+ recommendations: [
301
+ "Enable authentication (security.authorization: enabled)",
302
+ "Bind to 127.0.0.1 only",
303
+ "Create application-specific users with minimal privileges",
304
+ "Enable TLS for connections",
305
+ "Disable JavaScript execution if not needed",
306
+ ],
307
+ riskLevel: "critical",
308
+ securityConcerns: [
309
+ "No authentication by default",
310
+ "Exposed MongoDB = full data breach",
311
+ "JavaScript execution enabled by default",
312
+ ],
313
+ },
314
+ exim: {
315
+ name: "Exim Mail Server",
316
+ processNames: ["exim4", "exim"],
317
+ category: "mail",
318
+ typicalPorts: [
319
+ { port: 25, protocol: "tcp", purpose: "SMTP" },
320
+ { port: 587, protocol: "tcp", purpose: "SMTP Submission" },
321
+ ],
322
+ requiredPorts: [],
323
+ localhostOnlyPorts: [{ port: 25, protocol: "tcp", purpose: "SMTP — localhost only" }],
324
+ systemdHardening: {
325
+ ProtectSystem: "full",
326
+ PrivateTmp: "true",
327
+ NoNewPrivileges: "true",
328
+ },
329
+ writablePaths: ["/var/spool/exim4", "/var/log/exim4"],
330
+ readablePaths: ["/etc/exim4"],
331
+ recommendations: [
332
+ "Disable if not sending mail (use nullmailer/msmtp instead)",
333
+ "Bind to 127.0.0.1 only for local delivery",
334
+ "Disable open relay",
335
+ "Enable TLS for SMTP connections",
336
+ ],
337
+ riskLevel: "high",
338
+ securityConcerns: [
339
+ "Open SMTP relay can be abused for spam",
340
+ "Historical RCE vulnerabilities in Exim",
341
+ "Port 25 is a common attack target",
342
+ ],
343
+ },
344
+ samba: {
345
+ name: "Samba File Sharing",
346
+ processNames: ["smbd", "nmbd"],
347
+ category: "file-sharing",
348
+ typicalPorts: [
349
+ { port: 139, protocol: "tcp", purpose: "NetBIOS Session" },
350
+ { port: 445, protocol: "tcp", purpose: "SMB Direct" },
351
+ ],
352
+ requiredPorts: [{ port: 445, protocol: "tcp", purpose: "SMB" }],
353
+ localhostOnlyPorts: [],
354
+ systemdHardening: {
355
+ ProtectSystem: "full",
356
+ PrivateTmp: "true",
357
+ NoNewPrivileges: "true",
358
+ },
359
+ writablePaths: ["/var/lib/samba", "/var/log/samba", "/run/samba"],
360
+ readablePaths: ["/etc/samba"],
361
+ recommendations: [
362
+ "Disable SMBv1 (min protocol = SMB2)",
363
+ "Restrict to LAN interfaces only",
364
+ "Require authentication (security = user)",
365
+ "Set valid users for each share",
366
+ "Disable guest access",
367
+ "Use firewall to restrict SMB to LAN only",
368
+ ],
369
+ riskLevel: "high",
370
+ securityConcerns: [
371
+ "SMBv1 has critical vulnerabilities (EternalBlue/WannaCry)",
372
+ "Guest access can expose files",
373
+ "Network-exposed file shares are high-value targets",
374
+ ],
375
+ },
376
+ };
377
+ async function detectRunningApps() {
378
+ const detected = [];
379
+ const psResult = await executeCommand({
380
+ command: "ps",
381
+ args: ["axo", "pid,user,comm"],
382
+ timeout: 10000,
383
+ });
384
+ if (psResult.exitCode !== 0)
385
+ return detected;
386
+ const processLines = psResult.stdout.trim().split("\n").slice(1);
387
+ const runningProcesses = [];
388
+ for (const line of processLines) {
389
+ const parts = line.trim().split(/\s+/);
390
+ if (parts.length >= 3) {
391
+ runningProcesses.push({ pid: parseInt(parts[0], 10), user: parts[1], comm: parts[2] });
392
+ }
393
+ }
394
+ const ssResult = await executeCommand({ command: "ss", args: ["-tulnp"], timeout: 10000 });
395
+ const ssLines = ssResult.exitCode === 0 ? ssResult.stdout.split("\n") : [];
396
+ const svcResult = await executeCommand({
397
+ command: "systemctl",
398
+ args: ["list-units", "--type=service", "--state=running", "--no-pager", "--no-legend"],
399
+ timeout: 10000,
400
+ });
401
+ const svcLines = svcResult.exitCode === 0 ? svcResult.stdout.split("\n") : [];
402
+ for (const [profileId, profile] of Object.entries(APP_PROFILES)) {
403
+ const matchingProcesses = runningProcesses.filter((p) => profile.processNames.some((name) => p.comm.toLowerCase() === name.toLowerCase()));
404
+ if (matchingProcesses.length > 0) {
405
+ const pids = matchingProcesses.map((p) => p.pid);
406
+ const user = matchingProcesses[0].user;
407
+ const listenPorts = [];
408
+ for (const line of ssLines) {
409
+ if (pids.some((pid) => line.includes(`pid=${pid}`))) {
410
+ const addrMatch = line.match(/\s(\S+):(\d+)\s/);
411
+ if (addrMatch) {
412
+ listenPorts.push({
413
+ port: parseInt(addrMatch[2], 10),
414
+ protocol: line.startsWith("tcp") ? "tcp" : "udp",
415
+ address: addrMatch[1],
416
+ });
417
+ }
418
+ }
419
+ }
420
+ let serviceUnit;
421
+ for (const procName of profile.processNames) {
422
+ for (const svcLine of svcLines) {
423
+ if (svcLine.toLowerCase().includes(procName.toLowerCase())) {
424
+ serviceUnit = svcLine.trim().split(/\s+/)[0];
425
+ break;
426
+ }
427
+ }
428
+ if (serviceUnit)
429
+ break;
430
+ }
431
+ detected.push({ profileId, profile, pids, user, listenPorts, serviceUnit });
432
+ }
433
+ }
434
+ return detected;
435
+ }
436
+ // ── Registration ─────────────────────────────────────────────────────────────
437
+ export function registerAppHardeningTools(server) {
438
+ server.tool("app_harden", "Application hardening: audit running apps, get recommendations, apply firewall rules, or apply systemd sandboxing.", {
439
+ action: z.enum(["audit", "recommend", "firewall", "systemd"]).describe("Action: audit=detect/audit apps, recommend=hardening guide, firewall=network rules, systemd=sandboxing"),
440
+ // recommend/firewall/systemd params
441
+ app_name: z.string().optional().describe("Application name (recommend/firewall/systemd). Available: " + Object.keys(APP_PROFILES).join(", ")),
442
+ // firewall params
443
+ lan_cidr: z.string().optional().default("192.168.0.0/16").describe("LAN CIDR for localhost-only ports (firewall action)"),
444
+ // systemd params
445
+ service_name: z.string().optional().describe("Override systemd service name (systemd action)"),
446
+ // shared
447
+ dry_run: z.boolean().optional().describe("Preview changes without applying"),
448
+ }, async (params) => {
449
+ const { action } = params;
450
+ switch (action) {
451
+ // ── audit ───────────────────────────────────────────────────
452
+ case "audit": {
453
+ try {
454
+ const sections = [];
455
+ sections.push("🔍 Application Security Audit");
456
+ sections.push("=".repeat(55));
457
+ const apps = await detectRunningApps();
458
+ if (apps.length === 0) {
459
+ sections.push("\nNo known applications detected.");
460
+ sections.push("Recognized apps: " + Object.values(APP_PROFILES).map((p) => p.name).join(", "));
461
+ return { content: [createTextContent(sections.join("\n"))] };
462
+ }
463
+ sections.push(`\nDetected ${apps.length} application(s):\n`);
464
+ const riskOrder = { critical: 0, high: 1, medium: 2, low: 3 };
465
+ apps.sort((a, b) => riskOrder[a.profile.riskLevel] - riskOrder[b.profile.riskLevel]);
466
+ let totalRisks = 0;
467
+ for (const app of apps) {
468
+ const riskIcon = app.profile.riskLevel === "critical" ? "⛔" :
469
+ app.profile.riskLevel === "high" ? "🔴" : app.profile.riskLevel === "medium" ? "🟡" : "🟢";
470
+ sections.push(`── ${riskIcon} ${app.profile.name} ──`);
471
+ sections.push(` Category: ${app.profile.category}`);
472
+ sections.push(` Risk Level: ${app.profile.riskLevel.toUpperCase()}`);
473
+ sections.push(` Running as: ${app.user}`);
474
+ sections.push(` PIDs: ${app.pids.join(", ")}`);
475
+ if (app.serviceUnit)
476
+ sections.push(` Service Unit: ${app.serviceUnit}`);
477
+ if (app.listenPorts.length > 0) {
478
+ sections.push(` Listening Ports:`);
479
+ for (const lp of app.listenPorts) {
480
+ const external = !lp.address.includes("127.0.0.1") && !lp.address.includes("::1");
481
+ sections.push(` ${lp.protocol}/${lp.port} on ${lp.address} [${external ? "⚠️ EXTERNAL" : "✅ localhost"}]`);
482
+ }
483
+ }
484
+ sections.push(` Security Concerns:`);
485
+ for (const concern of app.profile.securityConcerns) {
486
+ sections.push(` ⚠️ ${concern}`);
487
+ totalRisks++;
488
+ }
489
+ sections.push(` Top Recommendations:`);
490
+ for (const rec of app.profile.recommendations.slice(0, 3)) {
491
+ sections.push(` 💡 ${rec}`);
492
+ }
493
+ if (app.profile.recommendations.length > 3) {
494
+ sections.push(` ... +${app.profile.recommendations.length - 3} more (use action=recommend)`);
495
+ }
496
+ sections.push("");
497
+ }
498
+ sections.push("── Summary ──");
499
+ sections.push(` Applications: ${apps.length} | Concerns: ${totalRisks} | Critical/High: ${apps.filter((a) => ["critical", "high"].includes(a.profile.riskLevel)).length}`);
500
+ logChange(createChangeEntry({
501
+ tool: "app_harden",
502
+ action: "Application security audit",
503
+ target: "system",
504
+ after: `${apps.length} apps, ${totalRisks} concerns`,
505
+ dryRun: false,
506
+ success: true,
507
+ }));
508
+ return { content: [createTextContent(sections.join("\n"))] };
509
+ }
510
+ catch (err) {
511
+ return { content: [createErrorContent(err instanceof Error ? err.message : String(err))], isError: true };
512
+ }
513
+ }
514
+ // ── recommend ───────────────────────────────────────────────
515
+ case "recommend": {
516
+ const { app_name } = params;
517
+ try {
518
+ if (!app_name) {
519
+ return { content: [createErrorContent("app_name is required for recommend action")], isError: true };
520
+ }
521
+ const profileId = app_name.toLowerCase().replace(/[^a-z0-9]/g, "");
522
+ const profile = APP_PROFILES[profileId];
523
+ if (!profile) {
524
+ const available = Object.entries(APP_PROFILES).map(([k, v]) => `${k} (${v.name})`).join(", ");
525
+ return {
526
+ content: [createErrorContent(`Unknown application '${app_name}'. Available: ${available}`)],
527
+ isError: true,
528
+ };
529
+ }
530
+ const sections = [];
531
+ sections.push(`🛡️ Hardening Guide: ${profile.name}`);
532
+ sections.push("=".repeat(55));
533
+ sections.push(`Category: ${profile.category} | Risk: ${profile.riskLevel.toUpperCase()}`);
534
+ sections.push("\n── Security Concerns ──");
535
+ for (const concern of profile.securityConcerns) {
536
+ sections.push(` ⚠️ ${concern}`);
537
+ }
538
+ sections.push("\n── Network Hardening ──");
539
+ if (profile.requiredPorts.length > 0) {
540
+ sections.push(" Ports that MUST remain open (core functionality):");
541
+ for (const p of profile.requiredPorts) {
542
+ sections.push(` ✅ ${p.protocol}/${p.port} — ${p.purpose}`);
543
+ }
544
+ }
545
+ if (profile.localhostOnlyPorts.length > 0) {
546
+ sections.push(" Ports to restrict to localhost/LAN:");
547
+ for (const p of profile.localhostOnlyPorts) {
548
+ sections.push(` 🔒 ${p.protocol}/${p.port} — ${p.purpose}`);
549
+ }
550
+ }
551
+ sections.push(" Firewall strategy:");
552
+ sections.push(" 1. Allow required ports from any source");
553
+ sections.push(" 2. Restrict localhost-only ports to 127.0.0.1");
554
+ sections.push(" 3. Drop all other traffic to this application");
555
+ sections.push(` → Use app_harden action=firewall app_name=${profileId} to generate rules`);
556
+ sections.push("\n── Systemd Sandboxing ──");
557
+ sections.push(" Recommended directives for the service unit:");
558
+ for (const [key, value] of Object.entries(profile.systemdHardening)) {
559
+ sections.push(` ${key}=${value}`);
560
+ }
561
+ if (profile.writablePaths.length > 0) {
562
+ sections.push(` ReadWritePaths=${profile.writablePaths.join(" ")}`);
563
+ }
564
+ sections.push(` → Use app_harden action=systemd app_name=${profileId} to apply`);
565
+ sections.push("\n── Application-Level Recommendations ──");
566
+ for (let i = 0; i < profile.recommendations.length; i++) {
567
+ sections.push(` ${i + 1}. ${profile.recommendations[i]}`);
568
+ }
569
+ sections.push("\n── Filesystem Permissions ──");
570
+ sections.push(" Writable paths (required for operation):");
571
+ for (const p of profile.writablePaths) {
572
+ sections.push(` 📝 ${p}`);
573
+ }
574
+ sections.push(" Read-only paths:");
575
+ for (const p of profile.readablePaths) {
576
+ sections.push(` 📖 ${p}`);
577
+ }
578
+ return { content: [createTextContent(sections.join("\n"))] };
579
+ }
580
+ catch (err) {
581
+ return { content: [createErrorContent(err instanceof Error ? err.message : String(err))], isError: true };
582
+ }
583
+ }
584
+ // ── firewall ────────────────────────────────────────────────
585
+ case "firewall": {
586
+ const { app_name, lan_cidr, dry_run } = params;
587
+ try {
588
+ if (!app_name) {
589
+ return { content: [createErrorContent("app_name is required for firewall action")], isError: true };
590
+ }
591
+ const profileId = app_name.toLowerCase().replace(/[^a-z0-9]/g, "");
592
+ const profile = APP_PROFILES[profileId];
593
+ if (!profile) {
594
+ return {
595
+ content: [createErrorContent(`Unknown application '${app_name}'. Available: ${Object.keys(APP_PROFILES).join(", ")}`)],
596
+ isError: true,
597
+ };
598
+ }
599
+ const effectiveDryRun = dry_run ?? getConfig().dryRun;
600
+ const sections = [];
601
+ sections.push(`🔥 Firewall Rules for ${profile.name}`);
602
+ sections.push("=".repeat(55));
603
+ sections.push(`LAN CIDR: ${lan_cidr}`);
604
+ sections.push(effectiveDryRun ? "\n[DRY RUN] Rules that would be applied:\n" : "\nApplying rules:\n");
605
+ const rules = [];
606
+ for (const p of profile.requiredPorts) {
607
+ rules.push(`iptables -A INPUT -p ${p.protocol} --dport ${p.port} -j ACCEPT # ${p.purpose}`);
608
+ }
609
+ for (const p of profile.localhostOnlyPorts) {
610
+ rules.push(`iptables -A INPUT -p ${p.protocol} --dport ${p.port} -s 127.0.0.0/8 -j ACCEPT # ${p.purpose} (localhost)`);
611
+ rules.push(`iptables -A INPUT -p ${p.protocol} --dport ${p.port} -s ${lan_cidr} -j ACCEPT # ${p.purpose} (LAN)`);
612
+ rules.push(`iptables -A INPUT -p ${p.protocol} --dport ${p.port} -j DROP # ${p.purpose} (block external)`);
613
+ }
614
+ for (const rule of rules) {
615
+ sections.push(` ${rule}`);
616
+ }
617
+ if (!effectiveDryRun && rules.length > 0) {
618
+ let applied = 0;
619
+ let failed = 0;
620
+ for (const rule of rules) {
621
+ const parts = rule.split("#")[0].trim().split(/\s+/);
622
+ const result = await executeCommand({
623
+ command: "sudo",
624
+ args: parts,
625
+ timeout: 10000,
626
+ });
627
+ if (result.exitCode === 0)
628
+ applied++;
629
+ else
630
+ failed++;
631
+ }
632
+ sections.push(`\n✅ Applied ${applied} rules, ❌ ${failed} failed`);
633
+ }
634
+ sections.push("\n── Additional Recommendations ──");
635
+ sections.push(" • Consider using nftables for more granular control");
636
+ sections.push(" • Save rules with: sudo iptables-save > /etc/iptables/rules.v4");
637
+ sections.push(" • Install iptables-persistent for reboot survival");
638
+ logChange(createChangeEntry({
639
+ tool: "app_harden",
640
+ action: `Firewall rules for ${profile.name}`,
641
+ target: profileId,
642
+ after: `${rules.length} rules`,
643
+ dryRun: effectiveDryRun,
644
+ success: true,
645
+ }));
646
+ return { content: [createTextContent(sections.join("\n"))] };
647
+ }
648
+ catch (err) {
649
+ return { content: [createErrorContent(err instanceof Error ? err.message : String(err))], isError: true };
650
+ }
651
+ }
652
+ // ── systemd ─────────────────────────────────────────────────
653
+ case "systemd": {
654
+ const { app_name, service_name, dry_run } = params;
655
+ try {
656
+ if (!app_name) {
657
+ return { content: [createErrorContent("app_name is required for systemd action")], isError: true };
658
+ }
659
+ const profileId = app_name.toLowerCase().replace(/[^a-z0-9]/g, "");
660
+ const profile = APP_PROFILES[profileId];
661
+ if (!profile) {
662
+ return {
663
+ content: [createErrorContent(`Unknown application '${app_name}'. Available: ${Object.keys(APP_PROFILES).join(", ")}`)],
664
+ isError: true,
665
+ };
666
+ }
667
+ const effectiveDryRun = dry_run ?? getConfig().dryRun;
668
+ let svcName = service_name;
669
+ if (!svcName) {
670
+ const svcResult = await executeCommand({
671
+ command: "systemctl",
672
+ args: ["list-units", "--type=service", "--state=running", "--no-pager", "--no-legend"],
673
+ timeout: 10000,
674
+ });
675
+ if (svcResult.exitCode === 0) {
676
+ for (const procName of profile.processNames) {
677
+ for (const line of svcResult.stdout.split("\n")) {
678
+ if (line.toLowerCase().includes(procName.toLowerCase())) {
679
+ svcName = line.trim().split(/\s+/)[0];
680
+ break;
681
+ }
682
+ }
683
+ if (svcName)
684
+ break;
685
+ }
686
+ }
687
+ }
688
+ const sections = [];
689
+ sections.push(`🔒 Systemd Hardening: ${profile.name}`);
690
+ sections.push("=".repeat(55));
691
+ if (!svcName) {
692
+ sections.push(`\n⚠️ No running systemd service found for ${profile.name}.`);
693
+ sections.push("Provide service_name manually if the service uses a different name.");
694
+ sections.push("\nRecommended override content for when the service is configured:\n");
695
+ }
696
+ else {
697
+ sections.push(`Service: ${svcName}`);
698
+ }
699
+ const overrideLines = ["[Service]"];
700
+ for (const [key, value] of Object.entries(profile.systemdHardening)) {
701
+ overrideLines.push(`${key}=${value}`);
702
+ }
703
+ if (profile.writablePaths.length > 0) {
704
+ overrideLines.push(`ReadWritePaths=${profile.writablePaths.join(" ")}`);
705
+ }
706
+ sections.push(effectiveDryRun ? "\n[DRY RUN] Override that would be created:\n" : "\nApplying override:\n");
707
+ sections.push(" # /etc/systemd/system/" + (svcName ?? `${profileId}.service`) + ".d/hardening.conf");
708
+ for (const line of overrideLines) {
709
+ sections.push(` ${line}`);
710
+ }
711
+ if (!effectiveDryRun && svcName) {
712
+ const overrideDir = `/etc/systemd/system/${svcName}.d`;
713
+ const overridePath = `${overrideDir}/hardening.conf`;
714
+ const overrideContent = overrideLines.join("\n") + "\n";
715
+ await executeCommand({ command: "sudo", args: ["mkdir", "-p", overrideDir], timeout: 5000 });
716
+ const writeResult = await executeCommand({
717
+ command: "sudo",
718
+ args: ["tee", overridePath],
719
+ stdin: overrideContent,
720
+ timeout: 5000,
721
+ });
722
+ if (writeResult.exitCode === 0) {
723
+ await executeCommand({ command: "sudo", args: ["systemctl", "daemon-reload"], timeout: 10000 });
724
+ sections.push(`\n✅ Override written to ${overridePath}`);
725
+ sections.push("✅ systemd daemon reloaded");
726
+ sections.push(`\n⚠️ Restart the service to apply: sudo systemctl restart ${svcName}`);
727
+ }
728
+ else {
729
+ sections.push(`\n❌ Failed to write override: ${writeResult.stderr}`);
730
+ }
731
+ }
732
+ sections.push("\n── What These Directives Do ──");
733
+ const explanations = {
734
+ ProtectSystem: "Mounts /usr and /boot read-only (full) or entire filesystem (strict)",
735
+ ProtectHome: "Makes /home, /root, /run/user inaccessible or read-only",
736
+ PrivateTmp: "Creates a private /tmp namespace for this service",
737
+ NoNewPrivileges: "Prevents the service from gaining new privileges via setuid/setgid",
738
+ ProtectKernelTunables: "Makes /proc/sys, /sys read-only",
739
+ ProtectKernelModules: "Prevents loading/unloading kernel modules",
740
+ ProtectControlGroups: "Makes /sys/fs/cgroup read-only",
741
+ RestrictNamespaces: "Restricts creation of new namespaces",
742
+ RestrictRealtime: "Prevents acquiring realtime scheduling",
743
+ MemoryDenyWriteExecute: "Prevents creating writable+executable memory mappings",
744
+ };
745
+ for (const [key] of Object.entries(profile.systemdHardening)) {
746
+ if (explanations[key]) {
747
+ sections.push(` ${key}: ${explanations[key]}`);
748
+ }
749
+ }
750
+ logChange(createChangeEntry({
751
+ tool: "app_harden",
752
+ action: `Systemd hardening for ${profile.name}`,
753
+ target: svcName ?? profileId,
754
+ after: `${Object.keys(profile.systemdHardening).length} directives`,
755
+ dryRun: effectiveDryRun,
756
+ success: true,
757
+ }));
758
+ return { content: [createTextContent(sections.join("\n"))] };
759
+ }
760
+ catch (err) {
761
+ return { content: [createErrorContent(err instanceof Error ? err.message : String(err))], isError: true };
762
+ }
763
+ }
764
+ default:
765
+ return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
766
+ }
767
+ });
768
+ }