mcp-server-kubernetes 4.1.4 → 4.1.5
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.
|
@@ -21,6 +21,23 @@ export declare function assertNoDangerousFlags(flags?: Record<string, unknown>,
|
|
|
21
21
|
* flag unless ALLOW_KUBECTL_UNSAFE_FLAGS=true.
|
|
22
22
|
*/
|
|
23
23
|
export declare function assertSafeArgv(args: readonly string[]): void;
|
|
24
|
+
/**
|
|
25
|
+
* Reject a caller-supplied value that a tool is about to place in a *positional*
|
|
26
|
+
* argv slot (a release name, a chart reference, a namespace) when it is shaped
|
|
27
|
+
* like a command-line flag.
|
|
28
|
+
*
|
|
29
|
+
* assertSafeArgv is a deny-list: it can only refuse the flags it knows about,
|
|
30
|
+
* so every dangerous flag added upstream is exploitable through any positional
|
|
31
|
+
* slot until the list is extended. This guard closes the injection point
|
|
32
|
+
* instead of chasing the flags: pflag treats any token starting with "-" as a
|
|
33
|
+
* flag no matter where it sits, and none of these operands is ever legitimately
|
|
34
|
+
* flag-shaped (helm release names and namespaces are RFC 1123 names; chart
|
|
35
|
+
* references are repo/name pairs, paths or URLs). Refusing a leading "-" here
|
|
36
|
+
* means caller data can never occupy a slot where it would be parsed as a flag.
|
|
37
|
+
*
|
|
38
|
+
* Honours the same ALLOW_KUBECTL_UNSAFE_FLAGS escape hatch as the flag guard.
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertNotFlagLike(value: string | undefined, field: string): void;
|
|
24
41
|
/**
|
|
25
42
|
* Drop-in replacement for child_process.execFileSync that scans the argv for
|
|
26
43
|
* credential/target-redirecting flags before executing. Tool files import this
|
|
@@ -69,7 +69,8 @@ const SHORT_VALUE_FLAGS = new Set([
|
|
|
69
69
|
"v", // --v (log level)
|
|
70
70
|
]);
|
|
71
71
|
// helm exposes the same exfiltration surface as kubectl, but under "kube-"
|
|
72
|
-
// prefixed flag names (e.g. --kube-apiserver instead of --server)
|
|
72
|
+
// prefixed flag names (e.g. --kube-apiserver instead of --server), plus a few
|
|
73
|
+
// helm-only flags that run or overwrite things on the MCP server host. We add
|
|
73
74
|
// those here so the argv-level guard covers helm invocations too. Context
|
|
74
75
|
// selection flags (--context / --kube-context) are intentionally omitted:
|
|
75
76
|
// they can only select a cluster already present in the loaded kubeconfig,
|
|
@@ -83,6 +84,16 @@ const HELM_DANGEROUS_FLAGS = new Set([
|
|
|
83
84
|
"kube-as-group",
|
|
84
85
|
"kube-tls-server-name",
|
|
85
86
|
"kube-insecure-skip-tls-verify",
|
|
87
|
+
// Runs a binary on the MCP server host: helm 3.x resolves --post-renderer to
|
|
88
|
+
// an executable path (or a $PATH name) and runs it over the rendered
|
|
89
|
+
// manifests. Nothing about installing a chart otherwise gives host-side
|
|
90
|
+
// execution, so this is never a flag a tool should emit.
|
|
91
|
+
"post-renderer",
|
|
92
|
+
"post-renderer-args",
|
|
93
|
+
// Writes to attacker-chosen filesystem paths: helm rewrites these files as
|
|
94
|
+
// part of "repo add" / registry login.
|
|
95
|
+
"repository-config",
|
|
96
|
+
"registry-config",
|
|
86
97
|
]);
|
|
87
98
|
// Flag names that are dangerous when they appear anywhere in a fully
|
|
88
99
|
// constructed argv (positional slots included), regardless of which tool
|
|
@@ -159,10 +170,10 @@ function isDangerousFlagName(rawName, fromArgs) {
|
|
|
159
170
|
}
|
|
160
171
|
function reject(flag) {
|
|
161
172
|
throw new McpError(ErrorCode.InvalidParams, `Refusing to run kubectl with flag "${flag}": this flag can redirect ` +
|
|
162
|
-
`kubectl to a different API server
|
|
163
|
-
`
|
|
164
|
-
`genuinely need this flag, set
|
|
165
|
-
`server environment.`);
|
|
173
|
+
`kubectl/helm to a different API server, substitute credentials, or ` +
|
|
174
|
+
`act on the MCP server's own host, which would allow exfiltration of ` +
|
|
175
|
+
`the operator's bearer token. If you genuinely need this flag, set ` +
|
|
176
|
+
`ALLOW_KUBECTL_UNSAFE_FLAGS=true in the server environment.`);
|
|
166
177
|
}
|
|
167
178
|
/**
|
|
168
179
|
* Validate user-supplied kubectl flags and args. Throws an McpError if any
|
|
@@ -222,6 +233,32 @@ export function assertSafeArgv(args) {
|
|
|
222
233
|
reject(tok);
|
|
223
234
|
}
|
|
224
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Reject a caller-supplied value that a tool is about to place in a *positional*
|
|
238
|
+
* argv slot (a release name, a chart reference, a namespace) when it is shaped
|
|
239
|
+
* like a command-line flag.
|
|
240
|
+
*
|
|
241
|
+
* assertSafeArgv is a deny-list: it can only refuse the flags it knows about,
|
|
242
|
+
* so every dangerous flag added upstream is exploitable through any positional
|
|
243
|
+
* slot until the list is extended. This guard closes the injection point
|
|
244
|
+
* instead of chasing the flags: pflag treats any token starting with "-" as a
|
|
245
|
+
* flag no matter where it sits, and none of these operands is ever legitimately
|
|
246
|
+
* flag-shaped (helm release names and namespaces are RFC 1123 names; chart
|
|
247
|
+
* references are repo/name pairs, paths or URLs). Refusing a leading "-" here
|
|
248
|
+
* means caller data can never occupy a slot where it would be parsed as a flag.
|
|
249
|
+
*
|
|
250
|
+
* Honours the same ALLOW_KUBECTL_UNSAFE_FLAGS escape hatch as the flag guard.
|
|
251
|
+
*/
|
|
252
|
+
export function assertNotFlagLike(value, field) {
|
|
253
|
+
if (isUnsafeFlagsAllowed())
|
|
254
|
+
return;
|
|
255
|
+
if (typeof value !== "string" || !value.startsWith("-"))
|
|
256
|
+
return;
|
|
257
|
+
throw new McpError(ErrorCode.InvalidParams, `Refusing to run helm with ${field}="${value}": a value starting with ` +
|
|
258
|
+
`"-" is parsed as a command-line flag rather than as the ${field}, ` +
|
|
259
|
+
`which would let the caller inject arbitrary helm flags. Provide a ` +
|
|
260
|
+
`${field} that does not begin with "-".`);
|
|
261
|
+
}
|
|
225
262
|
/**
|
|
226
263
|
* Drop-in replacement for child_process.execFileSync that scans the argv for
|
|
227
264
|
* credential/target-redirecting flags before executing. Tool files import this
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Supports local chart paths, remote repositories, and custom values.
|
|
6
6
|
*/
|
|
7
7
|
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
-
import { execFileSyncSafe } from "../security/kubectl-flags.js";
|
|
8
|
+
import { assertNotFlagLike, execFileSyncSafe, } from "../security/kubectl-flags.js";
|
|
9
9
|
import { writeFileSync, unlinkSync } from "fs";
|
|
10
10
|
import { dump } from "js-yaml";
|
|
11
11
|
import { isRemoteTransport } from "../security/transport.js";
|
|
@@ -166,6 +166,19 @@ const rejectValuesFileOnRemoteTransport = (valuesFile) => {
|
|
|
166
166
|
throw new McpError(ErrorCode.InvalidRequest, "The 'valuesFile' parameter reads a file from the MCP server's filesystem and is disabled on remote (SSE/Streamable HTTP) transports. Pass the values inline via 'values' instead.");
|
|
167
167
|
}
|
|
168
168
|
};
|
|
169
|
+
// The release name, chart reference, namespace and repo URL are all placed in
|
|
170
|
+
// positional argv slots ("helm install <name> <chart>", "helm repo add
|
|
171
|
+
// <name> <url>", "kubectl create namespace <ns>"). pflag parses any token
|
|
172
|
+
// beginning with "-" as a flag regardless of position, so a caller-supplied
|
|
173
|
+
// value such as "--post-renderer=/tmp/x.sh" would become a helm flag instead
|
|
174
|
+
// of an operand. Reject flag-shaped operands up front rather than relying on
|
|
175
|
+
// the argv deny-list to know every dangerous helm flag.
|
|
176
|
+
const assertOperandsNotFlagLike = (params) => {
|
|
177
|
+
assertNotFlagLike(params.name, "release name");
|
|
178
|
+
assertNotFlagLike(params.chart, "chart");
|
|
179
|
+
assertNotFlagLike(params.namespace, "namespace");
|
|
180
|
+
assertNotFlagLike(params.repo, "repo");
|
|
181
|
+
};
|
|
169
182
|
/**
|
|
170
183
|
* Install a Helm chart using template mode (helm template + kubectl apply).
|
|
171
184
|
* This mode bypasses authentication issues and kubeconfig API version mismatches.
|
|
@@ -274,6 +287,7 @@ async function installHelmChartTemplate(params) {
|
|
|
274
287
|
*/
|
|
275
288
|
export async function installHelmChart(params) {
|
|
276
289
|
rejectValuesFileOnRemoteTransport(params.valuesFile);
|
|
290
|
+
assertOperandsNotFlagLike(params);
|
|
277
291
|
// Use template mode if requested
|
|
278
292
|
if (params.useTemplate) {
|
|
279
293
|
return installHelmChartTemplate(params);
|
|
@@ -349,6 +363,7 @@ export async function installHelmChart(params) {
|
|
|
349
363
|
*/
|
|
350
364
|
export async function upgradeHelmChart(params) {
|
|
351
365
|
rejectValuesFileOnRemoteTransport(params.valuesFile);
|
|
366
|
+
assertOperandsNotFlagLike(params);
|
|
352
367
|
try {
|
|
353
368
|
// Add repository if provided
|
|
354
369
|
if (params.repo) {
|
|
@@ -415,6 +430,7 @@ export async function upgradeHelmChart(params) {
|
|
|
415
430
|
* @returns Promise with uninstall result
|
|
416
431
|
*/
|
|
417
432
|
export async function uninstallHelmChart(params) {
|
|
433
|
+
assertOperandsNotFlagLike(params);
|
|
418
434
|
try {
|
|
419
435
|
executeCommand("helm", [
|
|
420
436
|
"uninstall",
|