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,763 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deception / honeypot tools for Kali Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool: honeypot_manage (actions: deploy_canary, deploy_honeyport,
|
|
5
|
+
* check_triggers, remove, list)
|
|
6
|
+
*
|
|
7
|
+
* Provides canary token deployment (file, credential, directory, ssh_key),
|
|
8
|
+
* honeyport listener management, trigger detection, canary removal, and
|
|
9
|
+
* registry listing for deception-based intrusion detection.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { spawnSafe } from "../core/spawn-safe.js";
|
|
13
|
+
import { secureWriteFileSync } from "../core/secure-fs.js";
|
|
14
|
+
import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
|
|
15
|
+
import { existsSync, readFileSync, unlinkSync, rmSync } from "node:fs";
|
|
16
|
+
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
17
|
+
/** Base directory for canary registry and logs */
|
|
18
|
+
const CANARY_BASE_DIR = "/var/lib/kali-defense/canaries";
|
|
19
|
+
/** Path to the canary registry file */
|
|
20
|
+
const REGISTRY_PATH = `${CANARY_BASE_DIR}/registry.json`;
|
|
21
|
+
/**
|
|
22
|
+
* Run a command via spawnSafe and collect output as a promise.
|
|
23
|
+
* Handles errors gracefully — returns error info instead of throwing.
|
|
24
|
+
*/
|
|
25
|
+
async function runCommand(command, args, timeoutMs = 30_000) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
let child;
|
|
28
|
+
try {
|
|
29
|
+
child = spawnSafe(command, args);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33
|
+
resolve({ stdout: "", stderr: msg, exitCode: -1 });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let stdout = "";
|
|
37
|
+
let stderr = "";
|
|
38
|
+
let resolved = false;
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
if (!resolved) {
|
|
41
|
+
resolved = true;
|
|
42
|
+
child.kill("SIGTERM");
|
|
43
|
+
resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
|
|
44
|
+
}
|
|
45
|
+
}, timeoutMs);
|
|
46
|
+
child.stdout?.on("data", (data) => {
|
|
47
|
+
stdout += data.toString();
|
|
48
|
+
});
|
|
49
|
+
child.stderr?.on("data", (data) => {
|
|
50
|
+
stderr += data.toString();
|
|
51
|
+
});
|
|
52
|
+
child.on("close", (code) => {
|
|
53
|
+
if (!resolved) {
|
|
54
|
+
resolved = true;
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
child.on("error", (err) => {
|
|
60
|
+
if (!resolved) {
|
|
61
|
+
resolved = true;
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
resolve({ stdout, stderr: err.message, exitCode: -1 });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generate a unique canary ID based on timestamp and random suffix.
|
|
70
|
+
*/
|
|
71
|
+
function generateCanaryId() {
|
|
72
|
+
const ts = Date.now();
|
|
73
|
+
const rand = Math.random().toString(36).substring(2, 8);
|
|
74
|
+
return `canary-${ts}-${rand}`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Read the canary registry from disk.
|
|
78
|
+
* Returns an empty registry if the file doesn't exist or is invalid.
|
|
79
|
+
*/
|
|
80
|
+
function readRegistry() {
|
|
81
|
+
try {
|
|
82
|
+
if (existsSync(REGISTRY_PATH)) {
|
|
83
|
+
const data = readFileSync(REGISTRY_PATH, "utf-8");
|
|
84
|
+
return JSON.parse(data);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Fall through to default
|
|
89
|
+
}
|
|
90
|
+
return { canaries: [], lastUpdated: new Date().toISOString() };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Write the canary registry to disk using secureWriteFileSync.
|
|
94
|
+
*/
|
|
95
|
+
function writeRegistry(registry) {
|
|
96
|
+
registry.lastUpdated = new Date().toISOString();
|
|
97
|
+
secureWriteFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
|
|
98
|
+
}
|
|
99
|
+
// ── Fake credential generators ─────────────────────────────────────────────
|
|
100
|
+
/** Generate realistic-looking fake credential file content */
|
|
101
|
+
function generateFakePasswordFile() {
|
|
102
|
+
return [
|
|
103
|
+
"# Internal credentials - DO NOT DELETE",
|
|
104
|
+
"# Last rotated: 2025-11-15",
|
|
105
|
+
"",
|
|
106
|
+
"admin_portal=admin:xK9$mP2vL8qR3nT6",
|
|
107
|
+
"database_prod=dbadmin:Wy7#jF4hN1sD9bQ5",
|
|
108
|
+
"api_gateway=svc-account:mH3@kL6pR8wE2xV4",
|
|
109
|
+
"jenkins_ci=deploy:tN5$qJ9fB3yK7gU1",
|
|
110
|
+
"redis_cache=default:vC8#nD4hS6mW2pL9",
|
|
111
|
+
"",
|
|
112
|
+
"# AWS staging credentials",
|
|
113
|
+
"aws_access_key=AKIAIOSFODNN7FAKEXMP",
|
|
114
|
+
"aws_secret_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYFAKEKEY1",
|
|
115
|
+
"",
|
|
116
|
+
].join("\n");
|
|
117
|
+
}
|
|
118
|
+
/** Generate realistic-looking fake AWS credentials */
|
|
119
|
+
function generateFakeAwsCredentials() {
|
|
120
|
+
return [
|
|
121
|
+
"[default]",
|
|
122
|
+
"aws_access_key_id = AKIAIOSFODNN7FAKEXMP",
|
|
123
|
+
"aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYFAKEKEY1",
|
|
124
|
+
"region = us-east-1",
|
|
125
|
+
"",
|
|
126
|
+
"[staging]",
|
|
127
|
+
"aws_access_key_id = AKIAI44QH8DHBFAKEKEY",
|
|
128
|
+
"aws_secret_access_key = je7MtGbClwBF/2Zp9Utk/h3yCo8nvbFAKEKEY2",
|
|
129
|
+
"region = us-west-2",
|
|
130
|
+
"",
|
|
131
|
+
].join("\n");
|
|
132
|
+
}
|
|
133
|
+
/** Generate realistic-looking fake SSH private key */
|
|
134
|
+
function generateFakeSshKey() {
|
|
135
|
+
return [
|
|
136
|
+
"-----BEGIN OPENSSH PRIVATE KEY-----",
|
|
137
|
+
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn",
|
|
138
|
+
"NhAAAAAwEAAQAAAIEA0Z3IkCnr8TcHbGO3LiGx7bV2FAKEFAKEFAKEFAKEFAKEFAKEFAKE",
|
|
139
|
+
"FAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEF",
|
|
140
|
+
"AKEAAAARaG9uZXlwb3Qta2V5LWZha2UBAAACGA0Z3IkCnr8TcHbGO3LiGx7bV2FAKEFAK",
|
|
141
|
+
"EFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKE==",
|
|
142
|
+
"-----END OPENSSH PRIVATE KEY-----",
|
|
143
|
+
"",
|
|
144
|
+
].join("\n");
|
|
145
|
+
}
|
|
146
|
+
/** Generate realistic-looking directory listing files */
|
|
147
|
+
function generateFakeDirectoryFiles() {
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
name: "passwords.txt",
|
|
151
|
+
content: generateFakePasswordFile(),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "id_rsa",
|
|
155
|
+
content: generateFakeSshKey(),
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: ".env.backup",
|
|
159
|
+
content: [
|
|
160
|
+
"DATABASE_URL=postgresql://admin:s3cretP@ss@db.internal:5432/production",
|
|
161
|
+
"REDIS_URL=redis://:r3d1sP@ss@cache.internal:6379",
|
|
162
|
+
"JWT_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.FAKE.TOKEN",
|
|
163
|
+
"STRIPE_SECRET_KEY=sk_live_FAKEFAKEFAKEFAKEFAKE",
|
|
164
|
+
"SENDGRID_API_KEY=SG.FAKEFAKEFAKEFAKE.FAKEFAKEFAKEFAKE",
|
|
165
|
+
"",
|
|
166
|
+
].join("\n"),
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
async function deployCanary(canaryType, canaryPath) {
|
|
171
|
+
const canaryId = generateCanaryId();
|
|
172
|
+
let monitoringSetup = false;
|
|
173
|
+
let monitoringDetails = "";
|
|
174
|
+
let description = "";
|
|
175
|
+
switch (canaryType) {
|
|
176
|
+
case "file": {
|
|
177
|
+
const targetPath = canaryPath.endsWith("/")
|
|
178
|
+
? `${canaryPath}passwords.txt`
|
|
179
|
+
: canaryPath;
|
|
180
|
+
const content = generateFakePasswordFile();
|
|
181
|
+
secureWriteFileSync(targetPath, content);
|
|
182
|
+
description = `Canary file deployed at ${targetPath}`;
|
|
183
|
+
// Set up inotifywait monitoring
|
|
184
|
+
const inotifyResult = await runCommand("inotifywait", ["-m", "-e", "access,open", "--format", "%T %w%f %e", "--timefmt", "%Y-%m-%dT%H:%M:%S", "-d", "-o", `${CANARY_BASE_DIR}/canary-${canaryId}.log`, targetPath], 10_000);
|
|
185
|
+
monitoringSetup = inotifyResult.exitCode === 0 || inotifyResult.exitCode === -1;
|
|
186
|
+
monitoringDetails = monitoringSetup
|
|
187
|
+
? `inotifywait monitoring active, log: ${CANARY_BASE_DIR}/canary-${canaryId}.log`
|
|
188
|
+
: `inotifywait setup failed: ${inotifyResult.stderr}`;
|
|
189
|
+
// Get initial access time for comparison
|
|
190
|
+
const statResult = await runCommand("stat", ["-c", "%X", targetPath], 5_000);
|
|
191
|
+
const accessTime = statResult.exitCode === 0 ? statResult.stdout.trim() : "";
|
|
192
|
+
// Register in registry
|
|
193
|
+
const registry = readRegistry();
|
|
194
|
+
registry.canaries.push({
|
|
195
|
+
id: canaryId,
|
|
196
|
+
type: "file",
|
|
197
|
+
path: targetPath,
|
|
198
|
+
deployedAt: new Date().toISOString(),
|
|
199
|
+
status: "active",
|
|
200
|
+
description,
|
|
201
|
+
accessTimeAtDeploy: accessTime,
|
|
202
|
+
});
|
|
203
|
+
writeRegistry(registry);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case "credential": {
|
|
207
|
+
const targetPath = canaryPath.endsWith("/")
|
|
208
|
+
? `${canaryPath}.aws/credentials`
|
|
209
|
+
: canaryPath;
|
|
210
|
+
const content = generateFakeAwsCredentials();
|
|
211
|
+
secureWriteFileSync(targetPath, content);
|
|
212
|
+
description = `Canary credential file deployed at ${targetPath}`;
|
|
213
|
+
// Set up inotifywait monitoring
|
|
214
|
+
const inotifyResult = await runCommand("inotifywait", ["-m", "-e", "access,open", "--format", "%T %w%f %e", "--timefmt", "%Y-%m-%dT%H:%M:%S", "-d", "-o", `${CANARY_BASE_DIR}/canary-${canaryId}.log`, targetPath], 10_000);
|
|
215
|
+
monitoringSetup = inotifyResult.exitCode === 0 || inotifyResult.exitCode === -1;
|
|
216
|
+
monitoringDetails = monitoringSetup
|
|
217
|
+
? `inotifywait monitoring active, log: ${CANARY_BASE_DIR}/canary-${canaryId}.log`
|
|
218
|
+
: `inotifywait setup failed: ${inotifyResult.stderr}`;
|
|
219
|
+
const statResult = await runCommand("stat", ["-c", "%X", targetPath], 5_000);
|
|
220
|
+
const accessTime = statResult.exitCode === 0 ? statResult.stdout.trim() : "";
|
|
221
|
+
const registry = readRegistry();
|
|
222
|
+
registry.canaries.push({
|
|
223
|
+
id: canaryId,
|
|
224
|
+
type: "credential",
|
|
225
|
+
path: targetPath,
|
|
226
|
+
deployedAt: new Date().toISOString(),
|
|
227
|
+
status: "active",
|
|
228
|
+
description,
|
|
229
|
+
accessTimeAtDeploy: accessTime,
|
|
230
|
+
});
|
|
231
|
+
writeRegistry(registry);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case "directory": {
|
|
235
|
+
const targetDir = canaryPath.endsWith("/") ? canaryPath.slice(0, -1) : canaryPath;
|
|
236
|
+
const files = generateFakeDirectoryFiles();
|
|
237
|
+
// Create the directory and write files
|
|
238
|
+
for (const file of files) {
|
|
239
|
+
const filePath = `${targetDir}/${file.name}`;
|
|
240
|
+
secureWriteFileSync(filePath, file.content);
|
|
241
|
+
}
|
|
242
|
+
description = `Canary directory deployed at ${targetDir} with ${files.length} files`;
|
|
243
|
+
// Set up inotifywait on the directory
|
|
244
|
+
const inotifyResult = await runCommand("inotifywait", ["-m", "-r", "-e", "access,open", "--format", "%T %w%f %e", "--timefmt", "%Y-%m-%dT%H:%M:%S", "-d", "-o", `${CANARY_BASE_DIR}/canary-${canaryId}.log`, targetDir], 10_000);
|
|
245
|
+
monitoringSetup = inotifyResult.exitCode === 0 || inotifyResult.exitCode === -1;
|
|
246
|
+
monitoringDetails = monitoringSetup
|
|
247
|
+
? `inotifywait monitoring active (recursive), log: ${CANARY_BASE_DIR}/canary-${canaryId}.log`
|
|
248
|
+
: `inotifywait setup failed: ${inotifyResult.stderr}`;
|
|
249
|
+
const statResult = await runCommand("stat", ["-c", "%X", targetDir], 5_000);
|
|
250
|
+
const accessTime = statResult.exitCode === 0 ? statResult.stdout.trim() : "";
|
|
251
|
+
const registry = readRegistry();
|
|
252
|
+
registry.canaries.push({
|
|
253
|
+
id: canaryId,
|
|
254
|
+
type: "directory",
|
|
255
|
+
path: targetDir,
|
|
256
|
+
deployedAt: new Date().toISOString(),
|
|
257
|
+
status: "active",
|
|
258
|
+
description,
|
|
259
|
+
accessTimeAtDeploy: accessTime,
|
|
260
|
+
});
|
|
261
|
+
writeRegistry(registry);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
case "ssh_key": {
|
|
265
|
+
const targetPath = canaryPath.endsWith("/")
|
|
266
|
+
? `${canaryPath}id_rsa`
|
|
267
|
+
: canaryPath;
|
|
268
|
+
const content = generateFakeSshKey();
|
|
269
|
+
secureWriteFileSync(targetPath, content);
|
|
270
|
+
description = `Canary SSH key deployed at ${targetPath}`;
|
|
271
|
+
// Set up inotifywait monitoring
|
|
272
|
+
const inotifyResult = await runCommand("inotifywait", ["-m", "-e", "access,open", "--format", "%T %w%f %e", "--timefmt", "%Y-%m-%dT%H:%M:%S", "-d", "-o", `${CANARY_BASE_DIR}/canary-${canaryId}.log`, targetPath], 10_000);
|
|
273
|
+
monitoringSetup = inotifyResult.exitCode === 0 || inotifyResult.exitCode === -1;
|
|
274
|
+
monitoringDetails = monitoringSetup
|
|
275
|
+
? `inotifywait monitoring active, log: ${CANARY_BASE_DIR}/canary-${canaryId}.log`
|
|
276
|
+
: `inotifywait setup failed: ${inotifyResult.stderr}`;
|
|
277
|
+
const statResult = await runCommand("stat", ["-c", "%X", targetPath], 5_000);
|
|
278
|
+
const accessTime = statResult.exitCode === 0 ? statResult.stdout.trim() : "";
|
|
279
|
+
const registry = readRegistry();
|
|
280
|
+
registry.canaries.push({
|
|
281
|
+
id: canaryId,
|
|
282
|
+
type: "ssh_key",
|
|
283
|
+
path: targetPath,
|
|
284
|
+
deployedAt: new Date().toISOString(),
|
|
285
|
+
status: "active",
|
|
286
|
+
description,
|
|
287
|
+
accessTimeAtDeploy: accessTime,
|
|
288
|
+
});
|
|
289
|
+
writeRegistry(registry);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
canaryId,
|
|
295
|
+
type: canaryType,
|
|
296
|
+
path: canaryPath,
|
|
297
|
+
monitoringSetup,
|
|
298
|
+
monitoringDetails,
|
|
299
|
+
description,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async function deployHoneyport(port) {
|
|
303
|
+
const canaryId = generateCanaryId();
|
|
304
|
+
const logPath = `${CANARY_BASE_DIR}/honeyport-${port}.log`;
|
|
305
|
+
let listenerPid = null;
|
|
306
|
+
let iptablesRuleAdded = false;
|
|
307
|
+
// Start ncat listener in background
|
|
308
|
+
const ncatResult = await runCommand("ncat", ["-l", "-k", "-p", String(port), "-o", logPath], 5_000);
|
|
309
|
+
// ncat runs in background, so we check for PID
|
|
310
|
+
if (ncatResult.exitCode === 0 || ncatResult.exitCode === -1) {
|
|
311
|
+
// Try to find the PID of the ncat process
|
|
312
|
+
const pidResult = await runCommand("sh", ["-c", `lsof -ti tcp:${port} -sTCP:LISTEN | head -1`], 5_000);
|
|
313
|
+
if (pidResult.exitCode === 0 && pidResult.stdout.trim().length > 0) {
|
|
314
|
+
listenerPid = parseInt(pidResult.stdout.trim(), 10);
|
|
315
|
+
if (isNaN(listenerPid))
|
|
316
|
+
listenerPid = null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Add iptables LOG rule
|
|
320
|
+
const iptablesResult = await runCommand("iptables", ["-A", "INPUT", "-p", "tcp", "--dport", String(port), "-j", "LOG", "--log-prefix", `HONEYPORT:${port}: `], 10_000);
|
|
321
|
+
iptablesRuleAdded = iptablesResult.exitCode === 0;
|
|
322
|
+
const description = `Honeyport listener on port ${port}`;
|
|
323
|
+
// Register in registry
|
|
324
|
+
const registry = readRegistry();
|
|
325
|
+
registry.canaries.push({
|
|
326
|
+
id: canaryId,
|
|
327
|
+
type: "honeyport",
|
|
328
|
+
port,
|
|
329
|
+
pid: listenerPid ?? undefined,
|
|
330
|
+
deployedAt: new Date().toISOString(),
|
|
331
|
+
status: "active",
|
|
332
|
+
description,
|
|
333
|
+
});
|
|
334
|
+
writeRegistry(registry);
|
|
335
|
+
return {
|
|
336
|
+
canaryId,
|
|
337
|
+
port,
|
|
338
|
+
listenerPid,
|
|
339
|
+
logPath,
|
|
340
|
+
iptablesRuleAdded,
|
|
341
|
+
description,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
async function checkTriggers() {
|
|
345
|
+
const registry = readRegistry();
|
|
346
|
+
const result = {
|
|
347
|
+
totalCanaries: registry.canaries.length,
|
|
348
|
+
triggeredCount: 0,
|
|
349
|
+
triggered: [],
|
|
350
|
+
notTriggered: [],
|
|
351
|
+
syslogEntries: [],
|
|
352
|
+
};
|
|
353
|
+
for (const canary of registry.canaries) {
|
|
354
|
+
if (canary.status === "removed")
|
|
355
|
+
continue;
|
|
356
|
+
const triggerInfo = {
|
|
357
|
+
id: canary.id,
|
|
358
|
+
type: canary.type,
|
|
359
|
+
path: canary.path,
|
|
360
|
+
port: canary.port,
|
|
361
|
+
triggered: false,
|
|
362
|
+
severity: "INFO",
|
|
363
|
+
accessDetails: [],
|
|
364
|
+
};
|
|
365
|
+
if (canary.type === "honeyport") {
|
|
366
|
+
// Check honeyport connection logs
|
|
367
|
+
const logPath = `${CANARY_BASE_DIR}/honeyport-${canary.port}.log`;
|
|
368
|
+
if (existsSync(logPath)) {
|
|
369
|
+
try {
|
|
370
|
+
const logContent = readFileSync(logPath, "utf-8");
|
|
371
|
+
if (logContent.trim().length > 0) {
|
|
372
|
+
triggerInfo.triggered = true;
|
|
373
|
+
triggerInfo.severity = "CRITICAL";
|
|
374
|
+
triggerInfo.accessDetails.push(`Connection log entries found in ${logPath}`);
|
|
375
|
+
const lines = logContent.trim().split("\n").slice(-5);
|
|
376
|
+
for (const line of lines) {
|
|
377
|
+
triggerInfo.accessDetails.push(` ${line.trim()}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// File not readable
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else if (canary.path) {
|
|
387
|
+
// For file-based canaries: check if access time changed
|
|
388
|
+
const statResult = await runCommand("stat", ["-c", "%X", canary.path], 5_000);
|
|
389
|
+
if (statResult.exitCode === 0) {
|
|
390
|
+
const currentAccessTime = statResult.stdout.trim();
|
|
391
|
+
triggerInfo.lastAccessed = currentAccessTime;
|
|
392
|
+
if (canary.accessTimeAtDeploy && currentAccessTime !== canary.accessTimeAtDeploy) {
|
|
393
|
+
triggerInfo.triggered = true;
|
|
394
|
+
triggerInfo.severity = "HIGH";
|
|
395
|
+
triggerInfo.accessDetails.push(`Access time changed: ${canary.accessTimeAtDeploy} → ${currentAccessTime}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Check inotifywait log
|
|
399
|
+
const inotifyLogPath = `${CANARY_BASE_DIR}/canary-${canary.id}.log`;
|
|
400
|
+
if (existsSync(inotifyLogPath)) {
|
|
401
|
+
try {
|
|
402
|
+
const logContent = readFileSync(inotifyLogPath, "utf-8");
|
|
403
|
+
if (logContent.trim().length > 0) {
|
|
404
|
+
triggerInfo.triggered = true;
|
|
405
|
+
triggerInfo.severity = "CRITICAL";
|
|
406
|
+
triggerInfo.accessDetails.push(`inotify events detected in ${inotifyLogPath}`);
|
|
407
|
+
const lines = logContent.trim().split("\n").slice(-5);
|
|
408
|
+
for (const line of lines) {
|
|
409
|
+
triggerInfo.accessDetails.push(` ${line.trim()}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
// File not readable
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (triggerInfo.triggered) {
|
|
419
|
+
result.triggeredCount++;
|
|
420
|
+
// Update canary status in registry
|
|
421
|
+
canary.status = "triggered";
|
|
422
|
+
result.triggered.push(triggerInfo);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
result.notTriggered.push(canary.id);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// Check syslog for honeyport entries
|
|
429
|
+
const syslogResult = await runCommand("grep", ["-i", "HONEYPORT", "/var/log/syslog"], 10_000);
|
|
430
|
+
if (syslogResult.exitCode === 0 && syslogResult.stdout.trim().length > 0) {
|
|
431
|
+
const entries = syslogResult.stdout.trim().split("\n").slice(-10);
|
|
432
|
+
result.syslogEntries = entries;
|
|
433
|
+
}
|
|
434
|
+
// Save updated statuses
|
|
435
|
+
writeRegistry(registry);
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
async function removeCanary(canaryId) {
|
|
439
|
+
const registry = readRegistry();
|
|
440
|
+
const result = {
|
|
441
|
+
canaryId,
|
|
442
|
+
found: false,
|
|
443
|
+
fileRemoved: false,
|
|
444
|
+
listenerKilled: false,
|
|
445
|
+
iptablesRemoved: false,
|
|
446
|
+
description: "",
|
|
447
|
+
};
|
|
448
|
+
const canaryIndex = registry.canaries.findIndex((c) => c.id === canaryId);
|
|
449
|
+
if (canaryIndex === -1) {
|
|
450
|
+
result.description = `Canary ${canaryId} not found in registry`;
|
|
451
|
+
return result;
|
|
452
|
+
}
|
|
453
|
+
result.found = true;
|
|
454
|
+
const canary = registry.canaries[canaryIndex];
|
|
455
|
+
// Remove the canary file/directory
|
|
456
|
+
if (canary.path) {
|
|
457
|
+
try {
|
|
458
|
+
if (canary.type === "directory") {
|
|
459
|
+
rmSync(canary.path, { recursive: true, force: true });
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
unlinkSync(canary.path);
|
|
463
|
+
}
|
|
464
|
+
result.fileRemoved = true;
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// File may already be gone
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// Kill honeyport listener if applicable
|
|
471
|
+
if (canary.type === "honeyport" && canary.pid) {
|
|
472
|
+
const killResult = await runCommand("kill", [String(canary.pid)], 5_000);
|
|
473
|
+
result.listenerKilled = killResult.exitCode === 0;
|
|
474
|
+
}
|
|
475
|
+
// Remove iptables rule if honeyport
|
|
476
|
+
if (canary.type === "honeyport" && canary.port) {
|
|
477
|
+
const iptablesResult = await runCommand("iptables", ["-D", "INPUT", "-p", "tcp", "--dport", String(canary.port), "-j", "LOG", "--log-prefix", `HONEYPORT:${canary.port}: `], 10_000);
|
|
478
|
+
result.iptablesRemoved = iptablesResult.exitCode === 0;
|
|
479
|
+
}
|
|
480
|
+
// Remove inotify log if exists
|
|
481
|
+
const inotifyLogPath = `${CANARY_BASE_DIR}/canary-${canary.id}.log`;
|
|
482
|
+
try {
|
|
483
|
+
if (existsSync(inotifyLogPath)) {
|
|
484
|
+
unlinkSync(inotifyLogPath);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// Ignore cleanup errors
|
|
489
|
+
}
|
|
490
|
+
// Update registry
|
|
491
|
+
canary.status = "removed";
|
|
492
|
+
writeRegistry(registry);
|
|
493
|
+
result.description = `Canary ${canaryId} (${canary.type}) removed`;
|
|
494
|
+
return result;
|
|
495
|
+
}
|
|
496
|
+
function listCanaries() {
|
|
497
|
+
const registry = readRegistry();
|
|
498
|
+
const active = registry.canaries.filter((c) => c.status === "active").length;
|
|
499
|
+
const triggered = registry.canaries.filter((c) => c.status === "triggered").length;
|
|
500
|
+
const removed = registry.canaries.filter((c) => c.status === "removed").length;
|
|
501
|
+
return {
|
|
502
|
+
totalCanaries: registry.canaries.length,
|
|
503
|
+
active,
|
|
504
|
+
triggered,
|
|
505
|
+
removed,
|
|
506
|
+
canaries: registry.canaries,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
// ── Registration entry point ───────────────────────────────────────────────
|
|
510
|
+
export function registerDeceptionTools(server) {
|
|
511
|
+
server.tool("honeypot_manage", "Honeypot/deception: deploy canary tokens (file, credential, directory, ssh_key), set up honeyport listeners, check triggers, remove canaries, and list deployed deception assets.", {
|
|
512
|
+
action: z
|
|
513
|
+
.enum(["deploy_canary", "deploy_honeyport", "check_triggers", "remove", "list"])
|
|
514
|
+
.describe("Action: deploy_canary=deploy canary token/tripwire, deploy_honeyport=set up honeyport listener, check_triggers=check if canaries triggered, remove=remove a canary, list=list all canaries"),
|
|
515
|
+
canary_type: z
|
|
516
|
+
.enum(["file", "credential", "directory", "ssh_key"])
|
|
517
|
+
.optional()
|
|
518
|
+
.describe("Type of canary to deploy (used with deploy_canary): file, credential, directory, ssh_key"),
|
|
519
|
+
canary_path: z
|
|
520
|
+
.string()
|
|
521
|
+
.optional()
|
|
522
|
+
.describe("Path for canary deployment (used with deploy_canary)"),
|
|
523
|
+
port: z
|
|
524
|
+
.number()
|
|
525
|
+
.optional()
|
|
526
|
+
.describe("Port for honeyport listener (used with deploy_honeyport)"),
|
|
527
|
+
canary_id: z
|
|
528
|
+
.string()
|
|
529
|
+
.optional()
|
|
530
|
+
.describe("ID of canary to remove (used with remove)"),
|
|
531
|
+
output_format: z
|
|
532
|
+
.enum(["text", "json"])
|
|
533
|
+
.optional()
|
|
534
|
+
.default("text")
|
|
535
|
+
.describe("Output format (default text)"),
|
|
536
|
+
}, async (params) => {
|
|
537
|
+
const { action } = params;
|
|
538
|
+
const outputFormat = params.output_format ?? "text";
|
|
539
|
+
switch (action) {
|
|
540
|
+
// ── deploy_canary ────────────────────────────────────────────────
|
|
541
|
+
case "deploy_canary": {
|
|
542
|
+
try {
|
|
543
|
+
const canaryType = params.canary_type;
|
|
544
|
+
const canaryPath = params.canary_path;
|
|
545
|
+
if (!canaryType) {
|
|
546
|
+
return {
|
|
547
|
+
content: [createErrorContent("deploy_canary requires canary_type parameter")],
|
|
548
|
+
isError: true,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (!canaryPath) {
|
|
552
|
+
return {
|
|
553
|
+
content: [createErrorContent("deploy_canary requires canary_path parameter")],
|
|
554
|
+
isError: true,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
const deployResult = await deployCanary(canaryType, canaryPath);
|
|
558
|
+
const output = {
|
|
559
|
+
action: "deploy_canary",
|
|
560
|
+
canaryId: deployResult.canaryId,
|
|
561
|
+
type: deployResult.type,
|
|
562
|
+
path: deployResult.path,
|
|
563
|
+
monitoringSetup: deployResult.monitoringSetup,
|
|
564
|
+
monitoringDetails: deployResult.monitoringDetails,
|
|
565
|
+
description: deployResult.description,
|
|
566
|
+
};
|
|
567
|
+
if (outputFormat === "json") {
|
|
568
|
+
return { content: [formatToolOutput(output)] };
|
|
569
|
+
}
|
|
570
|
+
let text = "Honeypot — Deploy Canary\n\n";
|
|
571
|
+
text += `Canary ID: ${deployResult.canaryId}\n`;
|
|
572
|
+
text += `Type: ${deployResult.type}\n`;
|
|
573
|
+
text += `Path: ${deployResult.path}\n`;
|
|
574
|
+
text += `Monitoring: ${deployResult.monitoringSetup ? "active ✓" : "not set up ⚠"}\n`;
|
|
575
|
+
text += `Details: ${deployResult.monitoringDetails}\n`;
|
|
576
|
+
text += `\n${deployResult.description}\n`;
|
|
577
|
+
return { content: [createTextContent(text)] };
|
|
578
|
+
}
|
|
579
|
+
catch (err) {
|
|
580
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
581
|
+
return { content: [createErrorContent(`deploy_canary failed: ${msg}`)], isError: true };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// ── deploy_honeyport ─────────────────────────────────────────────
|
|
585
|
+
case "deploy_honeyport": {
|
|
586
|
+
try {
|
|
587
|
+
const port = params.port;
|
|
588
|
+
if (!port) {
|
|
589
|
+
return {
|
|
590
|
+
content: [createErrorContent("deploy_honeyport requires port parameter")],
|
|
591
|
+
isError: true,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
const honeyResult = await deployHoneyport(port);
|
|
595
|
+
const output = {
|
|
596
|
+
action: "deploy_honeyport",
|
|
597
|
+
canaryId: honeyResult.canaryId,
|
|
598
|
+
port: honeyResult.port,
|
|
599
|
+
listenerPid: honeyResult.listenerPid,
|
|
600
|
+
logPath: honeyResult.logPath,
|
|
601
|
+
iptablesRuleAdded: honeyResult.iptablesRuleAdded,
|
|
602
|
+
description: honeyResult.description,
|
|
603
|
+
};
|
|
604
|
+
if (outputFormat === "json") {
|
|
605
|
+
return { content: [formatToolOutput(output)] };
|
|
606
|
+
}
|
|
607
|
+
let text = "Honeypot — Deploy Honeyport\n\n";
|
|
608
|
+
text += `Canary ID: ${honeyResult.canaryId}\n`;
|
|
609
|
+
text += `Port: ${honeyResult.port}\n`;
|
|
610
|
+
text += `Listener PID: ${honeyResult.listenerPid ?? "unknown"}\n`;
|
|
611
|
+
text += `Log Path: ${honeyResult.logPath}\n`;
|
|
612
|
+
text += `Iptables LOG Rule: ${honeyResult.iptablesRuleAdded ? "added ✓" : "not added ⚠"}\n`;
|
|
613
|
+
text += `\n${honeyResult.description}\n`;
|
|
614
|
+
return { content: [createTextContent(text)] };
|
|
615
|
+
}
|
|
616
|
+
catch (err) {
|
|
617
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
618
|
+
return { content: [createErrorContent(`deploy_honeyport failed: ${msg}`)], isError: true };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// ── check_triggers ───────────────────────────────────────────────
|
|
622
|
+
case "check_triggers": {
|
|
623
|
+
try {
|
|
624
|
+
const triggerResult = await checkTriggers();
|
|
625
|
+
const output = {
|
|
626
|
+
action: "check_triggers",
|
|
627
|
+
totalCanaries: triggerResult.totalCanaries,
|
|
628
|
+
triggeredCount: triggerResult.triggeredCount,
|
|
629
|
+
triggered: triggerResult.triggered,
|
|
630
|
+
notTriggered: triggerResult.notTriggered,
|
|
631
|
+
syslogEntries: triggerResult.syslogEntries,
|
|
632
|
+
};
|
|
633
|
+
if (outputFormat === "json") {
|
|
634
|
+
return { content: [formatToolOutput(output)] };
|
|
635
|
+
}
|
|
636
|
+
let text = "Honeypot — Check Triggers\n\n";
|
|
637
|
+
text += `Total Canaries: ${triggerResult.totalCanaries}\n`;
|
|
638
|
+
text += `Triggered: ${triggerResult.triggeredCount}\n`;
|
|
639
|
+
text += `Not Triggered: ${triggerResult.notTriggered.length}\n\n`;
|
|
640
|
+
if (triggerResult.triggered.length > 0) {
|
|
641
|
+
text += "⚠ TRIGGERED CANARIES:\n";
|
|
642
|
+
for (const t of triggerResult.triggered) {
|
|
643
|
+
text += `\n • ${t.id} [${t.type}] — Severity: ${t.severity}\n`;
|
|
644
|
+
if (t.path)
|
|
645
|
+
text += ` Path: ${t.path}\n`;
|
|
646
|
+
if (t.port)
|
|
647
|
+
text += ` Port: ${t.port}\n`;
|
|
648
|
+
for (const detail of t.accessDetails) {
|
|
649
|
+
text += ` ${detail}\n`;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (triggerResult.notTriggered.length > 0) {
|
|
654
|
+
text += "\nNot Triggered:\n";
|
|
655
|
+
for (const id of triggerResult.notTriggered) {
|
|
656
|
+
text += ` • ${id}\n`;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (triggerResult.syslogEntries.length > 0) {
|
|
660
|
+
text += "\nSyslog Honeyport Entries:\n";
|
|
661
|
+
for (const entry of triggerResult.syslogEntries) {
|
|
662
|
+
text += ` • ${entry}\n`;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return { content: [createTextContent(text)] };
|
|
666
|
+
}
|
|
667
|
+
catch (err) {
|
|
668
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
669
|
+
return { content: [createErrorContent(`check_triggers failed: ${msg}`)], isError: true };
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// ── remove ───────────────────────────────────────────────────────
|
|
673
|
+
case "remove": {
|
|
674
|
+
try {
|
|
675
|
+
const canaryId = params.canary_id;
|
|
676
|
+
if (!canaryId) {
|
|
677
|
+
return {
|
|
678
|
+
content: [createErrorContent("remove requires canary_id parameter")],
|
|
679
|
+
isError: true,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const removeResult = await removeCanary(canaryId);
|
|
683
|
+
const output = {
|
|
684
|
+
action: "remove",
|
|
685
|
+
canaryId: removeResult.canaryId,
|
|
686
|
+
found: removeResult.found,
|
|
687
|
+
fileRemoved: removeResult.fileRemoved,
|
|
688
|
+
listenerKilled: removeResult.listenerKilled,
|
|
689
|
+
iptablesRemoved: removeResult.iptablesRemoved,
|
|
690
|
+
description: removeResult.description,
|
|
691
|
+
};
|
|
692
|
+
if (outputFormat === "json") {
|
|
693
|
+
return { content: [formatToolOutput(output)] };
|
|
694
|
+
}
|
|
695
|
+
let text = "Honeypot — Remove Canary\n\n";
|
|
696
|
+
if (!removeResult.found) {
|
|
697
|
+
text += `Canary ${canaryId} not found in registry\n`;
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
text += `Canary ID: ${removeResult.canaryId}\n`;
|
|
701
|
+
text += `File/Directory Removed: ${removeResult.fileRemoved ? "yes ✓" : "no"}\n`;
|
|
702
|
+
if (removeResult.listenerKilled) {
|
|
703
|
+
text += `Listener Killed: yes ✓\n`;
|
|
704
|
+
}
|
|
705
|
+
if (removeResult.iptablesRemoved) {
|
|
706
|
+
text += `Iptables Rule Removed: yes ✓\n`;
|
|
707
|
+
}
|
|
708
|
+
text += `\n${removeResult.description}\n`;
|
|
709
|
+
}
|
|
710
|
+
return { content: [createTextContent(text)] };
|
|
711
|
+
}
|
|
712
|
+
catch (err) {
|
|
713
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
714
|
+
return { content: [createErrorContent(`remove failed: ${msg}`)], isError: true };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// ── list ─────────────────────────────────────────────────────────
|
|
718
|
+
case "list": {
|
|
719
|
+
try {
|
|
720
|
+
const listResult = listCanaries();
|
|
721
|
+
const output = {
|
|
722
|
+
action: "list",
|
|
723
|
+
totalCanaries: listResult.totalCanaries,
|
|
724
|
+
active: listResult.active,
|
|
725
|
+
triggered: listResult.triggered,
|
|
726
|
+
removed: listResult.removed,
|
|
727
|
+
canaries: listResult.canaries,
|
|
728
|
+
};
|
|
729
|
+
if (outputFormat === "json") {
|
|
730
|
+
return { content: [formatToolOutput(output)] };
|
|
731
|
+
}
|
|
732
|
+
let text = "Honeypot — Canary Registry\n\n";
|
|
733
|
+
text += `Total: ${listResult.totalCanaries}\n`;
|
|
734
|
+
text += `Active: ${listResult.active}\n`;
|
|
735
|
+
text += `Triggered: ${listResult.triggered}\n`;
|
|
736
|
+
text += `Removed: ${listResult.removed}\n\n`;
|
|
737
|
+
if (listResult.canaries.length > 0) {
|
|
738
|
+
text += "Canaries:\n";
|
|
739
|
+
for (const canary of listResult.canaries) {
|
|
740
|
+
text += `\n • ${canary.id} [${canary.type}] — ${canary.status.toUpperCase()}\n`;
|
|
741
|
+
text += ` Deployed: ${canary.deployedAt}\n`;
|
|
742
|
+
if (canary.path)
|
|
743
|
+
text += ` Path: ${canary.path}\n`;
|
|
744
|
+
if (canary.port)
|
|
745
|
+
text += ` Port: ${canary.port}\n`;
|
|
746
|
+
text += ` ${canary.description}\n`;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
text += "No canaries deployed\n";
|
|
751
|
+
}
|
|
752
|
+
return { content: [createTextContent(text)] };
|
|
753
|
+
}
|
|
754
|
+
catch (err) {
|
|
755
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
756
|
+
return { content: [createErrorContent(`list failed: ${msg}`)], isError: true };
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
default:
|
|
760
|
+
return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|