How Secrets Masking Works for kubectl Commands in MCP Server Kubernetes
The MCP server automatically masks Kubernetes Secret values by replacing them with "*" when executing kubectl get commands for secrets, controlled by the MASK_SECRETS environment variable.**
The flux159/mcp-server-kubernetes repository implements a security feature that prevents sensitive data from leaking through Model Context Protocol (MCP) tool responses. When users retrieve Kubernetes Secret objects using the kubectl_get tool, the server intercepts the output and masks all values stored in the data field before returning the response to the client.
The Decision Logic for Masking
The masking behavior is determined at runtime by evaluating two conditions in src/tools/kubectl-get.ts. The server checks whether the MASK_SECRETS environment variable is explicitly disabled and whether the requested resource type is a Secret.
const shouldMaskSecrets =
process.env.MASK_SECRETS !== "false" &&
(resourceType === "secrets" || resourceType === "secret");
(See lines 164‑168 in src/tools/kubectl-get.ts)
If MASK_SECRETS is undefined, empty, or any value other than "false", masking is enabled by default. The logic only triggers when users specifically set MASK_SECRETS=false to retrieve raw Secret values.
How the Masking Pipeline Works
When the shouldMaskSecrets condition evaluates to true, the server executes the kubectl command and processes the output before returning it to the client. The raw output is captured using execFileSync, then conditionally passed to the masking function.
let processedResult = result;
if (shouldMaskSecrets) {
processedResult = maskSecretsData(result, output);
}
(See lines 169‑172 in src/tools/kubectl-get.ts)
Parsing and Format Handling
The maskSecretsData function handles both JSON and YAML output formats. It parses the raw kubectl output into a JavaScript object, applies the masking transformation, then serializes the result back to the original format.
function maskSecretsData(output: string, format: string): string {
if (format === "json") {
const parsed = JSON.parse(output);
const masked = maskDataValues(parsed);
return JSON.stringify(masked, null, 2);
} else if (format === "yaml") {
const parsed = yaml.load(output);
const masked = maskDataValues(parsed);
return yaml.dump(masked, { indent: 2, lineWidth: -1, noRefs: true });
}
// fall‑back: return the original output if parsing fails
}
(See lines 496‑511 in src/tools/kubectl-get.ts)
Recursive Value Masking
The core masking logic resides in two recursive functions: maskDataValues and maskAllLeafValues. The traversal begins at maskDataValues, which walks the entire object tree looking for keys named "data" that contain objects.
function maskDataValues(obj: any): any {
if (Array.isArray(obj)) return obj.map(maskDataValues);
if (typeof obj === "object") {
const result: any = {};
for (const key in obj) {
if (key === "data" && typeof obj[key] === "object" && obj[key] !== null) {
result[key] = maskAllLeafValues(obj[key]); // <‑‑ mask leaf values only inside "data"
} else {
result[key] = maskDataValues(obj[key]); // recurse into everything else
}
}
return result;
}
return obj;
}
(See lines 434‑452 in src/tools/kubectl-get.ts)
When a "data" object is found, maskAllLeafValues takes over to replace every primitive value (strings, numbers, booleans) with the placeholder "***" while preserving the object structure.
function maskAllLeafValues(obj: any): any {
const maskValue = "***";
if (Array.isArray(obj)) return obj.map(maskAllLeafValues);
if (typeof obj === "object") {
const result: any = {};
for (const key in obj) result[key] = maskAllLeafValues(obj[key]);
return result;
}
return maskValue; // primitive → mask
}
(See lines 665‑684 in src/tools/kubectl-get.ts)
Configuration and Usage Examples
The secrets masking functionality is controlled entirely through environment variables and applies automatically to all kubectl get operations targeting secrets.
Enabling Masking (Default)
By default, masking is active. You do not need to set any environment variable, or you can explicitly confirm the default behavior:
# Option 1: Omit the variable entirely
bun run start
# Option 2: Set to any value other than "false"
export MASK_SECRETS=true
bun run start
A client request to retrieve a secret:
{
"method": "tools/call",
"params": {
"name": "kubectl_get",
"arguments": {
"resourceType": "secrets",
"name": "my-secret",
"namespace": "default",
"output": "json"
}
}
}
Response (masked):
{
"content": [
{
"type": "text",
"text": "{\n \"apiVersion\": \"v1\",\n \"kind\": \"Secret\",\n \"metadata\": { \"name\": \"my-secret\", \"namespace\": \"default\" },\n \"data\": { \"username\": \"***\", \"password\": \"***\" }\n}"
}
]
}
Disabling Masking
To retrieve actual secret values (Base64‑encoded), disable the feature:
export MASK_SECRETS=false
bun run start
With masking disabled, the same client request returns the raw data field containing the actual Base64‑encoded secrets.
Summary
- Default protection: The
flux159/mcp-server-kubernetesserver masks Kubernetes Secret values by default, replacing all entries in thedatafield with"***". - Environment control: Set
MASK_SECRETS=falseto disable masking and retrieve raw Base64 values. - Targeted application: Masking only applies to
kubectl getcommands whereresourceTypeis"secrets"or"secret". - Format agnostic: The implementation handles both JSON and YAML output formats through recursive object traversal.
- Key implementation files: The logic resides in
src/tools/kubectl-get.ts(lines 164‑172 for decision logic, lines 434‑452 and 665‑684 for masking functions) with integration tests intests/kubectl-get-secrets.test.ts.
Frequently Asked Questions
How do I completely disable secrets masking in the MCP server?
Set the environment variable MASK_SECRETS to the string "false" before starting the server. Any other value, including an empty string or undefined variable, enables masking by default. For example: export MASK_SECRETS=false && bun run start.
Does masking affect all kubectl commands or only get operations?
The masking logic specifically targets kubectl get operations. The code checks the resourceType parameter and only activates when it equals "secrets" or "secret". Other operations like kubectl describe, kubectl create, or kubectl apply are not processed through the masking pipeline.
What happens if the kubectl output format is neither JSON nor YAML?
If the requested output format is something other than json or yaml, the maskSecretsData function falls back to returning the original unprocessed output. This prevents parsing errors from breaking the tool response, though it means secrets would be visible in non-standard formats.
Is the masking applied to nested data fields or only top-level data keys?
The masking applies to any object keyed by "data" at any depth in the object tree. The maskDataValues function recursively traverses the entire structure, and whenever it encounters a key named "data" whose value is an object, it invokes maskAllLeafValues to mask all primitive values within that specific object.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →