mcp-server-kubernetes 4.0.8 → 4.1.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.
|
@@ -35,10 +35,39 @@ const DANGEROUS_FLAGS = new Set([
|
|
|
35
35
|
"as",
|
|
36
36
|
"as-group",
|
|
37
37
|
"as-uid",
|
|
38
|
+
// Writes to attacker-chosen filesystem paths
|
|
39
|
+
"profile-output",
|
|
40
|
+
"log-file",
|
|
41
|
+
"cache-dir",
|
|
38
42
|
]);
|
|
39
43
|
const SHORT_ALIASES = new Set([
|
|
40
44
|
"s", // -s is an alias for --server
|
|
41
45
|
]);
|
|
46
|
+
// Short flags that consume a value. pflag stops parsing a shorthand cluster at
|
|
47
|
+
// the first of these: everything after it inside the token is that flag's
|
|
48
|
+
// value ("-ojsonpath={.items[0]}"), not more flags. Every other letter is a
|
|
49
|
+
// boolean shorthand, which pflag parses and then keeps going past — so the
|
|
50
|
+
// scan in shortFlagLetters() must keep going too.
|
|
51
|
+
//
|
|
52
|
+
// Comparison is case-sensitive, exactly like pflag: "-A" (--all-namespaces)
|
|
53
|
+
// and "-a" are different flags.
|
|
54
|
+
//
|
|
55
|
+
// Only letters that take a value in *every* command that defines them belong
|
|
56
|
+
// here. "-f" is deliberately absent: it is --filename for apply/delete but the
|
|
57
|
+
// boolean --follow for logs, so pflag keeps parsing the cluster after it.
|
|
58
|
+
// Leaving a letter out is always the safe direction — at worst an attached
|
|
59
|
+
// value containing "s" is refused, and the split form ("-f /path") or the long
|
|
60
|
+
// form ("--filename=/path") still works.
|
|
61
|
+
const SHORT_VALUE_FLAGS = new Set([
|
|
62
|
+
"c", // --container
|
|
63
|
+
"k", // --kustomize
|
|
64
|
+
"l", // --selector
|
|
65
|
+
"L", // --label-columns
|
|
66
|
+
"n", // --namespace
|
|
67
|
+
"o", // --output
|
|
68
|
+
"s", // --server (dangerous; listed so the scan stops after it as well)
|
|
69
|
+
"v", // --v (log level)
|
|
70
|
+
]);
|
|
42
71
|
// helm exposes the same exfiltration surface as kubectl, but under "kube-"
|
|
43
72
|
// prefixed flag names (e.g. --kube-apiserver instead of --server). We add
|
|
44
73
|
// those here so the argv-level guard covers helm invocations too. Context
|
|
@@ -69,21 +98,48 @@ function normalizeFlagName(raw) {
|
|
|
69
98
|
const eq = name.indexOf("=");
|
|
70
99
|
if (eq !== -1)
|
|
71
100
|
name = name.slice(0, eq);
|
|
72
|
-
|
|
101
|
+
// kubectl and helm install pflag's WordSepNormalizeFunc, which treats "_" as
|
|
102
|
+
// equivalent to "-" in long flag names: "--insecure_skip_tls_verify" and
|
|
103
|
+
// "--insecure-skip-tls-verify" are the same flag. Normalize the same way so
|
|
104
|
+
// both spellings compare equal against the sets above.
|
|
105
|
+
return name.toLowerCase().replace(/_/g, "-");
|
|
73
106
|
}
|
|
74
|
-
//
|
|
75
|
-
// is not a single-dash short flag.
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
107
|
+
// Return every letter pflag would parse as a flag out of a single-dash token,
|
|
108
|
+
// in order, or null if the token is not a single-dash short flag.
|
|
109
|
+
//
|
|
110
|
+
// pflag walks a shorthand cluster letter by letter: a boolean shorthand is
|
|
111
|
+
// consumed and parsing continues with the next letter, while a value-taking
|
|
112
|
+
// shorthand swallows the remainder of the token as its value. "-Aowide" is
|
|
113
|
+
// therefore "-A -o wide", not a single unknown flag, so every letter pflag
|
|
114
|
+
// reaches has to be checked — not just the first one after the dash.
|
|
115
|
+
//
|
|
116
|
+
// Long "--" flags never attach a value without "=", so normalizeFlagName
|
|
117
|
+
// already handles them.
|
|
118
|
+
function shortFlagLetters(raw) {
|
|
81
119
|
if (!raw.startsWith("-") || raw.startsWith("--"))
|
|
82
120
|
return null;
|
|
83
121
|
const body = raw.slice(1);
|
|
84
122
|
if (body.length === 0)
|
|
85
123
|
return null;
|
|
86
|
-
|
|
124
|
+
const letters = [];
|
|
125
|
+
for (let i = 0; i < body.length; i++) {
|
|
126
|
+
const letter = body[i];
|
|
127
|
+
letters.push(letter);
|
|
128
|
+
// "-o=json": the "=" and everything after it is the value.
|
|
129
|
+
if (body[i + 1] === "=")
|
|
130
|
+
break;
|
|
131
|
+
// A value-taking shorthand consumes the rest of the token as its value,
|
|
132
|
+
// so no further letters are parsed as flags.
|
|
133
|
+
if (SHORT_VALUE_FLAGS.has(letter))
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
return letters;
|
|
137
|
+
}
|
|
138
|
+
function hasDangerousShortFlag(raw) {
|
|
139
|
+
const letters = shortFlagLetters(raw);
|
|
140
|
+
if (letters === null)
|
|
141
|
+
return false;
|
|
142
|
+
return letters.some((letter) => SHORT_ALIASES.has(letter));
|
|
87
143
|
}
|
|
88
144
|
function isDangerousFlagName(rawName, fromArgs) {
|
|
89
145
|
const name = normalizeFlagName(rawName);
|
|
@@ -91,13 +147,12 @@ function isDangerousFlagName(rawName, fromArgs) {
|
|
|
91
147
|
return true;
|
|
92
148
|
// Short aliases (-s) are only meaningful when they appear as a CLI token,
|
|
93
149
|
// not as a key in the `flags` object. Match both the bare/split forms
|
|
94
|
-
// (normalizeFlagName -> "s") and the attached
|
|
95
|
-
//
|
|
150
|
+
// (normalizeFlagName -> "s") and the attached/clustered forms ("-sURL",
|
|
151
|
+
// "-Ashttps://attacker"), where pflag parses the alias out of the cluster.
|
|
96
152
|
if (fromArgs) {
|
|
97
153
|
if (SHORT_ALIASES.has(name))
|
|
98
154
|
return true;
|
|
99
|
-
|
|
100
|
-
if (short !== null && SHORT_ALIASES.has(short))
|
|
155
|
+
if (hasDangerousShortFlag(rawName))
|
|
101
156
|
return true;
|
|
102
157
|
}
|
|
103
158
|
return false;
|
|
@@ -160,10 +215,10 @@ export function assertSafeArgv(args) {
|
|
|
160
215
|
const name = normalizeFlagName(tok);
|
|
161
216
|
if (ARGV_DANGEROUS_FLAGS.has(name) || SHORT_ALIASES.has(name))
|
|
162
217
|
reject(tok);
|
|
163
|
-
// Attached short-flag
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
if (
|
|
218
|
+
// Attached/clustered short-flag forms ("-sURL", "-Ashttps://attacker"):
|
|
219
|
+
// match every letter pflag would parse out of the cluster, not just the
|
|
220
|
+
// first one.
|
|
221
|
+
if (hasDangerousShortFlag(tok))
|
|
167
222
|
reject(tok);
|
|
168
223
|
}
|
|
169
224
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-server-kubernetes",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
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.21.2",
|
|
49
|
-
"js-yaml": "4.
|
|
49
|
+
"js-yaml": "4.3.0",
|
|
50
50
|
"yaml": "2.8.3",
|
|
51
51
|
"zod": "3.25.76"
|
|
52
52
|
},
|