high · 7.9CVE-2026-54552Jul 17, 2026

CVE-2026-54552: sh Incomplete Privilege Drop via _uid (Supplementary Groups Not Cleared)

Rohit Hatagale
AI Security Researcher, SecureLayer7

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…

Packagesh
Ecosystempip
Affected< 2.2.4
Fixed in2.2.4

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.

python
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 cleared

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

Reporter not attributed.

References: [1][2][3]

Related research