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,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Malware detection and quarantine tools for Kali Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 4 tools: malware_clamav (actions: scan, update),
|
|
5
|
+
* malware_yara_scan, malware_file_scan (actions: suspicious, webshell),
|
|
6
|
+
* malware_quarantine_manage.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { executeCommand } from "../core/executor.js";
|
|
11
|
+
import { getConfig, getToolTimeout } from "../core/config.js";
|
|
12
|
+
import { createTextContent, createErrorContent, parseClamavOutput, formatToolOutput, } from "../core/parsers.js";
|
|
13
|
+
import { logChange, createChangeEntry, } from "../core/changelog.js";
|
|
14
|
+
import { validateFilePath, validateYaraRule, sanitizeArgs, } from "../core/sanitizer.js";
|
|
15
|
+
// ── TOOL-006 remediation: quarantine file_id validation ────────────────────
|
|
16
|
+
/** Strict pattern for quarantine file IDs — simple identifiers only */
|
|
17
|
+
const FILE_ID_REGEX = /^[a-zA-Z0-9._-]+$/;
|
|
18
|
+
/**
|
|
19
|
+
* Validate a quarantine file_id to prevent path traversal.
|
|
20
|
+
* Rejects any id containing `..`, `/`, or `\`.
|
|
21
|
+
* After validation, resolves the full path and verifies it stays within the quarantine directory.
|
|
22
|
+
*/
|
|
23
|
+
function validateQuarantineFileId(fileId, quarantineDir) {
|
|
24
|
+
// Reject any file_id containing path separators or traversal sequences
|
|
25
|
+
if (fileId.includes("..") || fileId.includes("/") || fileId.includes("\\")) {
|
|
26
|
+
throw new Error(`Invalid file_id: path traversal sequences are not allowed`);
|
|
27
|
+
}
|
|
28
|
+
if (!FILE_ID_REGEX.test(fileId)) {
|
|
29
|
+
throw new Error(`Invalid file_id: '${fileId}'. Only [a-zA-Z0-9._-] characters are allowed.`);
|
|
30
|
+
}
|
|
31
|
+
// Resolve full path and verify it's within the quarantine directory
|
|
32
|
+
const resolvedPath = path.resolve(quarantineDir, fileId);
|
|
33
|
+
const resolvedBase = path.resolve(quarantineDir);
|
|
34
|
+
if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
|
|
35
|
+
throw new Error(`Invalid file_id: resolved path escapes quarantine directory`);
|
|
36
|
+
}
|
|
37
|
+
return fileId;
|
|
38
|
+
}
|
|
39
|
+
// ── Registration entry point ───────────────────────────────────────────────
|
|
40
|
+
export function registerMalwareTools(server) {
|
|
41
|
+
// ── 1. malware_clamav (merged: scan + update) ─────────────────────────
|
|
42
|
+
server.tool("malware_clamav", "ClamAV antivirus: scan files/directories or update virus definitions.", {
|
|
43
|
+
action: z.enum(["scan", "update"]).describe("Action: scan=scan files, update=update virus definitions"),
|
|
44
|
+
// scan params
|
|
45
|
+
path: z.string().optional().describe("File or directory path to scan (scan action)"),
|
|
46
|
+
recursive: z.boolean().optional().default(true).describe("Recursively scan directories (scan action)"),
|
|
47
|
+
remove_infected: z.boolean().optional().default(false).describe("Remove infected files (scan action)"),
|
|
48
|
+
move_to_quarantine: z.boolean().optional().default(false).describe("Move infected files to quarantine (scan action)"),
|
|
49
|
+
max_filesize: z.string().optional().default("100M").describe("Maximum file size to scan (scan action)"),
|
|
50
|
+
// shared
|
|
51
|
+
dry_run: z.boolean().optional().describe("Preview without executing (defaults to KALI_DEFENSE_DRY_RUN env var)"),
|
|
52
|
+
}, async (params) => {
|
|
53
|
+
const { action } = params;
|
|
54
|
+
switch (action) {
|
|
55
|
+
case "scan": {
|
|
56
|
+
const { path: scanPath, recursive, remove_infected, move_to_quarantine, max_filesize, dry_run } = params;
|
|
57
|
+
try {
|
|
58
|
+
if (!scanPath) {
|
|
59
|
+
return { content: [createErrorContent("path is required for scan action")], isError: true };
|
|
60
|
+
}
|
|
61
|
+
const validatedPath = validateFilePath(scanPath);
|
|
62
|
+
const config = getConfig();
|
|
63
|
+
const args = [];
|
|
64
|
+
if (recursive)
|
|
65
|
+
args.push("--recursive");
|
|
66
|
+
if (!dry_run && remove_infected)
|
|
67
|
+
args.push("--remove");
|
|
68
|
+
if (!dry_run && move_to_quarantine)
|
|
69
|
+
args.push(`--move=${config.quarantineDir}`);
|
|
70
|
+
args.push(`--max-filesize=${max_filesize}`);
|
|
71
|
+
args.push(validatedPath);
|
|
72
|
+
sanitizeArgs(args);
|
|
73
|
+
const fullCmd = `clamscan ${args.join(" ")}`;
|
|
74
|
+
if (dry_run && (remove_infected || move_to_quarantine)) {
|
|
75
|
+
const scanArgs = args.filter((a) => !a.startsWith("--remove") && !a.startsWith("--move="));
|
|
76
|
+
const result = await executeCommand({
|
|
77
|
+
command: "clamscan",
|
|
78
|
+
args: scanArgs,
|
|
79
|
+
toolName: "malware_clamav",
|
|
80
|
+
timeout: getToolTimeout("clamav"),
|
|
81
|
+
});
|
|
82
|
+
const parsed = parseClamavOutput(result.stdout);
|
|
83
|
+
const infected = parsed.filter((r) => r.status === "FOUND");
|
|
84
|
+
logChange(createChangeEntry({
|
|
85
|
+
tool: "malware_clamav",
|
|
86
|
+
action: `[DRY-RUN] ClamAV scan`,
|
|
87
|
+
target: validatedPath,
|
|
88
|
+
after: `Would execute: ${fullCmd}`,
|
|
89
|
+
dryRun: true,
|
|
90
|
+
success: true,
|
|
91
|
+
}));
|
|
92
|
+
return { content: [formatToolOutput({
|
|
93
|
+
mode: "dry_run",
|
|
94
|
+
wouldExecute: fullCmd,
|
|
95
|
+
scannedFiles: parsed.length,
|
|
96
|
+
infectedFiles: infected.length,
|
|
97
|
+
findings: infected,
|
|
98
|
+
raw: result.stdout,
|
|
99
|
+
})] };
|
|
100
|
+
}
|
|
101
|
+
const result = await executeCommand({
|
|
102
|
+
command: "clamscan",
|
|
103
|
+
args,
|
|
104
|
+
toolName: "malware_clamav",
|
|
105
|
+
timeout: getToolTimeout("clamav"),
|
|
106
|
+
});
|
|
107
|
+
const parsed = parseClamavOutput(result.stdout);
|
|
108
|
+
const infected = parsed.filter((r) => r.status === "FOUND");
|
|
109
|
+
logChange(createChangeEntry({
|
|
110
|
+
tool: "malware_clamav",
|
|
111
|
+
action: `ClamAV scan${remove_infected ? " (remove)" : ""}${move_to_quarantine ? " (quarantine)" : ""}`,
|
|
112
|
+
target: validatedPath,
|
|
113
|
+
after: `Scanned: ${parsed.length}, Infected: ${infected.length}`,
|
|
114
|
+
dryRun: false,
|
|
115
|
+
success: true,
|
|
116
|
+
}));
|
|
117
|
+
return { content: [formatToolOutput({
|
|
118
|
+
scannedFiles: parsed.length,
|
|
119
|
+
infectedFiles: infected.length,
|
|
120
|
+
findings: infected,
|
|
121
|
+
actionsApplied: { removed: remove_infected, quarantined: move_to_quarantine },
|
|
122
|
+
raw: result.stdout,
|
|
123
|
+
})] };
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
127
|
+
return { content: [createErrorContent(msg)], isError: true };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
case "update": {
|
|
131
|
+
const { dry_run } = params;
|
|
132
|
+
try {
|
|
133
|
+
const fullCmd = "sudo freshclam";
|
|
134
|
+
if (dry_run ?? getConfig().dryRun) {
|
|
135
|
+
logChange(createChangeEntry({
|
|
136
|
+
tool: "malware_clamav",
|
|
137
|
+
action: `[DRY-RUN] Update ClamAV virus definitions`,
|
|
138
|
+
target: "freshclam",
|
|
139
|
+
after: fullCmd,
|
|
140
|
+
dryRun: true,
|
|
141
|
+
success: true,
|
|
142
|
+
}));
|
|
143
|
+
return {
|
|
144
|
+
content: [createTextContent(`[DRY-RUN] Would execute:\n ${fullCmd}\n\nThis would update ClamAV virus definitions.`)],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const result = await executeCommand({
|
|
148
|
+
command: "sudo",
|
|
149
|
+
args: ["freshclam"],
|
|
150
|
+
toolName: "malware_clamav",
|
|
151
|
+
timeout: getToolTimeout("clamav"),
|
|
152
|
+
});
|
|
153
|
+
const success = result.exitCode === 0;
|
|
154
|
+
logChange(createChangeEntry({
|
|
155
|
+
tool: "malware_clamav",
|
|
156
|
+
action: "Update ClamAV virus definitions",
|
|
157
|
+
target: "freshclam",
|
|
158
|
+
after: result.stdout,
|
|
159
|
+
dryRun: false,
|
|
160
|
+
success,
|
|
161
|
+
error: success ? undefined : result.stderr,
|
|
162
|
+
}));
|
|
163
|
+
if (!success) {
|
|
164
|
+
return { content: [createErrorContent(`freshclam failed (exit ${result.exitCode}): ${result.stderr}`)], isError: true };
|
|
165
|
+
}
|
|
166
|
+
return { content: [createTextContent(`ClamAV definitions updated successfully.\n\n${result.stdout}`)] };
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
170
|
+
return { content: [createErrorContent(msg)], isError: true };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
default:
|
|
174
|
+
return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
// ── 2. malware_yara_scan (kept as-is) ─────────────────────────────────
|
|
178
|
+
server.tool("malware_yara_scan", "Scan files with YARA rules for pattern-based malware detection", {
|
|
179
|
+
rules_path: z
|
|
180
|
+
.string()
|
|
181
|
+
.describe("Path to YARA rules file (.yar/.yara)"),
|
|
182
|
+
target_path: z.string().describe("File or directory to scan"),
|
|
183
|
+
recursive: z
|
|
184
|
+
.boolean()
|
|
185
|
+
.optional()
|
|
186
|
+
.default(false)
|
|
187
|
+
.describe("Recursively scan directories (default: false)"),
|
|
188
|
+
timeout_per_file: z
|
|
189
|
+
.number()
|
|
190
|
+
.optional()
|
|
191
|
+
.default(60)
|
|
192
|
+
.describe("Timeout in seconds per file (default: 60)"),
|
|
193
|
+
}, async ({ rules_path, target_path, recursive, timeout_per_file }) => {
|
|
194
|
+
try {
|
|
195
|
+
const validatedRules = validateYaraRule(rules_path);
|
|
196
|
+
const validatedTarget = validateFilePath(target_path);
|
|
197
|
+
const args = [];
|
|
198
|
+
if (recursive) {
|
|
199
|
+
args.push("-r");
|
|
200
|
+
}
|
|
201
|
+
args.push("-t", String(timeout_per_file));
|
|
202
|
+
args.push(validatedRules);
|
|
203
|
+
args.push(validatedTarget);
|
|
204
|
+
sanitizeArgs(args);
|
|
205
|
+
const result = await executeCommand({
|
|
206
|
+
command: "yara",
|
|
207
|
+
args,
|
|
208
|
+
toolName: "malware_yara_scan",
|
|
209
|
+
timeout: getToolTimeout("malware_yara_scan"),
|
|
210
|
+
});
|
|
211
|
+
// Parse YARA output: each line is "RuleName MatchedFile"
|
|
212
|
+
const findings = [];
|
|
213
|
+
for (const line of result.stdout.split("\n")) {
|
|
214
|
+
const trimmed = line.trim();
|
|
215
|
+
if (!trimmed)
|
|
216
|
+
continue;
|
|
217
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
218
|
+
if (spaceIdx > 0) {
|
|
219
|
+
findings.push({
|
|
220
|
+
rule: trimmed.substring(0, spaceIdx),
|
|
221
|
+
file: trimmed.substring(spaceIdx + 1),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const entry = createChangeEntry({
|
|
226
|
+
tool: "malware_yara_scan",
|
|
227
|
+
action: "YARA scan",
|
|
228
|
+
target: validatedTarget,
|
|
229
|
+
after: `Rules: ${validatedRules}, Matches: ${findings.length}`,
|
|
230
|
+
dryRun: false,
|
|
231
|
+
success: true,
|
|
232
|
+
});
|
|
233
|
+
logChange(entry);
|
|
234
|
+
const output = {
|
|
235
|
+
rulesFile: validatedRules,
|
|
236
|
+
targetPath: validatedTarget,
|
|
237
|
+
recursive,
|
|
238
|
+
matchCount: findings.length,
|
|
239
|
+
findings,
|
|
240
|
+
raw: result.stdout,
|
|
241
|
+
};
|
|
242
|
+
return { content: [formatToolOutput(output)] };
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
246
|
+
return { content: [createErrorContent(msg)], isError: true };
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
// ── 3. malware_file_scan (merged: suspicious_files + webshell_detect) ──
|
|
250
|
+
server.tool("malware_file_scan", "File scanning: find suspicious files (SUID, SGID, world-writable, hidden executables, recently modified) or detect web shells.", {
|
|
251
|
+
action: z.enum(["suspicious", "webshell"]).describe("Action: suspicious=find suspicious files, webshell=detect web shells"),
|
|
252
|
+
// suspicious params
|
|
253
|
+
search_path: z.string().optional().default("/tmp").describe("Root path to search (suspicious action, default: /tmp)"),
|
|
254
|
+
check_type: z.enum(["suid", "sgid", "world_writable", "hidden_executables", "recently_modified", "all"]).optional().default("all").describe("Type of suspicious file check (suspicious action)"),
|
|
255
|
+
days: z.number().optional().default(7).describe("Days for recently_modified check (suspicious action)"),
|
|
256
|
+
// webshell params
|
|
257
|
+
path: z.string().optional().default("/var/www").describe("Web root directory to scan (webshell action)"),
|
|
258
|
+
max_depth: z.number().optional().default(5).describe("Max directory depth (webshell action)"),
|
|
259
|
+
}, async (params) => {
|
|
260
|
+
const { action } = params;
|
|
261
|
+
switch (action) {
|
|
262
|
+
case "suspicious": {
|
|
263
|
+
const { search_path, check_type, days } = params;
|
|
264
|
+
try {
|
|
265
|
+
const validatedPath = validateFilePath(search_path);
|
|
266
|
+
const checks = {
|
|
267
|
+
suid: { cmd: "find", args: [validatedPath, "-perm", "-4000", "-type", "f"] },
|
|
268
|
+
sgid: { cmd: "find", args: [validatedPath, "-perm", "-2000", "-type", "f"] },
|
|
269
|
+
world_writable: { cmd: "find", args: [validatedPath, "-perm", "-0002", "-type", "f", "-not", "-path", "*/proc/*"] },
|
|
270
|
+
hidden_executables: { cmd: "find", args: [validatedPath, "-name", ".*", "-executable", "-type", "f"] },
|
|
271
|
+
recently_modified: { cmd: "find", args: [validatedPath, "-mtime", `-${days}`, "-type", "f"] },
|
|
272
|
+
};
|
|
273
|
+
const typesToCheck = check_type === "all" ? Object.keys(checks) : [check_type];
|
|
274
|
+
const results = {};
|
|
275
|
+
for (const checkName of typesToCheck) {
|
|
276
|
+
const check = checks[checkName];
|
|
277
|
+
if (!check)
|
|
278
|
+
continue;
|
|
279
|
+
const result = await executeCommand({
|
|
280
|
+
command: check.cmd,
|
|
281
|
+
args: check.args,
|
|
282
|
+
toolName: "malware_file_scan",
|
|
283
|
+
timeout: getToolTimeout("malware_suspicious_files"),
|
|
284
|
+
});
|
|
285
|
+
results[checkName] = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
286
|
+
}
|
|
287
|
+
const totalFindings = Object.values(results).reduce((sum, files) => sum + files.length, 0);
|
|
288
|
+
logChange(createChangeEntry({
|
|
289
|
+
tool: "malware_file_scan",
|
|
290
|
+
action: `Suspicious file scan (${check_type})`,
|
|
291
|
+
target: validatedPath,
|
|
292
|
+
after: `Total findings: ${totalFindings}`,
|
|
293
|
+
dryRun: false,
|
|
294
|
+
success: true,
|
|
295
|
+
}));
|
|
296
|
+
return { content: [formatToolOutput({ searchPath: validatedPath, checkType: check_type, totalFindings, categories: results })] };
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
300
|
+
return { content: [createErrorContent(msg)], isError: true };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
case "webshell": {
|
|
304
|
+
const { path: webPath, max_depth } = params;
|
|
305
|
+
try {
|
|
306
|
+
const patterns = [
|
|
307
|
+
"eval\\s*\\(\\s*\\$_(POST|GET|REQUEST)",
|
|
308
|
+
"base64_decode\\s*\\(\\s*\\$_(POST|GET|REQUEST)",
|
|
309
|
+
"system\\s*\\(\\s*\\$_(POST|GET|REQUEST)",
|
|
310
|
+
"exec\\s*\\(\\s*\\$_(POST|GET|REQUEST)",
|
|
311
|
+
"passthru\\s*\\(",
|
|
312
|
+
"shell_exec\\s*\\(",
|
|
313
|
+
"proc_open\\s*\\(",
|
|
314
|
+
"assert\\s*\\(\\s*\\$_",
|
|
315
|
+
"preg_replace.*\\/e",
|
|
316
|
+
"\\$\\{.*\\$_(POST|GET|REQUEST)",
|
|
317
|
+
"Runtime\\.getRuntime\\(\\)\\.exec",
|
|
318
|
+
"ProcessBuilder",
|
|
319
|
+
"cmd\\.exe",
|
|
320
|
+
"powershell",
|
|
321
|
+
];
|
|
322
|
+
const grepPattern = patterns.join("|");
|
|
323
|
+
const result = await executeCommand({
|
|
324
|
+
command: "grep",
|
|
325
|
+
args: ["-rnl", "--include=*.php", "--include=*.jsp", "--include=*.asp", "--include=*.aspx", "--include=*.cgi", "--include=*.py", "-E", grepPattern, webPath, `--max-depth=${max_depth}`],
|
|
326
|
+
timeout: 60000,
|
|
327
|
+
toolName: "malware_file_scan",
|
|
328
|
+
});
|
|
329
|
+
const suspiciousFiles = result.stdout.trim().split("\n").filter((f) => f.trim());
|
|
330
|
+
const details = [];
|
|
331
|
+
for (const file of suspiciousFiles.slice(0, 20)) {
|
|
332
|
+
const statResult = await executeCommand({ command: "stat", args: ["-c", "%a %U:%G %Y %s", file], timeout: 5000, toolName: "malware_file_scan" });
|
|
333
|
+
const matchResult = await executeCommand({ command: "grep", args: ["-c", "-E", grepPattern, file], timeout: 5000, toolName: "malware_file_scan" });
|
|
334
|
+
details.push({ file, stat: statResult.stdout.trim(), patternMatches: parseInt(matchResult.stdout.trim()) || 0 });
|
|
335
|
+
}
|
|
336
|
+
return { content: [createTextContent(JSON.stringify({ scanPath: webPath, totalSuspicious: suspiciousFiles.length, details, note: suspiciousFiles.length > 0 ? "Review these files manually — pattern matches may include legitimate code" : "No webshell patterns detected" }, null, 2))] };
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
340
|
+
if (errMsg.includes("exit code 1")) {
|
|
341
|
+
return { content: [createTextContent(JSON.stringify({ scanPath: params.path, totalSuspicious: 0, note: "No webshell patterns detected" }, null, 2))] };
|
|
342
|
+
}
|
|
343
|
+
return { content: [createErrorContent(errMsg)], isError: true };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
default:
|
|
347
|
+
return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
// ── 4. malware_quarantine_manage (kept as-is) ─────────────────────────
|
|
351
|
+
server.tool("malware_quarantine_manage", "Manage quarantined files (list, restore, delete, info)", {
|
|
352
|
+
action: z
|
|
353
|
+
.enum(["list", "restore", "delete", "info"])
|
|
354
|
+
.describe("Action to perform on quarantine"),
|
|
355
|
+
file_id: z
|
|
356
|
+
.string()
|
|
357
|
+
.optional()
|
|
358
|
+
.describe("Filename in quarantine directory (required for restore, delete, info)"),
|
|
359
|
+
dry_run: z
|
|
360
|
+
.boolean()
|
|
361
|
+
.optional()
|
|
362
|
+
.describe("Preview destructive actions without executing (defaults to KALI_DEFENSE_DRY_RUN env var)"),
|
|
363
|
+
}, async ({ action, file_id, dry_run }) => {
|
|
364
|
+
try {
|
|
365
|
+
const config = getConfig();
|
|
366
|
+
const quarantineDir = config.quarantineDir;
|
|
367
|
+
if ((action === "restore" || action === "delete" || action === "info") && !file_id) {
|
|
368
|
+
return {
|
|
369
|
+
content: [
|
|
370
|
+
createErrorContent(`file_id is required for '${action}' action`),
|
|
371
|
+
],
|
|
372
|
+
isError: true,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// TOOL-006: Validate file_id against path traversal
|
|
376
|
+
if (file_id) {
|
|
377
|
+
validateQuarantineFileId(file_id, quarantineDir);
|
|
378
|
+
}
|
|
379
|
+
switch (action) {
|
|
380
|
+
case "list": {
|
|
381
|
+
const result = await executeCommand({
|
|
382
|
+
command: "ls",
|
|
383
|
+
args: ["-la", quarantineDir],
|
|
384
|
+
toolName: "malware_quarantine_manage",
|
|
385
|
+
timeout: getToolTimeout("malware_quarantine_manage"),
|
|
386
|
+
});
|
|
387
|
+
return {
|
|
388
|
+
content: [
|
|
389
|
+
createTextContent(`Quarantine directory: ${quarantineDir}\n\n${result.stdout || "No files in quarantine."}`),
|
|
390
|
+
],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
case "info": {
|
|
394
|
+
const filePath = `${quarantineDir}/${file_id}`;
|
|
395
|
+
const fileResult = await executeCommand({
|
|
396
|
+
command: "file",
|
|
397
|
+
args: [filePath],
|
|
398
|
+
toolName: "malware_quarantine_manage",
|
|
399
|
+
timeout: getToolTimeout("malware_quarantine_manage"),
|
|
400
|
+
});
|
|
401
|
+
const hashResult = await executeCommand({
|
|
402
|
+
command: "sha256sum",
|
|
403
|
+
args: [filePath],
|
|
404
|
+
toolName: "malware_quarantine_manage",
|
|
405
|
+
timeout: getToolTimeout("malware_quarantine_manage"),
|
|
406
|
+
});
|
|
407
|
+
const statResult = await executeCommand({
|
|
408
|
+
command: "stat",
|
|
409
|
+
args: [filePath],
|
|
410
|
+
toolName: "malware_quarantine_manage",
|
|
411
|
+
timeout: getToolTimeout("malware_quarantine_manage"),
|
|
412
|
+
});
|
|
413
|
+
const output = {
|
|
414
|
+
file: file_id,
|
|
415
|
+
fileType: fileResult.stdout.trim(),
|
|
416
|
+
sha256: hashResult.stdout.trim().split(/\s+/)[0] ?? "",
|
|
417
|
+
stat: statResult.stdout,
|
|
418
|
+
};
|
|
419
|
+
return { content: [formatToolOutput(output)] };
|
|
420
|
+
}
|
|
421
|
+
case "restore": {
|
|
422
|
+
const filePath = `${quarantineDir}/${file_id}`;
|
|
423
|
+
const fullCmd = `cp ${filePath} /tmp/${file_id}`;
|
|
424
|
+
if (dry_run ?? getConfig().dryRun) {
|
|
425
|
+
const entry = createChangeEntry({
|
|
426
|
+
tool: "malware_quarantine_manage",
|
|
427
|
+
action: `[DRY-RUN] Restore quarantined file`,
|
|
428
|
+
target: filePath,
|
|
429
|
+
after: `Would restore to /tmp/${file_id}`,
|
|
430
|
+
dryRun: true,
|
|
431
|
+
success: true,
|
|
432
|
+
});
|
|
433
|
+
logChange(entry);
|
|
434
|
+
return {
|
|
435
|
+
content: [
|
|
436
|
+
createTextContent(`[DRY-RUN] Would execute:\n ${fullCmd}\n\nNote: File would be restored to /tmp/ for safety.`),
|
|
437
|
+
],
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const result = await executeCommand({
|
|
441
|
+
command: "cp",
|
|
442
|
+
args: [filePath, `/tmp/${file_id}`],
|
|
443
|
+
toolName: "malware_quarantine_manage",
|
|
444
|
+
timeout: getToolTimeout("malware_quarantine_manage"),
|
|
445
|
+
});
|
|
446
|
+
const success = result.exitCode === 0;
|
|
447
|
+
const entry = createChangeEntry({
|
|
448
|
+
tool: "malware_quarantine_manage",
|
|
449
|
+
action: "Restore quarantined file",
|
|
450
|
+
target: filePath,
|
|
451
|
+
after: `/tmp/${file_id}`,
|
|
452
|
+
dryRun: false,
|
|
453
|
+
success,
|
|
454
|
+
error: success ? undefined : result.stderr,
|
|
455
|
+
rollbackCommand: `rm /tmp/${file_id}`,
|
|
456
|
+
});
|
|
457
|
+
logChange(entry);
|
|
458
|
+
if (!success) {
|
|
459
|
+
return {
|
|
460
|
+
content: [
|
|
461
|
+
createErrorContent(`Restore failed (exit ${result.exitCode}): ${result.stderr}`),
|
|
462
|
+
],
|
|
463
|
+
isError: true,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
content: [
|
|
468
|
+
createTextContent(`File restored to /tmp/${file_id}\nReview the file before moving to its original location.`),
|
|
469
|
+
],
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
case "delete": {
|
|
473
|
+
const filePath = `${quarantineDir}/${file_id}`;
|
|
474
|
+
const fullCmd = `rm ${filePath}`;
|
|
475
|
+
if (dry_run ?? getConfig().dryRun) {
|
|
476
|
+
const entry = createChangeEntry({
|
|
477
|
+
tool: "malware_quarantine_manage",
|
|
478
|
+
action: `[DRY-RUN] Delete quarantined file`,
|
|
479
|
+
target: filePath,
|
|
480
|
+
dryRun: true,
|
|
481
|
+
success: true,
|
|
482
|
+
});
|
|
483
|
+
logChange(entry);
|
|
484
|
+
return {
|
|
485
|
+
content: [
|
|
486
|
+
createTextContent(`[DRY-RUN] Would execute:\n ${fullCmd}\n\nThis would permanently delete the quarantined file.`),
|
|
487
|
+
],
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
const result = await executeCommand({
|
|
491
|
+
command: "rm",
|
|
492
|
+
args: [filePath],
|
|
493
|
+
toolName: "malware_quarantine_manage",
|
|
494
|
+
timeout: getToolTimeout("malware_quarantine_manage"),
|
|
495
|
+
});
|
|
496
|
+
const success = result.exitCode === 0;
|
|
497
|
+
const entry = createChangeEntry({
|
|
498
|
+
tool: "malware_quarantine_manage",
|
|
499
|
+
action: "Delete quarantined file",
|
|
500
|
+
target: filePath,
|
|
501
|
+
dryRun: false,
|
|
502
|
+
success,
|
|
503
|
+
error: success ? undefined : result.stderr,
|
|
504
|
+
});
|
|
505
|
+
logChange(entry);
|
|
506
|
+
if (!success) {
|
|
507
|
+
return {
|
|
508
|
+
content: [
|
|
509
|
+
createErrorContent(`Delete failed (exit ${result.exitCode}): ${result.stderr}`),
|
|
510
|
+
],
|
|
511
|
+
isError: true,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
content: [
|
|
516
|
+
createTextContent(`Quarantined file ${file_id} deleted permanently.`),
|
|
517
|
+
],
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
default:
|
|
521
|
+
return {
|
|
522
|
+
content: [createErrorContent(`Unknown action: ${action}`)],
|
|
523
|
+
isError: true,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
529
|
+
return { content: [createErrorContent(msg)], isError: true };
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta/utility tools for Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 6 tools: defense_check_tools, defense_workflow (actions: suggest, run),
|
|
5
|
+
* defense_change_history, security_posture (actions: score, trend, dashboard),
|
|
6
|
+
* scheduled_audit (actions: create, list, remove, history),
|
|
7
|
+
* auto_remediate (actions: plan, apply, rollback_session, status).
|
|
8
|
+
*/
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
export declare function registerMetaTools(server: McpServer): void;
|
|
11
|
+
//# sourceMappingURL=meta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../src/tools/meta.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwrCpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAk0CzD"}
|