mcp-server-kubernetes 4.1.3 → 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.
@@ -1,6 +1,6 @@
1
1
  export declare const serverConfig: {
2
2
  readonly name: "kubernetes";
3
- readonly version: "4.1.3";
3
+ readonly version: "4.1.5";
4
4
  readonly capabilities: {
5
5
  readonly resources: {};
6
6
  readonly tools: {};
@@ -1,6 +1,6 @@
1
1
  export const serverConfig = {
2
2
  name: "kubernetes",
3
- version: "4.1.3",
3
+ version: "4.1.5",
4
4
  capabilities: {
5
5
  resources: {},
6
6
  tools: {},
@@ -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). We add
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 or substitute credentials, which ` +
163
- `would allow exfiltration of the operator's bearer token. If you ` +
164
- `genuinely need this flag, set ALLOW_KUBECTL_UNSAFE_FLAGS=true in the ` +
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "4.1.3",
3
+ "version": "4.1.5",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -46,7 +46,7 @@
46
46
  "@opentelemetry/sdk-node": "0.219.0",
47
47
  "@opentelemetry/semantic-conventions": "1.40.0",
48
48
  "express": "4.22.2",
49
- "js-yaml": "4.3.0",
49
+ "js-yaml": "4.3.1",
50
50
  "yaml": "2.8.3",
51
51
  "zod": "3.25.76"
52
52
  },