CVE-2026-54552: sh Incomplete Privilege Drop via _uid (Supplementary Groups Not Cleared)
The Python sh library's _uid option changed a subprocess's user ID but forgot to clear the parent process's supplementary groups, so a child process expected to run with reduced privileges could still
The problem
The `_uid` special argument in the `sh` library is meant to launch a subprocess as a less-privileged user. On Linux, dropping privileges correctly requires three calls in order: `setgroups([])` to clear supplementary groups, then `setgid()`, then `setuid()`.
Versions before 2.2.4 performed `setuid()` and `setgid()` but skipped `setgroups([])`. A child process spawned from a root parent with `_uid=someuser` would still carry root's full supplementary group list, including groups like `docker`, `disk`, `shadow`, or `sudo`.
Any file or resource gated on those groups remained accessible to the supposedly sandboxed subprocess.
Proof of concept
A working proof-of-concept for CVE-2026-54552 in sh, with the exact payload below.
import os
import sh
# Run as root. Spawn a subprocess as an unprivileged uid.
# Before the fix, the child retains root's supplementary groups.
result = sh.python3(
"-c",
"import os; print('uid:', os.getuid(), 'groups:', os.getgroups())",
_uid=65534 # nobody
)
print(result)
# Vulnerable output example:
# uid: 65534 groups: [0, 4, 24, 27, 30, 46, 117] <-- root's groups still present
#
# Fixed output (2.2.4+):
# uid: 65534 groups: [] <-- supplementary groups clearedThe root cause is CWE-271 (Improper Reduction of Privileges). POSIX requires `setgroups()` to be called before `setuid()`/`setgid()` to fully relinquish a privileged process's group memberships. The `sh` library's pre-fork callback that applied `_uid` called `os.setgid()` and `os.setuid()` but had no `os.setgroups([])` call.
The patch for 2.2.4 adds `os.setgroups([])` as the first step inside the privilege-drop code path, clearing all supplementary groups before switching uid/gid. This mirrors the fix applied to Python's own subprocess module for CVE-2023-6507, which had the identical omission with `extra_groups=[]`.
The fix
Upgrade the `sh` package to version 2.2.4 or later (`pip install --upgrade 'sh>=2.2.4'`). As a workaround on older versions, avoid using `_uid` to enforce a privilege boundary from a privileged parent; use a separate process that starts unprivileged instead.
Related research
- high · 7.5CVE-2026-54567CVE-2026-54567: Flask-Reuploaded Extension Denylist Bypass via Case-Folding Asymmetry
- high · 8.3CVE-2026-54549CVE-2026-54549: meta-ads-mcp Server-Side Request Forgery in upload_ad_image
- high · 7.4CVE-2026-54547CVE-2026-54547: meta-ads-mcp Authentication Bypass via X-Pipeboard-Token Header
- high · 7.5CVE-2026-54234CVE-2026-54234: vLLM Remote DoS via Invalid Recovered Token Reinjection in Speculative Decoding