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,739 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud security tools for Kali Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool: cloud_security (actions: detect_environment, audit_metadata,
|
|
5
|
+
* check_iam_creds, audit_storage, check_imds)
|
|
6
|
+
*
|
|
7
|
+
* Provides cloud environment detection, metadata service auditing, credential
|
|
8
|
+
* exposure checking, storage audit, and IMDS security assessment for AWS/GCP/Azure.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { spawnSafe } from "../core/spawn-safe.js";
|
|
12
|
+
import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
14
|
+
/** Sensitive environment variable names for cloud credentials */
|
|
15
|
+
const CLOUD_CREDENTIAL_ENV_VARS = [
|
|
16
|
+
"AWS_ACCESS_KEY_ID",
|
|
17
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
18
|
+
"AWS_SESSION_TOKEN",
|
|
19
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
20
|
+
"GOOGLE_CLOUD_PROJECT",
|
|
21
|
+
"AZURE_CLIENT_SECRET",
|
|
22
|
+
"AZURE_CLIENT_ID",
|
|
23
|
+
"AZURE_TENANT_ID",
|
|
24
|
+
"AZURE_SUBSCRIPTION_ID",
|
|
25
|
+
];
|
|
26
|
+
/** Known cloud credential file paths (relative to home) */
|
|
27
|
+
const CREDENTIAL_FILE_PATHS = [
|
|
28
|
+
{ provider: "aws", path: "~/.aws/credentials" },
|
|
29
|
+
{ provider: "aws", path: "~/.aws/config" },
|
|
30
|
+
{ provider: "gcp", path: "~/.config/gcloud/application_default_credentials.json" },
|
|
31
|
+
{ provider: "azure", path: "~/.azure/accessTokens.json" },
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Run a command via spawnSafe and collect output as a promise.
|
|
35
|
+
* Handles errors gracefully — returns error info instead of throwing.
|
|
36
|
+
*/
|
|
37
|
+
async function runCommand(command, args, timeoutMs = 30_000) {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
let child;
|
|
40
|
+
try {
|
|
41
|
+
child = spawnSafe(command, args);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45
|
+
resolve({ stdout: "", stderr: msg, exitCode: -1 });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let stdout = "";
|
|
49
|
+
let stderr = "";
|
|
50
|
+
let resolved = false;
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
if (!resolved) {
|
|
53
|
+
resolved = true;
|
|
54
|
+
child.kill("SIGTERM");
|
|
55
|
+
resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
|
|
56
|
+
}
|
|
57
|
+
}, timeoutMs);
|
|
58
|
+
child.stdout?.on("data", (data) => {
|
|
59
|
+
stdout += data.toString();
|
|
60
|
+
});
|
|
61
|
+
child.stderr?.on("data", (data) => {
|
|
62
|
+
stderr += data.toString();
|
|
63
|
+
});
|
|
64
|
+
child.on("close", (code) => {
|
|
65
|
+
if (!resolved) {
|
|
66
|
+
resolved = true;
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
child.on("error", (err) => {
|
|
72
|
+
if (!resolved) {
|
|
73
|
+
resolved = true;
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
resolve({ stdout, stderr: err.message, exitCode: -1 });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// ── Credential masking ─────────────────────────────────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Mask a credential value, showing first 4 chars + asterisks.
|
|
83
|
+
* Returns "(empty)" for empty strings.
|
|
84
|
+
*/
|
|
85
|
+
export function maskCredential(value) {
|
|
86
|
+
if (!value || value.trim().length === 0)
|
|
87
|
+
return "(empty)";
|
|
88
|
+
const trimmed = value.trim();
|
|
89
|
+
if (trimmed.length <= 4)
|
|
90
|
+
return "****";
|
|
91
|
+
return trimmed.substring(0, 4) + "****";
|
|
92
|
+
}
|
|
93
|
+
async function detectEnvironment() {
|
|
94
|
+
const evidence = [];
|
|
95
|
+
let provider = "unknown";
|
|
96
|
+
let confidence = "none";
|
|
97
|
+
// Check DMI/BIOS info
|
|
98
|
+
const sysVendor = await runCommand("cat", ["/sys/class/dmi/id/sys_vendor"], 5_000);
|
|
99
|
+
const productName = await runCommand("cat", ["/sys/class/dmi/id/product_name"], 5_000);
|
|
100
|
+
if (sysVendor.exitCode === 0) {
|
|
101
|
+
const vendor = sysVendor.stdout.trim();
|
|
102
|
+
if (vendor.includes("Amazon")) {
|
|
103
|
+
provider = "aws";
|
|
104
|
+
confidence = "high";
|
|
105
|
+
evidence.push(`DMI sys_vendor: ${vendor}`);
|
|
106
|
+
}
|
|
107
|
+
else if (vendor.includes("Google")) {
|
|
108
|
+
provider = "gcp";
|
|
109
|
+
confidence = "high";
|
|
110
|
+
evidence.push(`DMI sys_vendor: ${vendor}`);
|
|
111
|
+
}
|
|
112
|
+
else if (vendor.includes("Microsoft")) {
|
|
113
|
+
provider = "azure";
|
|
114
|
+
confidence = "high";
|
|
115
|
+
evidence.push(`DMI sys_vendor: ${vendor}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (productName.exitCode === 0) {
|
|
119
|
+
const product = productName.stdout.trim();
|
|
120
|
+
if (product.includes("EC2") || product.includes("ec2")) {
|
|
121
|
+
if (provider === "unknown")
|
|
122
|
+
provider = "aws";
|
|
123
|
+
confidence = "high";
|
|
124
|
+
evidence.push(`DMI product_name: ${product}`);
|
|
125
|
+
}
|
|
126
|
+
else if (product.includes("Google Compute Engine")) {
|
|
127
|
+
if (provider === "unknown")
|
|
128
|
+
provider = "gcp";
|
|
129
|
+
confidence = "high";
|
|
130
|
+
evidence.push(`DMI product_name: ${product}`);
|
|
131
|
+
}
|
|
132
|
+
else if (product.includes("Virtual Machine")) {
|
|
133
|
+
if (provider === "unknown")
|
|
134
|
+
provider = "azure";
|
|
135
|
+
if (confidence !== "high")
|
|
136
|
+
confidence = "medium";
|
|
137
|
+
evidence.push(`DMI product_name: ${product}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Check cloud-init
|
|
141
|
+
if (existsSync("/run/cloud-init/instance-data.json")) {
|
|
142
|
+
evidence.push("cloud-init instance data found at /run/cloud-init/instance-data.json");
|
|
143
|
+
if (confidence === "none")
|
|
144
|
+
confidence = "medium";
|
|
145
|
+
}
|
|
146
|
+
const cloudInitStatus = await runCommand("cloud-init", ["status"], 5_000);
|
|
147
|
+
if (cloudInitStatus.exitCode === 0) {
|
|
148
|
+
evidence.push(`cloud-init status: ${cloudInitStatus.stdout.trim()}`);
|
|
149
|
+
if (confidence === "none")
|
|
150
|
+
confidence = "low";
|
|
151
|
+
}
|
|
152
|
+
// Check generic metadata endpoint
|
|
153
|
+
const metadataCheck = await runCommand("curl", ["-s", "-m", "2", "http://169.254.169.254/"], 5_000);
|
|
154
|
+
if (metadataCheck.exitCode === 0 && metadataCheck.stdout.trim().length > 0) {
|
|
155
|
+
evidence.push("Metadata endpoint 169.254.169.254 is reachable");
|
|
156
|
+
if (confidence === "none")
|
|
157
|
+
confidence = "medium";
|
|
158
|
+
}
|
|
159
|
+
// AWS-specific: Check hypervisor UUID
|
|
160
|
+
const hypervisorUuid = await runCommand("cat", ["/sys/hypervisor/uuid"], 5_000);
|
|
161
|
+
if (hypervisorUuid.exitCode === 0 && hypervisorUuid.stdout.trim().toLowerCase().startsWith("ec2")) {
|
|
162
|
+
provider = "aws";
|
|
163
|
+
confidence = "high";
|
|
164
|
+
evidence.push(`Hypervisor UUID starts with ec2: ${hypervisorUuid.stdout.trim()}`);
|
|
165
|
+
}
|
|
166
|
+
// GCP-specific: Check Google metadata header
|
|
167
|
+
const gcpMetadata = await runCommand("curl", ["-s", "-m", "2", "-H", "Metadata-Flavor: Google", "http://metadata.google.internal/"], 5_000);
|
|
168
|
+
if (gcpMetadata.exitCode === 0 && gcpMetadata.stdout.trim().length > 0) {
|
|
169
|
+
if (provider === "unknown")
|
|
170
|
+
provider = "gcp";
|
|
171
|
+
if (confidence !== "high")
|
|
172
|
+
confidence = "high";
|
|
173
|
+
evidence.push("GCP metadata endpoint responded");
|
|
174
|
+
}
|
|
175
|
+
// Azure-specific: Check Azure metadata
|
|
176
|
+
const azureMetadata = await runCommand("curl", ["-s", "-m", "2", "-H", "Metadata: true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"], 5_000);
|
|
177
|
+
if (azureMetadata.exitCode === 0 && azureMetadata.stdout.includes("compute")) {
|
|
178
|
+
if (provider === "unknown")
|
|
179
|
+
provider = "azure";
|
|
180
|
+
if (confidence !== "high")
|
|
181
|
+
confidence = "high";
|
|
182
|
+
evidence.push("Azure IMDS responded with compute metadata");
|
|
183
|
+
}
|
|
184
|
+
return { provider, confidence, evidence };
|
|
185
|
+
}
|
|
186
|
+
async function auditMetadata(requestedProvider) {
|
|
187
|
+
const result = {
|
|
188
|
+
provider: "unknown",
|
|
189
|
+
imdsVersion: "unknown",
|
|
190
|
+
imdsAccessible: false,
|
|
191
|
+
securityLevel: "moderate",
|
|
192
|
+
exposedCategories: [],
|
|
193
|
+
recommendations: [],
|
|
194
|
+
};
|
|
195
|
+
// Detect provider if auto
|
|
196
|
+
let provider = requestedProvider;
|
|
197
|
+
if (provider === "auto") {
|
|
198
|
+
const detection = await detectEnvironment();
|
|
199
|
+
provider = detection.provider;
|
|
200
|
+
}
|
|
201
|
+
result.provider = provider;
|
|
202
|
+
switch (provider) {
|
|
203
|
+
case "aws": {
|
|
204
|
+
// Check IMDSv1 (unauthenticated)
|
|
205
|
+
const v1Check = await runCommand("curl", ["-s", "-m", "2", "http://169.254.169.254/latest/meta-data/"], 5_000);
|
|
206
|
+
// Check IMDSv2 token endpoint
|
|
207
|
+
const v2TokenCheck = await runCommand("curl", ["-s", "-m", "2", "-X", "PUT", "-H", "X-aws-ec2-metadata-token-ttl-seconds: 21600", "http://169.254.169.254/latest/api/token"], 5_000);
|
|
208
|
+
if (v1Check.exitCode === 0 && v1Check.stdout.trim().length > 0) {
|
|
209
|
+
result.imdsAccessible = true;
|
|
210
|
+
result.imdsVersion = "v1 (unauthenticated)";
|
|
211
|
+
result.securityLevel = "insecure";
|
|
212
|
+
result.exposedCategories = v1Check.stdout.trim().split("\n").filter((l) => l.trim().length > 0);
|
|
213
|
+
result.recommendations.push("CRITICAL: IMDSv1 is accessible — enforce IMDSv2 to require session tokens");
|
|
214
|
+
result.recommendations.push("Set HttpTokens=required on the instance to disable IMDSv1");
|
|
215
|
+
}
|
|
216
|
+
if (v2TokenCheck.exitCode === 0 && v2TokenCheck.stdout.trim().length > 0) {
|
|
217
|
+
result.imdsAccessible = true;
|
|
218
|
+
if (result.imdsVersion === "unknown") {
|
|
219
|
+
result.imdsVersion = "v2 (token-based)";
|
|
220
|
+
result.securityLevel = "secure";
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
result.imdsVersion = "v1 + v2 (both accessible)";
|
|
224
|
+
result.securityLevel = "insecure";
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!result.imdsAccessible) {
|
|
228
|
+
result.imdsVersion = "not accessible";
|
|
229
|
+
result.securityLevel = "secure";
|
|
230
|
+
result.recommendations.push("IMDS not accessible — instance may not be in AWS or IMDS is disabled");
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case "gcp": {
|
|
235
|
+
// GCP requires Metadata-Flavor header — check without header
|
|
236
|
+
const noHeaderCheck = await runCommand("curl", ["-s", "-m", "2", "http://metadata.google.internal/"], 5_000);
|
|
237
|
+
// Check with proper header
|
|
238
|
+
const withHeaderCheck = await runCommand("curl", ["-s", "-m", "2", "-H", "Metadata-Flavor: Google", "http://metadata.google.internal/"], 5_000);
|
|
239
|
+
if (withHeaderCheck.exitCode === 0 && withHeaderCheck.stdout.trim().length > 0) {
|
|
240
|
+
result.imdsAccessible = true;
|
|
241
|
+
result.imdsVersion = "GCP metadata server";
|
|
242
|
+
if (noHeaderCheck.exitCode === 0 && noHeaderCheck.stdout.trim().length > 0 &&
|
|
243
|
+
!noHeaderCheck.stdout.includes("Forbidden")) {
|
|
244
|
+
result.securityLevel = "insecure";
|
|
245
|
+
result.recommendations.push("WARNING: Metadata accessible without Metadata-Flavor header");
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
result.securityLevel = "secure";
|
|
249
|
+
}
|
|
250
|
+
result.exposedCategories = withHeaderCheck.stdout.trim().split("\n").filter((l) => l.trim().length > 0);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
result.imdsVersion = "not accessible";
|
|
254
|
+
result.securityLevel = "secure";
|
|
255
|
+
result.recommendations.push("GCP metadata not accessible — instance may not be in GCP");
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case "azure": {
|
|
260
|
+
// Azure requires Metadata: true header
|
|
261
|
+
const noHeaderCheck = await runCommand("curl", ["-s", "-m", "2", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"], 5_000);
|
|
262
|
+
const withHeaderCheck = await runCommand("curl", ["-s", "-m", "2", "-H", "Metadata: true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"], 5_000);
|
|
263
|
+
if (withHeaderCheck.exitCode === 0 && withHeaderCheck.stdout.includes("compute")) {
|
|
264
|
+
result.imdsAccessible = true;
|
|
265
|
+
result.imdsVersion = "Azure IMDS";
|
|
266
|
+
if (noHeaderCheck.exitCode === 0 && noHeaderCheck.stdout.includes("compute")) {
|
|
267
|
+
result.securityLevel = "insecure";
|
|
268
|
+
result.recommendations.push("WARNING: Azure IMDS accessible without Metadata header");
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
result.securityLevel = "moderate";
|
|
272
|
+
result.recommendations.push("Azure IMDS requires Metadata header — standard security");
|
|
273
|
+
}
|
|
274
|
+
result.exposedCategories = ["compute", "network"];
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
result.imdsVersion = "not accessible";
|
|
278
|
+
result.securityLevel = "secure";
|
|
279
|
+
result.recommendations.push("Azure IMDS not accessible — instance may not be in Azure");
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
default:
|
|
284
|
+
result.recommendations.push("Cloud provider not detected — cannot audit metadata service");
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
async function checkIamCreds(requestedProvider) {
|
|
290
|
+
const result = {
|
|
291
|
+
provider: requestedProvider,
|
|
292
|
+
envVarsFound: [],
|
|
293
|
+
credentialFiles: [],
|
|
294
|
+
processesExposed: [],
|
|
295
|
+
recommendations: [],
|
|
296
|
+
};
|
|
297
|
+
// Check environment variables
|
|
298
|
+
const envResult = await runCommand("env", [], 10_000);
|
|
299
|
+
if (envResult.exitCode === 0) {
|
|
300
|
+
const lines = envResult.stdout.split("\n");
|
|
301
|
+
for (const line of lines) {
|
|
302
|
+
const eqIdx = line.indexOf("=");
|
|
303
|
+
if (eqIdx < 0)
|
|
304
|
+
continue;
|
|
305
|
+
const name = line.substring(0, eqIdx);
|
|
306
|
+
const value = line.substring(eqIdx + 1);
|
|
307
|
+
if (CLOUD_CREDENTIAL_ENV_VARS.includes(name)) {
|
|
308
|
+
result.envVarsFound.push({ name, masked: maskCredential(value) });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (result.envVarsFound.length > 0) {
|
|
313
|
+
result.recommendations.push("Cloud credential environment variables detected — consider using instance roles instead");
|
|
314
|
+
}
|
|
315
|
+
// Check credential files
|
|
316
|
+
const homeResult = await runCommand("sh", ["-c", "echo $HOME"], 5_000);
|
|
317
|
+
const home = homeResult.exitCode === 0 ? homeResult.stdout.trim() : "/root";
|
|
318
|
+
const filesToCheck = requestedProvider === "auto"
|
|
319
|
+
? CREDENTIAL_FILE_PATHS
|
|
320
|
+
: CREDENTIAL_FILE_PATHS.filter((f) => f.provider === requestedProvider);
|
|
321
|
+
for (const fileInfo of filesToCheck) {
|
|
322
|
+
const filePath = fileInfo.path.replace("~", home);
|
|
323
|
+
const fileExists = existsSync(filePath);
|
|
324
|
+
const entry = {
|
|
325
|
+
path: fileInfo.path,
|
|
326
|
+
exists: fileExists,
|
|
327
|
+
};
|
|
328
|
+
if (fileExists) {
|
|
329
|
+
// Check file permissions
|
|
330
|
+
const statResult = await runCommand("stat", ["-c", "%a", filePath], 5_000);
|
|
331
|
+
if (statResult.exitCode === 0) {
|
|
332
|
+
const perms = statResult.stdout.trim();
|
|
333
|
+
entry.permissions = perms;
|
|
334
|
+
// Check if overly permissive (readable by group or others)
|
|
335
|
+
const numPerms = parseInt(perms, 8);
|
|
336
|
+
if ((numPerms & 0o077) !== 0) {
|
|
337
|
+
entry.permWarning = `File permissions ${perms} are too open — should be 600 or stricter`;
|
|
338
|
+
result.recommendations.push(`Fix permissions on ${fileInfo.path}: chmod 600 ${filePath}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
result.credentialFiles.push(entry);
|
|
343
|
+
}
|
|
344
|
+
// Scan /proc/*/environ for cloud credential env vars
|
|
345
|
+
const procScan = await runCommand("grep", ["-rl", "AWS_ACCESS_KEY_ID\\|AWS_SECRET_ACCESS_KEY\\|GOOGLE_APPLICATION_CREDENTIALS\\|AZURE_CLIENT_SECRET", "/proc/*/environ"], 10_000);
|
|
346
|
+
if (procScan.exitCode === 0 && procScan.stdout.trim().length > 0) {
|
|
347
|
+
const procs = procScan.stdout.trim().split("\n")
|
|
348
|
+
.map((p) => p.trim())
|
|
349
|
+
.filter((p) => p.length > 0)
|
|
350
|
+
.slice(0, 20); // limit output
|
|
351
|
+
result.processesExposed = procs;
|
|
352
|
+
result.recommendations.push(`Found ${procs.length} process(es) with cloud credentials in environment — review for least privilege`);
|
|
353
|
+
}
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
async function auditStorage(requestedProvider) {
|
|
357
|
+
const result = {
|
|
358
|
+
provider: requestedProvider,
|
|
359
|
+
cliAvailable: { aws: false, gsutil: false, az: false },
|
|
360
|
+
accessibleStorage: [],
|
|
361
|
+
mountPoints: [],
|
|
362
|
+
recommendations: [],
|
|
363
|
+
};
|
|
364
|
+
// Check for CLI tools
|
|
365
|
+
const awsCheck = await runCommand("which", ["aws"], 5_000);
|
|
366
|
+
result.cliAvailable.aws = awsCheck.exitCode === 0;
|
|
367
|
+
const gsutilCheck = await runCommand("which", ["gsutil"], 5_000);
|
|
368
|
+
result.cliAvailable.gsutil = gsutilCheck.exitCode === 0;
|
|
369
|
+
const azCheck = await runCommand("which", ["az"], 5_000);
|
|
370
|
+
result.cliAvailable.az = azCheck.exitCode === 0;
|
|
371
|
+
const shouldCheckAws = requestedProvider === "auto" || requestedProvider === "aws";
|
|
372
|
+
const shouldCheckGcp = requestedProvider === "auto" || requestedProvider === "gcp";
|
|
373
|
+
const shouldCheckAzure = requestedProvider === "auto" || requestedProvider === "azure";
|
|
374
|
+
// AWS: List S3 buckets
|
|
375
|
+
if (shouldCheckAws && result.cliAvailable.aws) {
|
|
376
|
+
const s3ls = await runCommand("aws", ["s3", "ls"], 15_000);
|
|
377
|
+
if (s3ls.exitCode === 0 && s3ls.stdout.trim().length > 0) {
|
|
378
|
+
const buckets = s3ls.stdout.trim().split("\n").filter((l) => l.trim().length > 0);
|
|
379
|
+
result.accessibleStorage.push(...buckets.map((b) => `[AWS S3] ${b.trim()}`));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else if (shouldCheckAws && !result.cliAvailable.aws) {
|
|
383
|
+
result.recommendations.push("AWS CLI not installed — cannot audit S3 storage");
|
|
384
|
+
}
|
|
385
|
+
// GCP: List buckets
|
|
386
|
+
if (shouldCheckGcp && result.cliAvailable.gsutil) {
|
|
387
|
+
const gsls = await runCommand("gsutil", ["ls"], 15_000);
|
|
388
|
+
if (gsls.exitCode === 0 && gsls.stdout.trim().length > 0) {
|
|
389
|
+
const buckets = gsls.stdout.trim().split("\n").filter((l) => l.trim().length > 0);
|
|
390
|
+
result.accessibleStorage.push(...buckets.map((b) => `[GCP GCS] ${b.trim()}`));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (shouldCheckGcp && !result.cliAvailable.gsutil) {
|
|
394
|
+
result.recommendations.push("gsutil not installed — cannot audit GCS storage");
|
|
395
|
+
}
|
|
396
|
+
// Azure: List storage accounts
|
|
397
|
+
if (shouldCheckAzure && result.cliAvailable.az) {
|
|
398
|
+
const azStorage = await runCommand("az", ["storage", "account", "list"], 15_000);
|
|
399
|
+
if (azStorage.exitCode === 0 && azStorage.stdout.trim().length > 0) {
|
|
400
|
+
result.accessibleStorage.push(`[Azure] ${azStorage.stdout.trim().substring(0, 500)}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else if (shouldCheckAzure && !result.cliAvailable.az) {
|
|
404
|
+
result.recommendations.push("Azure CLI not installed — cannot audit Azure storage");
|
|
405
|
+
}
|
|
406
|
+
// Check for mounted cloud storage (NFS, FUSE)
|
|
407
|
+
const mountResult = await runCommand("mount", [], 10_000);
|
|
408
|
+
if (mountResult.exitCode === 0) {
|
|
409
|
+
const lines = mountResult.stdout.split("\n");
|
|
410
|
+
for (const line of lines) {
|
|
411
|
+
const lower = line.toLowerCase();
|
|
412
|
+
if (lower.includes("fuse") || lower.includes("nfs") ||
|
|
413
|
+
lower.includes("s3fs") || lower.includes("gcsfuse") ||
|
|
414
|
+
lower.includes("blobfuse") || lower.includes("cifs")) {
|
|
415
|
+
result.mountPoints.push(line.trim());
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (result.accessibleStorage.length === 0 && result.mountPoints.length === 0) {
|
|
420
|
+
result.recommendations.push("No cloud storage accessible from this instance");
|
|
421
|
+
}
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
async function checkImds() {
|
|
425
|
+
const result = {
|
|
426
|
+
v1Accessible: false,
|
|
427
|
+
v2Accessible: false,
|
|
428
|
+
v2TokenWorks: false,
|
|
429
|
+
iptablesBlocked: false,
|
|
430
|
+
iptablesRules: [],
|
|
431
|
+
hopLimit: "unknown",
|
|
432
|
+
severity: "INFO",
|
|
433
|
+
securityScore: 100,
|
|
434
|
+
recommendations: [],
|
|
435
|
+
};
|
|
436
|
+
// Test IMDSv1 (unauthenticated)
|
|
437
|
+
const v1Check = await runCommand("curl", ["-s", "-m", "2", "http://169.254.169.254/latest/meta-data/"], 5_000);
|
|
438
|
+
if (v1Check.exitCode === 0 && v1Check.stdout.trim().length > 0) {
|
|
439
|
+
result.v1Accessible = true;
|
|
440
|
+
result.severity = "CRITICAL";
|
|
441
|
+
result.securityScore -= 50;
|
|
442
|
+
result.recommendations.push("CRITICAL: IMDSv1 is accessible without authentication — enforce IMDSv2");
|
|
443
|
+
}
|
|
444
|
+
// Test IMDSv2 token endpoint
|
|
445
|
+
const v2TokenCheck = await runCommand("curl", ["-s", "-m", "2", "-X", "PUT", "-H", "X-aws-ec2-metadata-token-ttl-seconds: 21600", "http://169.254.169.254/latest/api/token"], 5_000);
|
|
446
|
+
if (v2TokenCheck.exitCode === 0 && v2TokenCheck.stdout.trim().length > 0) {
|
|
447
|
+
result.v2TokenWorks = true;
|
|
448
|
+
result.v2Accessible = true;
|
|
449
|
+
if (!result.v1Accessible) {
|
|
450
|
+
result.severity = "LOW";
|
|
451
|
+
result.securityScore = Math.max(result.securityScore, 80);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Check iptables for IMDS blocking rules
|
|
455
|
+
const iptablesResult = await runCommand("iptables", ["-L", "-n"], 10_000);
|
|
456
|
+
if (iptablesResult.exitCode === 0) {
|
|
457
|
+
const lines = iptablesResult.stdout.split("\n");
|
|
458
|
+
for (const line of lines) {
|
|
459
|
+
if (line.includes("169.254.169.254")) {
|
|
460
|
+
result.iptablesRules.push(line.trim());
|
|
461
|
+
if (line.includes("DROP") || line.includes("REJECT")) {
|
|
462
|
+
result.iptablesBlocked = true;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (!result.iptablesBlocked && (result.v1Accessible || result.v2Accessible)) {
|
|
468
|
+
result.securityScore -= 20;
|
|
469
|
+
if (result.severity !== "CRITICAL") {
|
|
470
|
+
result.severity = "MEDIUM";
|
|
471
|
+
}
|
|
472
|
+
result.recommendations.push("No iptables rules blocking IMDS from non-root users — consider adding restrictions");
|
|
473
|
+
}
|
|
474
|
+
// Check hop limit via curl with TTL
|
|
475
|
+
const hopCheck = await runCommand("curl", ["-s", "-m", "2", "--max-time", "2", "http://169.254.169.254/latest/meta-data/"], 5_000);
|
|
476
|
+
if (hopCheck.exitCode === 0 && hopCheck.stdout.trim().length > 0) {
|
|
477
|
+
result.hopLimit = "reachable (default)";
|
|
478
|
+
}
|
|
479
|
+
else if (!result.v1Accessible && !result.v2Accessible) {
|
|
480
|
+
result.hopLimit = "not reachable (IMDS may be disabled or restricted)";
|
|
481
|
+
}
|
|
482
|
+
// If nothing is accessible, it's fine
|
|
483
|
+
if (!result.v1Accessible && !result.v2Accessible) {
|
|
484
|
+
result.severity = "INFO";
|
|
485
|
+
result.securityScore = 100;
|
|
486
|
+
result.recommendations.push("IMDS not accessible — instance may not be in a cloud environment or IMDS is disabled");
|
|
487
|
+
}
|
|
488
|
+
result.securityScore = Math.max(0, Math.min(100, result.securityScore));
|
|
489
|
+
return result;
|
|
490
|
+
}
|
|
491
|
+
// ── Registration entry point ───────────────────────────────────────────────
|
|
492
|
+
export function registerCloudSecurityTools(server) {
|
|
493
|
+
server.tool("cloud_security", "Cloud security: detect cloud environment, audit metadata services, check IAM credentials, audit storage, and test IMDS security for AWS/GCP/Azure.", {
|
|
494
|
+
action: z
|
|
495
|
+
.enum(["detect_environment", "audit_metadata", "check_iam_creds", "audit_storage", "check_imds"])
|
|
496
|
+
.describe("Action: detect_environment=detect cloud provider, audit_metadata=audit IMDS configuration, check_iam_creds=check for exposed credentials, audit_storage=audit cloud storage, check_imds=test IMDS security"),
|
|
497
|
+
provider: z
|
|
498
|
+
.enum(["aws", "gcp", "azure", "auto"])
|
|
499
|
+
.optional()
|
|
500
|
+
.default("auto")
|
|
501
|
+
.describe("Cloud provider to target (default auto-detect)"),
|
|
502
|
+
output_format: z
|
|
503
|
+
.enum(["text", "json"])
|
|
504
|
+
.optional()
|
|
505
|
+
.default("text")
|
|
506
|
+
.describe("Output format (default text)"),
|
|
507
|
+
}, async (params) => {
|
|
508
|
+
const { action } = params;
|
|
509
|
+
switch (action) {
|
|
510
|
+
// ── detect_environment ────────────────────────────────────────────
|
|
511
|
+
case "detect_environment": {
|
|
512
|
+
try {
|
|
513
|
+
const detection = await detectEnvironment();
|
|
514
|
+
const output = {
|
|
515
|
+
action: "detect_environment",
|
|
516
|
+
provider: detection.provider,
|
|
517
|
+
confidence: detection.confidence,
|
|
518
|
+
evidenceCount: detection.evidence.length,
|
|
519
|
+
evidence: detection.evidence,
|
|
520
|
+
isCloud: detection.provider !== "unknown",
|
|
521
|
+
};
|
|
522
|
+
if (params.output_format === "json") {
|
|
523
|
+
return { content: [formatToolOutput(output)] };
|
|
524
|
+
}
|
|
525
|
+
if (detection.provider === "unknown") {
|
|
526
|
+
return {
|
|
527
|
+
content: [createTextContent("Cloud Security — Environment Detection\n\n" +
|
|
528
|
+
"Result: Not running in a detected cloud environment\n" +
|
|
529
|
+
`Checks performed: ${detection.evidence.length > 0 ? detection.evidence.length : "standard suite"}\n` +
|
|
530
|
+
(detection.evidence.length > 0 ? `\nEvidence:\n${detection.evidence.map((e) => ` • ${e}`).join("\n")}\n` : "") +
|
|
531
|
+
"\nThis system does not appear to be running in AWS, GCP, or Azure.\n")],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
content: [createTextContent("Cloud Security — Environment Detection\n\n" +
|
|
536
|
+
`Provider: ${detection.provider.toUpperCase()}\n` +
|
|
537
|
+
`Confidence: ${detection.confidence}\n` +
|
|
538
|
+
`Evidence (${detection.evidence.length}):\n` +
|
|
539
|
+
detection.evidence.map((e) => ` • ${e}`).join("\n") + "\n")],
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
544
|
+
return { content: [createErrorContent(`detect_environment failed: ${msg}`)], isError: true };
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// ── audit_metadata ───────────────────────────────────────────────
|
|
548
|
+
case "audit_metadata": {
|
|
549
|
+
try {
|
|
550
|
+
const provider = params.provider ?? "auto";
|
|
551
|
+
const audit = await auditMetadata(provider);
|
|
552
|
+
const output = {
|
|
553
|
+
action: "audit_metadata",
|
|
554
|
+
provider: audit.provider,
|
|
555
|
+
imdsVersion: audit.imdsVersion,
|
|
556
|
+
imdsAccessible: audit.imdsAccessible,
|
|
557
|
+
securityLevel: audit.securityLevel,
|
|
558
|
+
exposedCategories: audit.exposedCategories,
|
|
559
|
+
recommendations: audit.recommendations,
|
|
560
|
+
};
|
|
561
|
+
if (params.output_format === "json") {
|
|
562
|
+
return { content: [formatToolOutput(output)] };
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
content: [createTextContent("Cloud Security — Metadata Audit\n\n" +
|
|
566
|
+
`Provider: ${audit.provider}\n` +
|
|
567
|
+
`IMDS Version: ${audit.imdsVersion}\n` +
|
|
568
|
+
`IMDS Accessible: ${audit.imdsAccessible ? "YES" : "no"}\n` +
|
|
569
|
+
`Security Level: ${audit.securityLevel.toUpperCase()}\n` +
|
|
570
|
+
(audit.exposedCategories.length > 0
|
|
571
|
+
? `\nExposed Categories:\n${audit.exposedCategories.map((c) => ` • ${c}`).join("\n")}\n`
|
|
572
|
+
: "") +
|
|
573
|
+
(audit.recommendations.length > 0
|
|
574
|
+
? `\nRecommendations:\n${audit.recommendations.map((r) => ` • ${r}`).join("\n")}\n`
|
|
575
|
+
: ""))],
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
catch (err) {
|
|
579
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
580
|
+
return { content: [createErrorContent(`audit_metadata failed: ${msg}`)], isError: true };
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// ── check_iam_creds ──────────────────────────────────────────────
|
|
584
|
+
case "check_iam_creds": {
|
|
585
|
+
try {
|
|
586
|
+
const provider = params.provider ?? "auto";
|
|
587
|
+
const creds = await checkIamCreds(provider);
|
|
588
|
+
const output = {
|
|
589
|
+
action: "check_iam_creds",
|
|
590
|
+
provider: creds.provider,
|
|
591
|
+
envVarsFound: creds.envVarsFound,
|
|
592
|
+
credentialFiles: creds.credentialFiles,
|
|
593
|
+
processesExposed: creds.processesExposed,
|
|
594
|
+
totalFindings: creds.envVarsFound.length + creds.credentialFiles.filter((f) => f.exists).length + creds.processesExposed.length,
|
|
595
|
+
recommendations: creds.recommendations,
|
|
596
|
+
};
|
|
597
|
+
if (params.output_format === "json") {
|
|
598
|
+
return { content: [formatToolOutput(output)] };
|
|
599
|
+
}
|
|
600
|
+
let text = "Cloud Security — IAM Credential Check\n\n";
|
|
601
|
+
if (creds.envVarsFound.length > 0) {
|
|
602
|
+
text += "Environment Variables with Cloud Credentials:\n";
|
|
603
|
+
for (const env of creds.envVarsFound) {
|
|
604
|
+
text += ` • ${env.name} = ${env.masked}\n`;
|
|
605
|
+
}
|
|
606
|
+
text += "\n";
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
text += "Environment Variables: No cloud credentials found in environment\n\n";
|
|
610
|
+
}
|
|
611
|
+
text += "Credential Files:\n";
|
|
612
|
+
for (const file of creds.credentialFiles) {
|
|
613
|
+
const status = file.exists
|
|
614
|
+
? `EXISTS (permissions: ${file.permissions || "unknown"})${file.permWarning ? ` ⚠ ${file.permWarning}` : ""}`
|
|
615
|
+
: "not found";
|
|
616
|
+
text += ` • ${file.path}: ${status}\n`;
|
|
617
|
+
}
|
|
618
|
+
if (creds.processesExposed.length > 0) {
|
|
619
|
+
text += `\nProcesses with Cloud Credentials: ${creds.processesExposed.length}\n`;
|
|
620
|
+
for (const proc of creds.processesExposed.slice(0, 10)) {
|
|
621
|
+
text += ` • ${proc}\n`;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (creds.recommendations.length > 0) {
|
|
625
|
+
text += `\nRecommendations:\n`;
|
|
626
|
+
for (const rec of creds.recommendations) {
|
|
627
|
+
text += ` • ${rec}\n`;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return { content: [createTextContent(text)] };
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
634
|
+
return { content: [createErrorContent(`check_iam_creds failed: ${msg}`)], isError: true };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// ── audit_storage ────────────────────────────────────────────────
|
|
638
|
+
case "audit_storage": {
|
|
639
|
+
try {
|
|
640
|
+
const provider = params.provider ?? "auto";
|
|
641
|
+
const storage = await auditStorage(provider);
|
|
642
|
+
const output = {
|
|
643
|
+
action: "audit_storage",
|
|
644
|
+
provider: storage.provider,
|
|
645
|
+
cliAvailable: storage.cliAvailable,
|
|
646
|
+
accessibleStorage: storage.accessibleStorage,
|
|
647
|
+
mountPoints: storage.mountPoints,
|
|
648
|
+
totalAccessible: storage.accessibleStorage.length + storage.mountPoints.length,
|
|
649
|
+
recommendations: storage.recommendations,
|
|
650
|
+
};
|
|
651
|
+
if (params.output_format === "json") {
|
|
652
|
+
return { content: [formatToolOutput(output)] };
|
|
653
|
+
}
|
|
654
|
+
let text = "Cloud Security — Storage Audit\n\n";
|
|
655
|
+
text += "CLI Tools:\n";
|
|
656
|
+
text += ` • AWS CLI: ${storage.cliAvailable.aws ? "installed" : "not found"}\n`;
|
|
657
|
+
text += ` • gsutil: ${storage.cliAvailable.gsutil ? "installed" : "not found"}\n`;
|
|
658
|
+
text += ` • Azure CLI: ${storage.cliAvailable.az ? "installed" : "not found"}\n\n`;
|
|
659
|
+
if (storage.accessibleStorage.length > 0) {
|
|
660
|
+
text += `Accessible Storage (${storage.accessibleStorage.length}):\n`;
|
|
661
|
+
for (const s of storage.accessibleStorage) {
|
|
662
|
+
text += ` • ${s}\n`;
|
|
663
|
+
}
|
|
664
|
+
text += "\n";
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
text += "Accessible Storage: none found\n\n";
|
|
668
|
+
}
|
|
669
|
+
if (storage.mountPoints.length > 0) {
|
|
670
|
+
text += `Cloud Mount Points (${storage.mountPoints.length}):\n`;
|
|
671
|
+
for (const m of storage.mountPoints) {
|
|
672
|
+
text += ` • ${m}\n`;
|
|
673
|
+
}
|
|
674
|
+
text += "\n";
|
|
675
|
+
}
|
|
676
|
+
if (storage.recommendations.length > 0) {
|
|
677
|
+
text += "Recommendations:\n";
|
|
678
|
+
for (const rec of storage.recommendations) {
|
|
679
|
+
text += ` • ${rec}\n`;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return { content: [createTextContent(text)] };
|
|
683
|
+
}
|
|
684
|
+
catch (err) {
|
|
685
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
686
|
+
return { content: [createErrorContent(`audit_storage failed: ${msg}`)], isError: true };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// ── check_imds ───────────────────────────────────────────────────
|
|
690
|
+
case "check_imds": {
|
|
691
|
+
try {
|
|
692
|
+
const imds = await checkImds();
|
|
693
|
+
const output = {
|
|
694
|
+
action: "check_imds",
|
|
695
|
+
v1Accessible: imds.v1Accessible,
|
|
696
|
+
v2Accessible: imds.v2Accessible,
|
|
697
|
+
v2TokenWorks: imds.v2TokenWorks,
|
|
698
|
+
iptablesBlocked: imds.iptablesBlocked,
|
|
699
|
+
iptablesRules: imds.iptablesRules,
|
|
700
|
+
hopLimit: imds.hopLimit,
|
|
701
|
+
severity: imds.severity,
|
|
702
|
+
securityScore: imds.securityScore,
|
|
703
|
+
recommendations: imds.recommendations,
|
|
704
|
+
};
|
|
705
|
+
if (params.output_format === "json") {
|
|
706
|
+
return { content: [formatToolOutput(output)] };
|
|
707
|
+
}
|
|
708
|
+
let text = "Cloud Security — IMDS Security Check\n\n";
|
|
709
|
+
text += `IMDSv1 (unauthenticated): ${imds.v1Accessible ? "ACCESSIBLE ⚠" : "not accessible ✓"}\n`;
|
|
710
|
+
text += `IMDSv2 (token-based): ${imds.v2Accessible ? "accessible" : "not accessible"}\n`;
|
|
711
|
+
text += `IMDSv2 Token Endpoint: ${imds.v2TokenWorks ? "working" : "not working"}\n`;
|
|
712
|
+
text += `Iptables IMDS Rules: ${imds.iptablesBlocked ? "BLOCKED ✓" : imds.iptablesRules.length > 0 ? "rules found" : "no rules"}\n`;
|
|
713
|
+
text += `Hop Limit: ${imds.hopLimit}\n`;
|
|
714
|
+
text += `\nSeverity: ${imds.severity}\n`;
|
|
715
|
+
text += `Security Score: ${imds.securityScore}/100\n`;
|
|
716
|
+
if (imds.iptablesRules.length > 0) {
|
|
717
|
+
text += `\nIptables Rules for 169.254.169.254:\n`;
|
|
718
|
+
for (const rule of imds.iptablesRules) {
|
|
719
|
+
text += ` • ${rule}\n`;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (imds.recommendations.length > 0) {
|
|
723
|
+
text += `\nRecommendations:\n`;
|
|
724
|
+
for (const rec of imds.recommendations) {
|
|
725
|
+
text += ` • ${rec}\n`;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return { content: [createTextContent(text)] };
|
|
729
|
+
}
|
|
730
|
+
catch (err) {
|
|
731
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
732
|
+
return { content: [createErrorContent(`check_imds failed: ${msg}`)], isError: true };
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
default:
|
|
736
|
+
return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
}
|