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,596 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets tools for Kali Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 4 tools: secrets_scan, secrets_env_audit,
|
|
5
|
+
* secrets_ssh_key_sprawl, scan_git_history.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { executeCommand } from "../core/executor.js";
|
|
9
|
+
import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
|
|
10
|
+
// ── TOOL-021 remediation: error message sanitization ───────────────────────
|
|
11
|
+
/** Patterns for sensitive environment variable names */
|
|
12
|
+
const SENSITIVE_ENV_PATTERNS = [
|
|
13
|
+
/SECRET/i,
|
|
14
|
+
/KEY/i,
|
|
15
|
+
/TOKEN/i,
|
|
16
|
+
/PASSWORD/i,
|
|
17
|
+
/PASSWD/i,
|
|
18
|
+
/CREDENTIAL/i,
|
|
19
|
+
/AUTH/i,
|
|
20
|
+
/API_KEY/i,
|
|
21
|
+
/PRIVATE/i,
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Sanitize an error message by redacting potential environment variable values
|
|
25
|
+
* and other sensitive data that might be exposed in error output.
|
|
26
|
+
* Never logs the full process.env object.
|
|
27
|
+
*/
|
|
28
|
+
function sanitizeErrorMessage(message) {
|
|
29
|
+
let sanitized = message;
|
|
30
|
+
// Redact values of known-sensitive environment variables
|
|
31
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
32
|
+
if (!value || value.length < 4)
|
|
33
|
+
continue; // Skip very short values
|
|
34
|
+
const isSensitive = SENSITIVE_ENV_PATTERNS.some((pattern) => pattern.test(key));
|
|
35
|
+
if (isSensitive) {
|
|
36
|
+
// Replace all occurrences of the sensitive value
|
|
37
|
+
sanitized = sanitized.split(value).join("[REDACTED]");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Redact common token-like patterns in the message itself
|
|
41
|
+
sanitized = sanitized.replace(/(ghp_|gho_|github_pat_|sk-|sk_live_|AKIA|glpat-|glrt-|hvs\.)[A-Za-z0-9_\-]{8,}/g, "$1[REDACTED]");
|
|
42
|
+
// Redact long base64-like strings that may be tokens/keys (40+ chars)
|
|
43
|
+
sanitized = sanitized.replace(/(?<==|:\s*)[A-Za-z0-9+/]{40,}={0,2}/g, "[REDACTED]");
|
|
44
|
+
return sanitized;
|
|
45
|
+
}
|
|
46
|
+
// ── Registration entry point ───────────────────────────────────────────────
|
|
47
|
+
export function registerSecretsTools(server) {
|
|
48
|
+
// ── 1. secrets_scan ────────────────────────────────────────────────────
|
|
49
|
+
server.tool("secrets_scan", "Scan filesystem for hardcoded secrets (API keys, passwords, private keys, tokens)", {
|
|
50
|
+
path: z
|
|
51
|
+
.string()
|
|
52
|
+
.default("/home")
|
|
53
|
+
.describe("Root path to scan for secrets"),
|
|
54
|
+
scan_type: z
|
|
55
|
+
.enum(["all", "api_keys", "private_keys", "passwords", "tokens"])
|
|
56
|
+
.default("all")
|
|
57
|
+
.describe("Type of secrets to scan for"),
|
|
58
|
+
max_depth: z
|
|
59
|
+
.number()
|
|
60
|
+
.optional()
|
|
61
|
+
.default(5)
|
|
62
|
+
.describe("Maximum directory depth to search"),
|
|
63
|
+
}, async ({ path: scanPath, scan_type, max_depth }) => {
|
|
64
|
+
try {
|
|
65
|
+
const findings = {};
|
|
66
|
+
// ── API Keys ───────────────────────────────────────────────────
|
|
67
|
+
if (scan_type === "all" || scan_type === "api_keys") {
|
|
68
|
+
const result = await executeCommand({
|
|
69
|
+
command: "grep",
|
|
70
|
+
args: [
|
|
71
|
+
"-rnl",
|
|
72
|
+
"--include=*.py", "--include=*.js", "--include=*.ts",
|
|
73
|
+
"--include=*.rb", "--include=*.go", "--include=*.java",
|
|
74
|
+
"--include=*.yaml", "--include=*.yml", "--include=*.json",
|
|
75
|
+
"--include=*.xml", "--include=*.conf", "--include=*.cfg",
|
|
76
|
+
"--include=*.env", "--include=*.ini", "--include=*.sh",
|
|
77
|
+
`-E`,
|
|
78
|
+
`(api[_-]?key|apikey|api[_-]?secret)\\s*[:=]`,
|
|
79
|
+
`--max-depth=${max_depth}`,
|
|
80
|
+
scanPath,
|
|
81
|
+
],
|
|
82
|
+
toolName: "secrets_scan",
|
|
83
|
+
timeout: 60000,
|
|
84
|
+
});
|
|
85
|
+
const files = result.stdout.split("\n").filter((l) => l.trim());
|
|
86
|
+
if (files.length > 0) {
|
|
87
|
+
findings["api_keys"] = files;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ── Private Keys ───────────────────────────────────────────────
|
|
91
|
+
if (scan_type === "all" || scan_type === "private_keys") {
|
|
92
|
+
const result = await executeCommand({
|
|
93
|
+
command: "find",
|
|
94
|
+
args: [
|
|
95
|
+
scanPath,
|
|
96
|
+
"-maxdepth", String(max_depth),
|
|
97
|
+
"(", "-name", "*.pem",
|
|
98
|
+
"-o", "-name", "*.key",
|
|
99
|
+
"-o", "-name", "id_rsa",
|
|
100
|
+
"-o", "-name", "id_ecdsa",
|
|
101
|
+
"-o", "-name", "id_ed25519",
|
|
102
|
+
"-o", "-name", "*.p12",
|
|
103
|
+
"-o", "-name", "*.pfx",
|
|
104
|
+
")",
|
|
105
|
+
"-type", "f",
|
|
106
|
+
],
|
|
107
|
+
toolName: "secrets_scan",
|
|
108
|
+
timeout: 60000,
|
|
109
|
+
});
|
|
110
|
+
const files = result.stdout.split("\n").filter((l) => l.trim());
|
|
111
|
+
if (files.length > 0) {
|
|
112
|
+
// Check permissions on each found key file
|
|
113
|
+
const keyDetails = [];
|
|
114
|
+
for (const file of files.slice(0, 50)) {
|
|
115
|
+
const statResult = await executeCommand({
|
|
116
|
+
command: "stat",
|
|
117
|
+
args: ["-c", "%a %U:%G %n", file],
|
|
118
|
+
toolName: "secrets_scan",
|
|
119
|
+
timeout: 5000,
|
|
120
|
+
});
|
|
121
|
+
if (statResult.exitCode === 0 && statResult.stdout.trim()) {
|
|
122
|
+
const perms = statResult.stdout.trim().split(" ")[0];
|
|
123
|
+
const permWarning = perms !== "600" && perms !== "400"
|
|
124
|
+
? " [WARNING: insecure permissions]"
|
|
125
|
+
: " [OK]";
|
|
126
|
+
keyDetails.push(`${statResult.stdout.trim()}${permWarning}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
findings["private_keys"] = keyDetails;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// ── Passwords ──────────────────────────────────────────────────
|
|
133
|
+
if (scan_type === "all" || scan_type === "passwords") {
|
|
134
|
+
const result = await executeCommand({
|
|
135
|
+
command: "grep",
|
|
136
|
+
args: [
|
|
137
|
+
"-rnl",
|
|
138
|
+
"--include=*.py", "--include=*.js", "--include=*.ts",
|
|
139
|
+
"--include=*.rb", "--include=*.conf", "--include=*.cfg",
|
|
140
|
+
"--include=*.env", "--include=*.ini", "--include=*.yaml",
|
|
141
|
+
"--include=*.yml", "--include=*.json",
|
|
142
|
+
`-E`,
|
|
143
|
+
`(password|passwd|secret|token)\\s*[:=]\\s*['"][^'"]{4,}`,
|
|
144
|
+
`--max-depth=${max_depth}`,
|
|
145
|
+
scanPath,
|
|
146
|
+
],
|
|
147
|
+
toolName: "secrets_scan",
|
|
148
|
+
timeout: 60000,
|
|
149
|
+
});
|
|
150
|
+
const files = result.stdout.split("\n").filter((l) => l.trim());
|
|
151
|
+
if (files.length > 0) {
|
|
152
|
+
findings["passwords"] = files;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// ── Tokens ─────────────────────────────────────────────────────
|
|
156
|
+
if (scan_type === "all" || scan_type === "tokens") {
|
|
157
|
+
const tokenPatterns = [
|
|
158
|
+
"ghp_", // GitHub personal access token
|
|
159
|
+
"gho_", // GitHub OAuth token
|
|
160
|
+
"github_pat_", // GitHub fine-grained PAT
|
|
161
|
+
"sk-", // OpenAI API key
|
|
162
|
+
"sk_live_[A-Za-z0-9]{24,}", // Stripe live secret key
|
|
163
|
+
"rk_live_", // Stripe restricted key
|
|
164
|
+
"AKIA[0-9A-Z]", // AWS access key ID
|
|
165
|
+
"xox[bpors]-[A-Za-z0-9-]{10,}", // Slack tokens (bot/user/app)
|
|
166
|
+
"glpat-[A-Za-z0-9_-]{20,}", // GitLab personal access token
|
|
167
|
+
"glrt-[A-Za-z0-9_-]{20,}", // GitLab pipeline token
|
|
168
|
+
"hvs\\.[A-Za-z0-9_-]{24,}", // HashiCorp Vault token (new format)
|
|
169
|
+
"s\\.[A-Za-z0-9]{24}", // HashiCorp Vault token (legacy)
|
|
170
|
+
'"type":.*"service_account"', // GCP service account key
|
|
171
|
+
"AZURE_(CLIENT_SECRET|TENANT_ID|SUBSCRIPTION_KEY)", // Azure credentials
|
|
172
|
+
"[A-Za-z0-9+/]{40,}={0,2}", // Generic high-entropy base64 secrets
|
|
173
|
+
];
|
|
174
|
+
const result = await executeCommand({
|
|
175
|
+
command: "grep",
|
|
176
|
+
args: [
|
|
177
|
+
"-rnl",
|
|
178
|
+
`-E`,
|
|
179
|
+
`(${tokenPatterns.join("|")})`,
|
|
180
|
+
`--max-depth=${max_depth}`,
|
|
181
|
+
scanPath,
|
|
182
|
+
],
|
|
183
|
+
toolName: "secrets_scan",
|
|
184
|
+
timeout: 60000,
|
|
185
|
+
});
|
|
186
|
+
const files = result.stdout.split("\n").filter((l) => l.trim());
|
|
187
|
+
if (files.length > 0) {
|
|
188
|
+
findings["tokens"] = files;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// ── Build report ───────────────────────────────────────────────
|
|
192
|
+
const totalFindings = Object.values(findings).reduce((sum, arr) => sum + arr.length, 0);
|
|
193
|
+
const lines = [
|
|
194
|
+
`=== Secrets Scan Report ===`,
|
|
195
|
+
`Path: ${scanPath}`,
|
|
196
|
+
`Scan Type: ${scan_type}`,
|
|
197
|
+
`Max Depth: ${max_depth}`,
|
|
198
|
+
`Total Findings: ${totalFindings}`,
|
|
199
|
+
``,
|
|
200
|
+
];
|
|
201
|
+
for (const [category, files] of Object.entries(findings)) {
|
|
202
|
+
lines.push(`── ${category.toUpperCase()} (${files.length} found) ──`);
|
|
203
|
+
for (const file of files) {
|
|
204
|
+
lines.push(` ${file}`);
|
|
205
|
+
}
|
|
206
|
+
lines.push(``);
|
|
207
|
+
}
|
|
208
|
+
if (totalFindings === 0) {
|
|
209
|
+
lines.push(`No hardcoded secrets detected in the scanned path.`);
|
|
210
|
+
}
|
|
211
|
+
return { content: [createTextContent(lines.join("\n"))] };
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
215
|
+
// TOOL-021: Sanitize error message to avoid leaking sensitive data
|
|
216
|
+
return { content: [createErrorContent(sanitizeErrorMessage(msg))], isError: true };
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
// ── 2. secrets_env_audit ───────────────────────────────────────────────
|
|
220
|
+
server.tool("secrets_env_audit", "Audit environment variable security and .env file exposure", {
|
|
221
|
+
check_env: z
|
|
222
|
+
.boolean()
|
|
223
|
+
.optional()
|
|
224
|
+
.default(true)
|
|
225
|
+
.describe("Check environment variables for sensitive names"),
|
|
226
|
+
check_files: z
|
|
227
|
+
.boolean()
|
|
228
|
+
.optional()
|
|
229
|
+
.default(true)
|
|
230
|
+
.describe("Check for exposed .env files on disk"),
|
|
231
|
+
}, async ({ check_env, check_files }) => {
|
|
232
|
+
try {
|
|
233
|
+
const lines = [
|
|
234
|
+
`=== Environment Security Audit ===`,
|
|
235
|
+
``,
|
|
236
|
+
];
|
|
237
|
+
// ── Check environment variables ────────────────────────────────
|
|
238
|
+
if (check_env) {
|
|
239
|
+
lines.push(`── SENSITIVE ENVIRONMENT VARIABLES ──`);
|
|
240
|
+
const envResult = await executeCommand({
|
|
241
|
+
command: "env",
|
|
242
|
+
args: [],
|
|
243
|
+
toolName: "secrets_env_audit",
|
|
244
|
+
timeout: 10000,
|
|
245
|
+
});
|
|
246
|
+
// Filter and redact sensitive variables in TypeScript
|
|
247
|
+
const sensitivePattern = /password|secret|token|key|api/i;
|
|
248
|
+
const envVars = envResult.stdout
|
|
249
|
+
.split("\n")
|
|
250
|
+
.filter((line) => sensitivePattern.test(line.split("=")[0] ?? ""))
|
|
251
|
+
.map((line) => line.replace(/=.*/, "=<REDACTED>"))
|
|
252
|
+
.sort();
|
|
253
|
+
if (envVars.length > 0) {
|
|
254
|
+
lines.push(`Found ${envVars.length} sensitive variable(s) (values redacted):`);
|
|
255
|
+
for (const v of envVars) {
|
|
256
|
+
lines.push(` ${v}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
lines.push(`No sensitive environment variables detected.`);
|
|
261
|
+
}
|
|
262
|
+
lines.push(``);
|
|
263
|
+
}
|
|
264
|
+
// ── Check .env files ───────────────────────────────────────────
|
|
265
|
+
if (check_files) {
|
|
266
|
+
lines.push(`── .ENV FILE EXPOSURE ──`);
|
|
267
|
+
const findResult = await executeCommand({
|
|
268
|
+
command: "find",
|
|
269
|
+
args: [
|
|
270
|
+
"/home", "/tmp", "/var",
|
|
271
|
+
"(", "-name", ".env",
|
|
272
|
+
"-o", "-name", ".env.local",
|
|
273
|
+
"-o", "-name", ".env.production",
|
|
274
|
+
")",
|
|
275
|
+
"-type", "f",
|
|
276
|
+
],
|
|
277
|
+
toolName: "secrets_env_audit",
|
|
278
|
+
timeout: 30000,
|
|
279
|
+
});
|
|
280
|
+
const envFiles = findResult.stdout.split("\n").filter((l) => l.trim());
|
|
281
|
+
if (envFiles.length > 0) {
|
|
282
|
+
lines.push(`Found ${envFiles.length} .env file(s):`);
|
|
283
|
+
for (const file of envFiles) {
|
|
284
|
+
const statResult = await executeCommand({
|
|
285
|
+
command: "stat",
|
|
286
|
+
args: ["-c", "%a %U:%G %n", file],
|
|
287
|
+
toolName: "secrets_env_audit",
|
|
288
|
+
timeout: 5000,
|
|
289
|
+
});
|
|
290
|
+
if (statResult.exitCode === 0 && statResult.stdout.trim()) {
|
|
291
|
+
const perms = statResult.stdout.trim().split(" ")[0];
|
|
292
|
+
let status = "[OK]";
|
|
293
|
+
if (perms !== "600") {
|
|
294
|
+
status = `[WARNING: permissions ${perms}, should be 600]`;
|
|
295
|
+
}
|
|
296
|
+
// Check world-readable
|
|
297
|
+
const worldReadable = (parseInt(perms, 8) & 0o004) !== 0;
|
|
298
|
+
if (worldReadable) {
|
|
299
|
+
status = `[CRITICAL: world-readable (${perms})]`;
|
|
300
|
+
}
|
|
301
|
+
lines.push(` ${statResult.stdout.trim()} ${status}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
lines.push(`No .env files found in /home, /tmp, /var.`);
|
|
307
|
+
}
|
|
308
|
+
lines.push(``);
|
|
309
|
+
// ── Check /proc/*/environ readability ────────────────────────
|
|
310
|
+
lines.push(`── /proc/*/environ ACCESSIBILITY ──`);
|
|
311
|
+
// Check /proc/1/environ permissions
|
|
312
|
+
const proc1Result = await executeCommand({
|
|
313
|
+
command: "ls",
|
|
314
|
+
args: ["-la", "/proc/1/environ"],
|
|
315
|
+
toolName: "secrets_env_audit",
|
|
316
|
+
timeout: 5000,
|
|
317
|
+
});
|
|
318
|
+
// Find readable environ files
|
|
319
|
+
const procFindResult = await executeCommand({
|
|
320
|
+
command: "find",
|
|
321
|
+
args: ["/proc", "-maxdepth", "2", "-name", "environ", "-readable"],
|
|
322
|
+
toolName: "secrets_env_audit",
|
|
323
|
+
timeout: 10000,
|
|
324
|
+
});
|
|
325
|
+
const procOutput = [
|
|
326
|
+
...(proc1Result.exitCode === 0 ? [proc1Result.stdout.trim()] : []),
|
|
327
|
+
...(procFindResult.exitCode === 0 ? procFindResult.stdout.trim().split("\n") : []),
|
|
328
|
+
].filter((l) => l.trim());
|
|
329
|
+
if (procOutput.length > 0) {
|
|
330
|
+
const readableFiles = procOutput.filter((l) => l.startsWith("/proc"));
|
|
331
|
+
if (readableFiles.length > 0) {
|
|
332
|
+
lines.push(`WARNING: ${readableFiles.length} /proc/*/environ file(s) are readable by current user.`);
|
|
333
|
+
lines.push(`These should only be readable by root (permissions 400).`);
|
|
334
|
+
for (const f of readableFiles.slice(0, 10)) {
|
|
335
|
+
lines.push(` ${f}`);
|
|
336
|
+
}
|
|
337
|
+
if (readableFiles.length > 10) {
|
|
338
|
+
lines.push(` ... and ${readableFiles.length - 10} more`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
lines.push(`/proc/*/environ files are properly restricted.`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
lines.push(`/proc/*/environ files are properly restricted.`);
|
|
347
|
+
}
|
|
348
|
+
lines.push(``);
|
|
349
|
+
}
|
|
350
|
+
return { content: [createTextContent(lines.join("\n"))] };
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
354
|
+
// TOOL-021: Sanitize error message to avoid leaking sensitive data
|
|
355
|
+
return { content: [createErrorContent(sanitizeErrorMessage(msg))], isError: true };
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
// ── 3. secrets_ssh_key_sprawl ──────────────────────────────────────────
|
|
359
|
+
server.tool("secrets_ssh_key_sprawl", "Detect SSH key sprawl — find all SSH keys, check their age, permissions, and authorized_keys files", {
|
|
360
|
+
search_path: z
|
|
361
|
+
.string()
|
|
362
|
+
.optional()
|
|
363
|
+
.default("/home")
|
|
364
|
+
.describe("Root path to search for SSH keys"),
|
|
365
|
+
check_authorized_keys: z
|
|
366
|
+
.boolean()
|
|
367
|
+
.optional()
|
|
368
|
+
.default(true)
|
|
369
|
+
.describe("Also audit authorized_keys files"),
|
|
370
|
+
}, async ({ search_path, check_authorized_keys }) => {
|
|
371
|
+
try {
|
|
372
|
+
const lines = [
|
|
373
|
+
`=== SSH Key Sprawl Report ===`,
|
|
374
|
+
`Search Path: ${search_path}`,
|
|
375
|
+
``,
|
|
376
|
+
];
|
|
377
|
+
// ── Find SSH private keys ──────────────────────────────────────
|
|
378
|
+
lines.push(`── SSH PRIVATE KEYS ──`);
|
|
379
|
+
const findResult = await executeCommand({
|
|
380
|
+
command: "find",
|
|
381
|
+
args: [
|
|
382
|
+
search_path,
|
|
383
|
+
"-name", "id_*",
|
|
384
|
+
"-not", "-name", "*.pub",
|
|
385
|
+
"-type", "f",
|
|
386
|
+
],
|
|
387
|
+
toolName: "secrets_ssh_key_sprawl",
|
|
388
|
+
timeout: 30000,
|
|
389
|
+
});
|
|
390
|
+
const keyFiles = findResult.stdout.split("\n").filter((l) => l.trim());
|
|
391
|
+
if (keyFiles.length > 0) {
|
|
392
|
+
lines.push(`Found ${keyFiles.length} SSH private key(s):`);
|
|
393
|
+
lines.push(``);
|
|
394
|
+
for (const keyFile of keyFiles) {
|
|
395
|
+
lines.push(` Key: ${keyFile}`);
|
|
396
|
+
// Check permissions
|
|
397
|
+
const statResult = await executeCommand({
|
|
398
|
+
command: "stat",
|
|
399
|
+
args: ["-c", "%a %U:%G %Y", keyFile],
|
|
400
|
+
toolName: "secrets_ssh_key_sprawl",
|
|
401
|
+
timeout: 5000,
|
|
402
|
+
});
|
|
403
|
+
if (statResult.exitCode === 0 && statResult.stdout.trim()) {
|
|
404
|
+
const parts = statResult.stdout.trim().split(" ");
|
|
405
|
+
const perms = parts[0];
|
|
406
|
+
const owner = parts[1];
|
|
407
|
+
const mtime = parseInt(parts[2], 10);
|
|
408
|
+
// Permission check
|
|
409
|
+
const permStatus = perms === "600" || perms === "400"
|
|
410
|
+
? "[OK]"
|
|
411
|
+
: `[WARNING: permissions ${perms}, should be 600]`;
|
|
412
|
+
lines.push(` Permissions: ${perms} ${permStatus}`);
|
|
413
|
+
lines.push(` Owner: ${owner}`);
|
|
414
|
+
// Age calculation
|
|
415
|
+
const ageMs = Date.now() - mtime * 1000;
|
|
416
|
+
const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
|
|
417
|
+
const ageWarning = ageDays > 365
|
|
418
|
+
? ` [WARNING: key is ${ageDays} days old, consider rotation]`
|
|
419
|
+
: "";
|
|
420
|
+
lines.push(` Age: ${ageDays} days${ageWarning}`);
|
|
421
|
+
}
|
|
422
|
+
// Check key type and bits
|
|
423
|
+
const keygenResult = await executeCommand({
|
|
424
|
+
command: "ssh-keygen",
|
|
425
|
+
args: ["-l", "-f", keyFile],
|
|
426
|
+
toolName: "secrets_ssh_key_sprawl",
|
|
427
|
+
timeout: 5000,
|
|
428
|
+
});
|
|
429
|
+
if (keygenResult.exitCode === 0 && keygenResult.stdout.trim()) {
|
|
430
|
+
lines.push(` Key Info: ${keygenResult.stdout.trim()}`);
|
|
431
|
+
}
|
|
432
|
+
lines.push(``);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
lines.push(`No SSH private keys found.`);
|
|
437
|
+
lines.push(``);
|
|
438
|
+
}
|
|
439
|
+
// ── Check authorized_keys files ────────────────────────────────
|
|
440
|
+
if (check_authorized_keys) {
|
|
441
|
+
lines.push(`── AUTHORIZED_KEYS FILES ──`);
|
|
442
|
+
const akResult = await executeCommand({
|
|
443
|
+
command: "find",
|
|
444
|
+
args: [
|
|
445
|
+
search_path,
|
|
446
|
+
"-name", "authorized_keys",
|
|
447
|
+
"-type", "f",
|
|
448
|
+
],
|
|
449
|
+
toolName: "secrets_ssh_key_sprawl",
|
|
450
|
+
timeout: 30000,
|
|
451
|
+
});
|
|
452
|
+
const akFiles = akResult.stdout.split("\n").filter((l) => l.trim());
|
|
453
|
+
if (akFiles.length > 0) {
|
|
454
|
+
lines.push(`Found ${akFiles.length} authorized_keys file(s):`);
|
|
455
|
+
lines.push(``);
|
|
456
|
+
for (const akFile of akFiles) {
|
|
457
|
+
lines.push(` File: ${akFile}`);
|
|
458
|
+
// Check permissions
|
|
459
|
+
const statResult = await executeCommand({
|
|
460
|
+
command: "stat",
|
|
461
|
+
args: ["-c", "%a %U:%G", akFile],
|
|
462
|
+
toolName: "secrets_ssh_key_sprawl",
|
|
463
|
+
timeout: 5000,
|
|
464
|
+
});
|
|
465
|
+
if (statResult.exitCode === 0 && statResult.stdout.trim()) {
|
|
466
|
+
const parts = statResult.stdout.trim().split(" ");
|
|
467
|
+
const perms = parts[0];
|
|
468
|
+
const owner = parts[1];
|
|
469
|
+
const permStatus = perms === "600" || perms === "644"
|
|
470
|
+
? "[OK]"
|
|
471
|
+
: `[WARNING: permissions ${perms}, should be 600]`;
|
|
472
|
+
lines.push(` Permissions: ${perms} ${permStatus}`);
|
|
473
|
+
lines.push(` Owner: ${owner}`);
|
|
474
|
+
}
|
|
475
|
+
// Count entries and check for command restrictions
|
|
476
|
+
const wcResult = await executeCommand({
|
|
477
|
+
command: "grep",
|
|
478
|
+
args: ["-c", "^[^#]", akFile],
|
|
479
|
+
toolName: "secrets_ssh_key_sprawl",
|
|
480
|
+
timeout: 5000,
|
|
481
|
+
});
|
|
482
|
+
const entryCount = parseInt(wcResult.stdout.trim(), 10) || 0;
|
|
483
|
+
lines.push(` Entries: ${entryCount}`);
|
|
484
|
+
// Check for command restrictions
|
|
485
|
+
const cmdResult = await executeCommand({
|
|
486
|
+
command: "grep",
|
|
487
|
+
args: ["-c", "^command=", akFile],
|
|
488
|
+
toolName: "secrets_ssh_key_sprawl",
|
|
489
|
+
timeout: 5000,
|
|
490
|
+
});
|
|
491
|
+
const cmdRestricted = parseInt(cmdResult.stdout.trim(), 10) || 0;
|
|
492
|
+
if (entryCount > 0) {
|
|
493
|
+
const unrestricted = entryCount - cmdRestricted;
|
|
494
|
+
if (unrestricted > 0) {
|
|
495
|
+
lines.push(` Command-restricted: ${cmdRestricted}/${entryCount} [WARNING: ${unrestricted} unrestricted key(s)]`);
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
lines.push(` Command-restricted: ${cmdRestricted}/${entryCount} [OK: all keys restricted]`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
lines.push(``);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
lines.push(`No authorized_keys files found.`);
|
|
506
|
+
lines.push(``);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// ── Summary ────────────────────────────────────────────────────
|
|
510
|
+
lines.push(`── SUMMARY ──`);
|
|
511
|
+
lines.push(`Private keys found: ${keyFiles.length}`);
|
|
512
|
+
if (check_authorized_keys) {
|
|
513
|
+
const akFindResult = await executeCommand({
|
|
514
|
+
command: "find",
|
|
515
|
+
args: [search_path, "-name", "authorized_keys", "-type", "f"],
|
|
516
|
+
toolName: "secrets_ssh_key_sprawl",
|
|
517
|
+
timeout: 10000,
|
|
518
|
+
});
|
|
519
|
+
const akCount = akFindResult.stdout.trim().split("\n").filter((l) => l.trim()).length;
|
|
520
|
+
lines.push(`Authorized_keys files: ${akCount}`);
|
|
521
|
+
}
|
|
522
|
+
return { content: [createTextContent(lines.join("\n"))] };
|
|
523
|
+
}
|
|
524
|
+
catch (err) {
|
|
525
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
526
|
+
// TOOL-021: Sanitize error message to avoid leaking sensitive data
|
|
527
|
+
return { content: [createErrorContent(sanitizeErrorMessage(msg))], isError: true };
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
// ── 4. scan_git_history ───────────────────────────────────────────────
|
|
531
|
+
server.tool("secrets_git_history_scan", "Scan git repository history for leaked secrets using truffleHog or gitleaks.", {
|
|
532
|
+
repoPath: z.string().describe("Path to git repository"),
|
|
533
|
+
dryRun: z.boolean().optional().default(true).describe("Preview only"),
|
|
534
|
+
}, async ({ repoPath, dryRun }) => {
|
|
535
|
+
try {
|
|
536
|
+
if (dryRun) {
|
|
537
|
+
return {
|
|
538
|
+
content: [formatToolOutput({
|
|
539
|
+
dryRun: true,
|
|
540
|
+
repoPath,
|
|
541
|
+
methods: ["truffleHog git", "gitleaks detect"],
|
|
542
|
+
})],
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
// Try truffleHog
|
|
546
|
+
const thWhich = await executeCommand({ command: "which", args: ["trufflehog"], timeout: 5000 });
|
|
547
|
+
if (thWhich.exitCode === 0) {
|
|
548
|
+
const result = await executeCommand({
|
|
549
|
+
command: "trufflehog",
|
|
550
|
+
args: ["git", `file://${repoPath}`, "--json"],
|
|
551
|
+
timeout: 300000,
|
|
552
|
+
});
|
|
553
|
+
const findings = result.stdout.trim().split("\n").filter(Boolean).map((l) => {
|
|
554
|
+
try {
|
|
555
|
+
return JSON.parse(l);
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
return { raw: l };
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
return {
|
|
562
|
+
content: [formatToolOutput({
|
|
563
|
+
tool: "trufflehog",
|
|
564
|
+
repoPath,
|
|
565
|
+
totalFindings: findings.length,
|
|
566
|
+
findings: findings.slice(0, 50),
|
|
567
|
+
})],
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
// Try gitleaks
|
|
571
|
+
const glWhich = await executeCommand({ command: "which", args: ["gitleaks"], timeout: 5000 });
|
|
572
|
+
if (glWhich.exitCode === 0) {
|
|
573
|
+
const result = await executeCommand({
|
|
574
|
+
command: "gitleaks",
|
|
575
|
+
args: ["detect", "--source", repoPath, "--report-format", "json", "--report-path", "/dev/stdout"],
|
|
576
|
+
timeout: 300000,
|
|
577
|
+
});
|
|
578
|
+
const findings = result.stdout.trim() ? JSON.parse(result.stdout) : [];
|
|
579
|
+
return {
|
|
580
|
+
content: [formatToolOutput({
|
|
581
|
+
tool: "gitleaks",
|
|
582
|
+
repoPath,
|
|
583
|
+
totalFindings: Array.isArray(findings) ? findings.length : 0,
|
|
584
|
+
findings: Array.isArray(findings) ? findings.slice(0, 50) : findings,
|
|
585
|
+
})],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
return { content: [createErrorContent("Neither truffleHog nor gitleaks found. Install one for git history scanning.")], isError: true };
|
|
589
|
+
}
|
|
590
|
+
catch (err) {
|
|
591
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
592
|
+
// TOOL-021: Sanitize error message to avoid leaking sensitive data
|
|
593
|
+
return { content: [createErrorContent(`Git history scan failed: ${sanitizeErrorMessage(msg)}`)], isError: true };
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIEM integration tools for Kali Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool: siem_export (actions: configure_syslog_forward,
|
|
5
|
+
* configure_filebeat, audit_forwarding, test_connectivity)
|
|
6
|
+
*
|
|
7
|
+
* Provides syslog/rsyslog remote forwarding configuration, Filebeat
|
|
8
|
+
* auditing, comprehensive log forwarding audit with CIS benchmark
|
|
9
|
+
* references, and SIEM endpoint connectivity testing.
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
+
/**
|
|
13
|
+
* Validate a SIEM host string (hostname or IP address).
|
|
14
|
+
* Returns true if the host looks reasonable.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateSiemHost(host: string): boolean;
|
|
17
|
+
export declare function registerSiemIntegrationTools(server: McpServer): void;
|
|
18
|
+
//# sourceMappingURL=siem-integration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"siem-integration.d.ts","sourceRoot":"","sources":["../../src/tools/siem-integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4FpE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQtD;AAkhBD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAmTpE"}
|