# How Secrets Masking Works for kubectl Commands in MCP Server Kubernetes

> Discover how MCP server masks Kubernetes Secret values in kubectl get commands. Learn to control this feature with the MASK_SECRETS environment variable for enhanced security. Understand secrets masking today.

- Repository: [Suyog Sonwalkar/mcp-server-kubernetes](https://github.com/flux159/mcp-server-kubernetes)
- Tags: how-to-guide
- Published: 2026-03-02

---

**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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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.

```typescript
const shouldMaskSecrets =
  process.env.MASK_SECRETS !== "false" &&
  (resourceType === "secrets" || resourceType === "secret");

```

(See lines 164‑168 in [src/tools/kubectl-get.ts](/blob/main/src/tools/kubectl-get.ts#L164-L168))

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.

```typescript
let processedResult = result;
if (shouldMaskSecrets) {
  processedResult = maskSecretsData(result, output);
}

```

(See lines 169‑172 in [src/tools/kubectl-get.ts](/blob/main/src/tools/kubectl-get.ts#L169-L172))

### 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.

```typescript
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](/blob/main/src/tools/kubectl-get.ts#L496-L511))

### 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.

```typescript
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](/blob/main/src/tools/kubectl-get.ts#L434-L452))

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.

```typescript
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](/blob/main/src/tools/kubectl-get.ts#L665-L684))

## 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:

```bash

# 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:

```json
{
  "method": "tools/call",
  "params": {
    "name": "kubectl_get",
    "arguments": {
      "resourceType": "secrets",
      "name": "my-secret",
      "namespace": "default",
      "output": "json"
    }
  }
}

```

**Response (masked):**

```json
{
  "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:

```bash
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-kubernetes` server masks Kubernetes Secret values by default, replacing all entries in the `data` field with `"***"`.
- **Environment control**: Set `MASK_SECRETS=false` to disable masking and retrieve raw Base64 values.
- **Targeted application**: Masking only applies to `kubectl get` commands where `resourceType` is `"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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/src/tools/kubectl-get.ts) (lines 164‑172 for decision logic, lines 434‑452 and 665‑684 for masking functions) with integration tests in [`tests/kubectl-get-secrets.test.ts`](https://github.com/flux159/mcp-server-kubernetes/blob/main/tests/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.