# CVE-2026-39884 — flux159/mcp-server-kubernetes argv flag-injection

| | |
|---|---|
| **Disclosed** | 2026-04-14 ([SentinelOne](https://www.sentinelone.com/vulnerability-database/cve-2026-39884/)) |
| **Affected** | `flux159/mcp-server-kubernetes` ≤ 3.4.x |
| **Fixed in** | 3.5.0 |
| **agent-airlock preset** | `flux159_mcp_kubernetes_cve_2026_39884_defaults()` (v0.5.6+) |
| **Module** | `agent_airlock.mcp_spec.argv_guard` |

## What broke

The `mcp-server-kubernetes` package built kubectl invocations by
**string-concatenating** caller-supplied fields like `localPort`,
`targetPort`, `namespace`, `resourceType`, and `resourceName` into a
**single argv element** before handing it to `subprocess.run`. A
single field value of `"8080 --kubeconfig=/etc/shadow"` therefore
became *extra flags* to kubectl rather than data. No shell
metacharacters were required.

Example (pre-fix):

```javascript
// vulnerable handler — concatenates user input into one argv slot
const args = [`--address=${userLocalPort}`, ...];
spawn("kubectl", args);  // shell=False, but kubectl re-parses the slot
```

Sending `userLocalPort = "0.0.0.0:8080 --kubeconfig=/tmp/mine"`
produces an argv element that kubectl interprets as multiple flags —
the second flag overrides the kubeconfig and the attacker controls
which cluster is targeted.

## Why it's a different class than CVE-2026-5023

agent-airlock's v0.5.5 `codebase_mcp_cve_2026_5023_defaults()` preset
rejects shell metacharacters (`;`, `&&`, `|`, `` ` ``, `$()`, etc.).
That's necessary but not sufficient: `8080 --kubeconfig=/etc/shadow`
contains *no* shell metacharacters, just an embedded space. The
attack surface is the consumer (kubectl), not the shell.

The v0.5.6 `argv_guard.enforce_argv_array` takes the stricter
position: every argv element must be a **single shell-safe token**.
A space in an element means the handler concatenated data into a
flag string — flag-injection territory.

## How agent-airlock blocks it

```python
from agent_airlock.policy_presets import flux159_mcp_kubernetes_cve_2026_39884_defaults
from agent_airlock import ArgvStringConcatenationError

cfg = flux159_mcp_kubernetes_cve_2026_39884_defaults()

try:
    cfg["check"](
        "port_forward",
        {
            "namespace": "default",
            "resourceType": "pod",
            "resourceName": "web-0",
            "localPort": "8080 --kubeconfig=/etc/shadow",  # injection
            "targetPort": "80",
        },
    )
except ArgvStringConcatenationError as e:
    print(f"blocked argv[{e.argv_index}] (field={e.field_name}): {e.offending_value}")
    # blocked argv[3] (field='localPort'): '8080 --kubeconfig=/etc/shadow'
```

The preset is **scoped to tool names matching `r"(?i)port_?forward"`**
— other handlers pass through unaffected. Five injection-prone
fields are checked in order: `namespace`, `resourceType`,
`resourceName`, `localPort`, `targetPort`.

## Direct primitive usage

If your handler isn't named `port_forward` but you still want the
guard, use the underlying primitive:

```python
from agent_airlock.mcp_spec.argv_guard import enforce_argv_array

enforce_argv_array(
    ["kubectl", "port-forward", "pod/web", user_localport, user_targetport],
    field_names=["argv0", "verb", "target", "localPort", "targetPort"],
)
```

`enforce_argv_array` raises `ArgvStringConcatenationError` on the
first element whose `shlex.quote(value) != value` round-trip — i.e.
any value `shlex` would have to wrap.

## Primary sources

- [SentinelOne CVE-2026-39884 (2026-04-14)](https://www.sentinelone.com/vulnerability-database/cve-2026-39884/)
- [NVD CVE-2026-39884](https://nvd.nist.gov/vuln/detail/CVE-2026-39884)
- Fixture: `tests/cves/fixtures/cve_2026_39884_kubectl_argv.json`
